Skip to content

Instantly share code, notes, and snippets.

@ericclemmons
Created May 10, 2024 20:23
Show Gist options
  • Save ericclemmons/55f10b9715e5150dfb526c9807c859ae to your computer and use it in GitHub Desktop.
Save ericclemmons/55f10b9715e5150dfb526c9807c859ae to your computer and use it in GitHub Desktop.
// @ts-check
/**
* Run `tsc` and pipe output to this script. All errors will write a `@ts-expect-error` comment into the source.
*
* Usage:
*
* tsc --noEmit --project ... | ./ts-expect-error.js
*
* Example (from the monorepo root):
*
* yarn tsc --noEmit --project packages/design-system | yarn workspace @honor/codemods codemod:ts-expect-error
*
* Finally, RUN IT AGAIN.
*
* If _any_ files are written again, then the affected file need manual review.
* If `prettier` moves the comment around, you can use [// prettier-ignore](https://prettier.io/docs/en/ignore.html) to prevent re-ordering of the comment.
*/
import MagicString from 'magic-string';
import {readFile, writeFile} from 'node:fs/promises';
import {relative, resolve} from 'node:path';
import {createInterface} from 'node:readline';
// This is a wrapper for `prettier.format(source, { parser: 'typescript', ...prettierConfig })`
import {format} from '../utils/format.js';
// Because the command output is piped to this script, we don't know what the true CWD is.
// So, I assume it's relative **from this script** to the the **root of the monorepo**.
const root = resolve('../../');
const lines = createInterface({input: process.stdin});
/** @type {Map<string, string>} */
const sources = new Map();
/** @type {Map<string, MagicString>} */
const updates = new Map();
for await (const line of lines) {
const match = line.match(
/(?<file>^.*)\((?<row>[\d]+),(?<column>[\d]+)\): error (?<tscode>TS\d+): (?<description>.*$)/,
);
if (!match) {
console.debug(
'\x1b[0m', // reset
`⏩ ${line}`,
);
continue;
}
console.debug(
'\x1b[1m\x1b[33m', // bold+yellow
`⏺️ ${line}`,
);
const {
groups: {file, tscode, description},
} = match;
const row = Number(match.groups.row);
const filepath = resolve(root, file);
if (!sources.has(filepath)) {
const source = await readFile(filepath, 'utf-8');
sources.set(filepath, source);
updates.set(filepath, new MagicString(source));
}
const newline = '\n';
const source = sources.get(filepath);
const lines = source.split(newline).slice(0, row - 1);
const lastLine = lines.at(-1);
const rowIndex = lines.join(newline).length;
// 🐛 Multiple errors for a single line should generate 1 comment, not multiple. This is rare, but causes another TSC error.
updates.get(filepath).prependLeft(
rowIndex,
/<\w+/.test(lastLine)
? // Looks like we're in JSX, so prettier will format it without an explicit \n
`\n{/* @ts-expect-error ${tscode}: ${description} */}`
: // Probably some regular JS
`\n// @ts-expect-error ${tscode}: ${description}`,
);
}
for (const [filepath, s] of updates) {
if (!s.hasChanged()) {
continue;
}
console.debug(
'\x1b[0m', // reset
'🔀 Rewriting',
'\x1b[1m\x1b[32m', // bold+green
relative(root, filepath),
'\x1b[0m', // reset
);
await writeFile(filepath, format(s.toString(), {filepath}), 'utf-8');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment