Skip to content

Instantly share code, notes, and snippets.

@wonderful-panda
Last active August 19, 2021 18:34
Show Gist options
  • Save wonderful-panda/fbd7c6c848f938668a81bce18799ece8 to your computer and use it in GitHub Desktop.
Save wonderful-panda/fbd7c6c848f938668a81bce18799ece8 to your computer and use it in GitHub Desktop.
[TypeScript] 期待した通りのコンパイルエラーが出るかどうかテストするやつ
/*
* 1. ../tsconfig.json に設定ファイルを置く
* 2. ../cases/ の中に example.ts みたいなファイルを置く (/// TSXXXX: ~ でその行で期待するコンパイルを記述する。~の部分は正規表現)
* 3. _runner.ts を ava で実行すると、出力サンプルみたいな感じで結果が出力される
*/
import * as ts from "typescript/lib/typescript";
import * as fs from "fs";
import * as path from "path";
import * as glob from "glob";
import test from "ava";
interface ExpectedError {
code: string;
message?: RegExp;
}
interface ActualError {
code: string;
message?: string;
}
const NoError = { code: "<no error>" };
function getExpectedErrors(file: string): ExpectedError[] {
const lines = fs.readFileSync(file).toString().split(/\r?\n/);
const ret: ExpectedError[] = [];
lines.forEach((line, n) => {
const match = /\/\/\/\s*(TS[0-9]+)\s*:\s*(.*)$/.exec(line);
if (match) {
ret[n + 1] = { code: match[1], message: new RegExp(match[2]) };
}
});
return ret;
}
function getFailureMessage(line: number, expected: ExpectedError, actual: ActualError) {
const expectedString = expected.message ? `${ expected.code }: ${ expected.message }` : expected.code;
const actualString = actual.message ? `${ actual.code }: ${ actual.message }` : actual.code;
return (
`At line ${line}
........ -----------
........ [expected]
........ ${ expectedString.replace(/^/gm, " ") }
........ [actual]
........ ${ actualString.replace(/^/gm, " ") }
........ `.replace(/^\.+ /gm, ""));
}
const baseDir = path.dirname(__dirname);
const configPath = path.join(baseDir, "tsconfig.json");
const configContent = fs.readFileSync(configPath).toString();
const parsed = ts.parseJsonConfigFileContent(JSON.parse(configContent), ts.sys, baseDir);
const cases = glob.sync(path.join(baseDir, "cases/*.ts"));
const host: ts.LanguageServiceHost = {
getScriptFileNames: () => cases,
getScriptVersion: f => "0",
getScriptSnapshot: f => {
if (!fs.existsSync(f)) {
return undefined;
}
return ts.ScriptSnapshot.fromString(fs.readFileSync(f).toString());
},
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => parsed.options,
getDefaultLibFileName: options => ts.getDefaultLibFileName(options)
}
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
cases.forEach(f => {
const filename = path.basename(f);
test(filename, t => {
const expectedErrors = getExpectedErrors(f);
const output = service.getEmitOutput(f);
const errors: ActualError[] = [];
service.getSemanticDiagnostics(f).forEach(d => {
const { line, character } = d.file.getLineAndCharacterOfPosition(d.start);
const message = ts.flattenDiagnosticMessageText(d.messageText, "\n");
errors[line + 1] = { code: `TS${d.code}`, message };
});
const msg: string[] = [];
for (let i = 0; i < expectedErrors.length || i < errors.length; ++i) {
const expected = expectedErrors[i] || NoError;
const actual = errors[i] || NoError;
if (expected.code !== actual.code) {
msg.push(getFailureMessage(i, expected, actual));
}
else if (expected.message && actual.message && !expected.message.test(actual.message)) {
msg.push(getFailureMessage(i, expected, actual));
}
}
if (msg.length > 0) {
msg.push(`> ${msg.length} failures are found.`)
t.fail("\n" + msg.join("\n"));
}
});
});
interface Foo {
foo: string;
bar: number;
}
function testfunc(foo: Foo) { /// TS9999: unknown error
console.log(foo.fooo); /// TS2339: Property 'fooo' does not exist
}
testfunc({ foo: true }); /// TS2345: Property 'bar' is missing in
× example.ts
At line 6
-----------
[expected]
TS9999: /unknown error/
[actual]
<no error>
At line 10
-----------
[expected]
TS2345: /Property 'bar' is missing in/
[actual]
TS2345: Argument of type '{ foo: boolean; }' is not assignable to parameter of type 'Foo'.
Types of property 'foo' are incompatible.
Type 'boolean' is not assignable to type 'string'.
> 2 failures are found.
1 test failed [20:24:04]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment