ts-expect-error を付与しながら .js を .ts に一括で書き換える
typescriptby mizchi created at 2020/06/23/20:52
やりたいこと
巨大なコードベースに対して、 .js
をとりあえず .ts
に書き換えてしまいたい。
だが、素朴な拡張子の書き換えで型違反が出ると jest やその他ツールが止まりはじめて面倒。
なので、エラー行には // @ts-expect-error
を自動で付与しながら書き換えたい。@ts-ignore
ではなく、@ts-expect-error
なのは、型エラーを修正した際に、それが伝搬するようにするため。
方法
- ファイルの拡張子を
.js
から.ts
に書き換える - typescript service 経由で型を検査し、エラー行を集める
- エラーが出た行に
// @ts-expect-error
を付与する
コード
書き捨てなので汚いです
import * as ts from "typescript";
import { uniq } from "lodash";
import fs from "fs";
import path from "path";
type ErrorLinesMap = { [k: string]: number[] };
function rewriteToTS(files: string[]) {
files.map((f) => {
fs.renameSync(f, f.replace(/\.js$/, ".ts"));
console.log("rename", f, f.replace(/\.js$/, ".ts"));
});
}
function compile(
tsFileNames: string[],
options: ts.CompilerOptions
): ErrorLinesMap {
const program = ts.createProgram(tsFileNames, options);
// skip write file. only diagnostics
const emitResult = program.emit(undefined, () => {});
const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
const errorLines: ErrorLinesMap = {};
allDiagnostics.forEach((diagnostic) => {
if (diagnostic.file) {
let { line } = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start!
);
let last = errorLines[diagnostic.file.fileName];
if (last == null) {
last = errorLines[diagnostic.file.fileName] = [];
}
errorLines[diagnostic.file.fileName] = uniq([...last, line]);
}
});
return errorLines;
}
function rewriteFiles(errorLines: ErrorLinesMap) {
Object.entries(errorLines).map(([file, errors]) => {
const content = fs.readFileSync(file, "utf-8");
const lines = content.split("\n");
const newLines = lines.map((line, idx) => {
const m = line.match(/^\s*/);
let head = "";
if (m) {
head = m[0];
}
if (errors.includes(idx)) {
return `${head}// @ts-expect-error\n${line}`;
} else {
return line;
}
});
// dry run if you need
const out = newLines.join("\n");
fs.writeFileSync(file, out);
console.log("update", file);
});
}
const configPath = path.join(process.cwd(), "tsconfig.json");
const tsconfig = ts.parseConfigFileTextToJson(
configPath,
fs.readFileSync(configPath, "utf-8")
);
const files = process.argv.slice(2);
rewriteToTS(files);
const tsFiles = files.map((f) => f.replace(".js", ".ts"));
const errors = compile(tsFiles, tsconfig.config!);
rewriteFiles(errors);
使い方
# yarn add ts-node typescript lodash -D
yarn ts-node -T -O '{"module":"commonjs"}' add-expect-error.ts src/*.js
こういう変換を想定
// src/foo.js
const x = {};
x.foo = 1;
// src/foo.ts
const x = {};
// @ts-expect-error
x.foo = 1;
注意点
- tsconfig.json があるディレクトリで実行してください
- transform 手順によりますが、ts の変換は babel と似た出力をするはずですが、完全に同一ではないです
雑なので色々動かないことはあると思いますが、一旦はこれで。
History
085ac04 - Update Tue Jun 23 22:11:13 2020 +0900
68e5f92 - Update Tue Jun 23 21:06:26 2020 +0900