|
import { Buffer } from "node:buffer"; |
|
import { readFile, writeFile } from "node:fs/promises"; |
|
|
|
/** |
|
* Parses tsc stdout lines into error objects. |
|
*/ |
|
function parseTsErrors(content) { |
|
return [ |
|
...content.matchAll( |
|
/^(?<file>[^\( ]+)\((?<lineNr>\d+),(?<columnNr>\d+)\): error TS(?<code>\d+): (?<description>.*)/gm, |
|
), |
|
].map(({ groups: { file, lineNr, columnNr, code, description } }) => ({ |
|
file, |
|
lineNr: parseInt(lineNr, 10), |
|
columnNr: parseInt(columnNr, 10), |
|
code: parseInt(code, 10), |
|
description, |
|
})); |
|
} |
|
|
|
/** |
|
* Deduplicates an array of items by a key function. |
|
* Returns the first occurrence of each unique key. |
|
*/ |
|
function uniqueBy(items, keyFn) { |
|
return [...new Map(items.map((value) => [keyFn(value), value])).values()]; |
|
} |
|
|
|
async function readText(stream) { |
|
const chunks = []; |
|
for await (const chunk of stream) chunks.push(chunk); |
|
return Buffer.concat(chunks).toString("utf8"); |
|
} |
|
|
|
const content = await readText(process.stdin); |
|
|
|
const fileErrors = Object.entries( |
|
Object.groupBy(parseTsErrors(content), ({ file }) => file), |
|
).map(([file, errors]) => [ |
|
file, |
|
// Ignore multiple errors on the same line. Only pick the first one. |
|
uniqueBy(errors, ({ file, lineNr }) => lineNr) |
|
// Sort the errors by line number in descending order. |
|
// Having them in descending order helps with making changes. The line numbers remain valid. |
|
.toSorted((a, b) => b.lineNr - a.lineNr), |
|
]); |
|
for (const [file, errors] of fileErrors) { |
|
const content = await readFile(file, "utf-8"); |
|
const lines = content.split("\n"); |
|
|
|
for (const { lineNr, code } of errors) { |
|
const lineIndex = lineNr - 1; |
|
const line = lines[lineIndex]; |
|
const indentation = line.match(/^\s*/)[0]; |
|
|
|
function log(message) { |
|
console.log(`${file}:${lineNr}: ${message}`); |
|
} |
|
|
|
if (code === 2578) { |
|
// Remove @ts-expect-error for TS2578. |
|
// TS2578: Unused '@ts-expect-error' directive. |
|
// Sanity check. |
|
if (!/^\s*\/\/\s*@ts-expect-error/.test(line)) { |
|
log("Expected // @ts-expect-error directive"); |
|
continue; |
|
} |
|
// Also remove TODO comments above @ts-expect-error. |
|
if (/^\s*\/\/\s*TODO:/.test(lines[lineIndex - 1])) { |
|
log("Removed unused @ts-expect-error and TODO comment"); |
|
lines.splice(lineIndex - 1, 2); |
|
} else { |
|
log("Removed unused @ts-expect-error"); |
|
lines.splice(lineIndex, 1); |
|
} |
|
} else { |
|
// Add @ts-expect-error for all other errors. |
|
log("Added @ts-expect-error"); |
|
lines.splice(lineIndex, 0, `${indentation}// @ts-expect-error`); |
|
lines.splice(lineIndex, 0, `${indentation}// TODO: Resolve type error`); |
|
} |
|
} |
|
|
|
const newContent = lines.join("\n"); |
|
await writeFile(file, newContent, "utf-8"); |
|
} |