@definitelytyped/dtslint 0.0.197 → 0.0.198
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/lint.js +41 -70
- package/dist/lint.js.map +1 -1
- package/dist/util.d.ts +0 -1
- package/dist/util.js +1 -7
- package/dist/util.js.map +1 -1
- package/dtslint.json +0 -1
- package/package.json +2 -2
- package/src/lint.ts +45 -87
- package/src/util.ts +0 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/rules/expectRule.d.ts +0 -22
- package/dist/rules/expectRule.js +0 -387
- package/dist/rules/expectRule.js.map +0 -1
- package/dtslint-expect-only.json +0 -6
- package/src/rules/expectRule.ts +0 -455
- package/test/expect/expectType.ts.lint +0 -39
- package/test/expect/tsconfig.json +0 -1
- package/test/expect/tslint.json +0 -6
package/src/rules/expectRule.ts
DELETED
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import os = require("os");
|
|
3
|
-
import { basename, dirname, join, resolve as resolvePath } from "path";
|
|
4
|
-
import * as Lint from "tslint";
|
|
5
|
-
import * as TsType from "typescript";
|
|
6
|
-
import { last } from "../util";
|
|
7
|
-
|
|
8
|
-
type Program = TsType.Program;
|
|
9
|
-
type SourceFile = TsType.SourceFile;
|
|
10
|
-
|
|
11
|
-
// Based on https://github.com/danvk/typings-checker
|
|
12
|
-
|
|
13
|
-
const cacheDir = join(os.homedir(), ".dts");
|
|
14
|
-
const perfDir = join(os.homedir(), ".dts", "perf");
|
|
15
|
-
|
|
16
|
-
export class Rule extends Lint.Rules.TypedRule {
|
|
17
|
-
static metadata: Lint.IRuleMetadata = {
|
|
18
|
-
ruleName: "expect",
|
|
19
|
-
description: "Asserts types with $ExpectType.",
|
|
20
|
-
optionsDescription: "Not configurable.",
|
|
21
|
-
options: null,
|
|
22
|
-
type: "functionality",
|
|
23
|
-
typescriptOnly: true,
|
|
24
|
-
requiresTypeInfo: true,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
static FAILURE_STRING_DUPLICATE_ASSERTION = "This line has 2 $ExpectType assertions.";
|
|
28
|
-
static FAILURE_STRING_ASSERTION_MISSING_NODE =
|
|
29
|
-
"Can not match a node to this assertion. If this is a multiline function call, ensure the assertion is on the line above.";
|
|
30
|
-
|
|
31
|
-
// TODO: If this naming convention is required by tslint, dump it when switching to eslint
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
33
|
-
static FAILURE_STRING(expectedVersion: string, expectedType: string, actualType: string): string {
|
|
34
|
-
return `TypeScript@${expectedVersion} expected type to be:\n ${expectedType}\ngot:\n ${actualType}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
applyWithProgram(sourceFile: SourceFile, lintProgram: Program): Lint.RuleFailure[] {
|
|
38
|
-
const options = this.ruleArguments[0] as Options | undefined;
|
|
39
|
-
if (!options) {
|
|
40
|
-
return this.applyWithFunction(sourceFile, (ctx) =>
|
|
41
|
-
walk(ctx, lintProgram, TsType, "next", /*nextHigherVersion*/ undefined),
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const { tsconfigPath, versionsToTest } = options;
|
|
46
|
-
|
|
47
|
-
const getFailures = (
|
|
48
|
-
{ versionName, path }: VersionToTest,
|
|
49
|
-
nextHigherVersion: string | undefined,
|
|
50
|
-
writeOutput: boolean,
|
|
51
|
-
) => {
|
|
52
|
-
const ts = require(path);
|
|
53
|
-
ts.performance.enable();
|
|
54
|
-
const program = getProgram(tsconfigPath, ts, versionName, lintProgram);
|
|
55
|
-
const failures = this.applyWithFunction(sourceFile, (ctx) =>
|
|
56
|
-
walk(ctx, program, ts, versionName, nextHigherVersion),
|
|
57
|
-
);
|
|
58
|
-
if (writeOutput) {
|
|
59
|
-
const packageName = basename(dirname(tsconfigPath));
|
|
60
|
-
if (!packageName.match(/v\d+/) && !packageName.match(/ts\d\.\d/)) {
|
|
61
|
-
const d = {
|
|
62
|
-
[packageName]: extendedDiagnostics(ts, program),
|
|
63
|
-
};
|
|
64
|
-
if (!existsSync(cacheDir)) {
|
|
65
|
-
mkdirSync(cacheDir);
|
|
66
|
-
}
|
|
67
|
-
if (!existsSync(perfDir)) {
|
|
68
|
-
mkdirSync(perfDir);
|
|
69
|
-
}
|
|
70
|
-
writeFileSync(join(perfDir, `${packageName}.json`), JSON.stringify(d));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return failures;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const maxFailures = getFailures(last(versionsToTest), undefined, /*writeOutput*/ true);
|
|
77
|
-
if (maxFailures.length) {
|
|
78
|
-
return maxFailures;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// As an optimization, check the earliest version for errors;
|
|
82
|
-
// assume that if it works on min and max, it works for everything in between.
|
|
83
|
-
const minFailures = getFailures(versionsToTest[0], undefined, /*writeOutput*/ false);
|
|
84
|
-
if (!minFailures.length) {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// There are no failures in the max version, but there are failures in the min version.
|
|
89
|
-
// Work backward to find the newest version with failures.
|
|
90
|
-
for (let i = versionsToTest.length - 2; i >= 0; i--) {
|
|
91
|
-
const failures = getFailures(versionsToTest[i], options.versionsToTest[i + 1].versionName, /*writeOutput*/ false);
|
|
92
|
-
if (failures.length) {
|
|
93
|
-
return failures;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
throw new Error(); // unreachable -- at least the min version should have failures.
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
////////// copied from executeCommandLine /////
|
|
102
|
-
function extendedDiagnostics(ts: typeof TsType, program: Program) {
|
|
103
|
-
const caches = program.getRelationCacheSizes();
|
|
104
|
-
|
|
105
|
-
const perf: Record<string, number> = {
|
|
106
|
-
files: program.getSourceFiles().length,
|
|
107
|
-
...countLines(ts, program),
|
|
108
|
-
identifiers: program.getIdentifierCount(),
|
|
109
|
-
symbols: program.getSymbolCount(),
|
|
110
|
-
types: program.getTypeCount(),
|
|
111
|
-
instantiations: program.getInstantiationCount(),
|
|
112
|
-
memory: ts.sys.getMemoryUsage ? ts.sys.getMemoryUsage() : 0,
|
|
113
|
-
"assignability cache size": caches.assignable,
|
|
114
|
-
"identity cache size": caches.identity,
|
|
115
|
-
"subtype cache size": caches.subtype,
|
|
116
|
-
"strict subtype cache size": caches.strictSubtype,
|
|
117
|
-
};
|
|
118
|
-
(ts as any).performance.forEachMeasure((name: string, duration: number) => {
|
|
119
|
-
perf[name] = duration;
|
|
120
|
-
});
|
|
121
|
-
perf["total time"] = perf.Program + perf.Bind + perf.Check; // and maybe parse?? not sure, I think it's included in Program
|
|
122
|
-
return perf;
|
|
123
|
-
}
|
|
124
|
-
function countLines(ts: typeof TsType, program: Program): Record<string, number> {
|
|
125
|
-
const counts = {
|
|
126
|
-
library: 0,
|
|
127
|
-
definitions: 0,
|
|
128
|
-
typescript: 0,
|
|
129
|
-
javascript: 0,
|
|
130
|
-
json: 0,
|
|
131
|
-
other: 0,
|
|
132
|
-
};
|
|
133
|
-
for (const file of program.getSourceFiles()) {
|
|
134
|
-
counts[getCountKey(ts, program, file)] += (ts as any).getLineStarts(file).length;
|
|
135
|
-
}
|
|
136
|
-
return counts;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function getCountKey(ts: any, program: Program, file: SourceFile) {
|
|
140
|
-
if (program.isSourceFileDefaultLibrary(file)) {
|
|
141
|
-
return "library";
|
|
142
|
-
} else if (file.isDeclarationFile) {
|
|
143
|
-
return "definitions";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const path = (file as any).path;
|
|
147
|
-
if (ts.fileExtensionIsOneOf(path, ts.supportedTSExtensionsFlat)) {
|
|
148
|
-
return "typescript";
|
|
149
|
-
} else if (ts.fileExtensionIsOneOf(path, ts.supportedJSExtensionsFlat)) {
|
|
150
|
-
return "javascript";
|
|
151
|
-
} else if (ts.fileExtensionIs(path, ts.Extension.Json)) {
|
|
152
|
-
return "json";
|
|
153
|
-
} else {
|
|
154
|
-
return "other";
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface Options {
|
|
159
|
-
readonly tsconfigPath: string;
|
|
160
|
-
// These should be sorted with oldest first.
|
|
161
|
-
readonly versionsToTest: readonly VersionToTest[];
|
|
162
|
-
}
|
|
163
|
-
export interface VersionToTest {
|
|
164
|
-
readonly versionName: string;
|
|
165
|
-
readonly path: string;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const programCache = new WeakMap<Program, Map<string, Program>>();
|
|
169
|
-
/** Maps a tslint Program to one created with the version specified in `options`. */
|
|
170
|
-
export function getProgram(configFile: string, ts: typeof TsType, versionName: string, lintProgram: Program): Program {
|
|
171
|
-
let versionToProgram = programCache.get(lintProgram);
|
|
172
|
-
if (versionToProgram === undefined) {
|
|
173
|
-
versionToProgram = new Map<string, Program>();
|
|
174
|
-
programCache.set(lintProgram, versionToProgram);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let newProgram = versionToProgram.get(versionName);
|
|
178
|
-
if (newProgram === undefined) {
|
|
179
|
-
newProgram = createProgram(configFile, ts);
|
|
180
|
-
versionToProgram.set(versionName, newProgram);
|
|
181
|
-
}
|
|
182
|
-
return newProgram;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function createProgram(configFile: string, ts: typeof TsType): Program {
|
|
186
|
-
const projectDirectory = dirname(configFile);
|
|
187
|
-
const { config } = ts.readConfigFile(configFile, ts.sys.readFile);
|
|
188
|
-
const parseConfigHost: TsType.ParseConfigHost = {
|
|
189
|
-
fileExists: existsSync,
|
|
190
|
-
readDirectory: ts.sys.readDirectory,
|
|
191
|
-
readFile: (file) => readFileSync(file, "utf8"),
|
|
192
|
-
useCaseSensitiveFileNames: true,
|
|
193
|
-
};
|
|
194
|
-
const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, resolvePath(projectDirectory), {
|
|
195
|
-
noEmit: true,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (config.compilerOptions?.module === "node16" && parsed.options.module === undefined) {
|
|
199
|
-
// TypeScript version is too old to handle the "node16" module option,
|
|
200
|
-
// but we can run tests falling back to commonjs/node.
|
|
201
|
-
parsed.options.module = ts.ModuleKind.CommonJS;
|
|
202
|
-
parsed.options.moduleResolution = ts.ModuleResolutionKind.NodeJs;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const host = ts.createCompilerHost(parsed.options, true);
|
|
206
|
-
return ts.createProgram(parsed.fileNames, parsed.options, host);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function walk(
|
|
210
|
-
ctx: Lint.WalkContext<void>,
|
|
211
|
-
program: Program,
|
|
212
|
-
ts: typeof TsType,
|
|
213
|
-
versionName: string,
|
|
214
|
-
nextHigherVersion: string | undefined,
|
|
215
|
-
): void {
|
|
216
|
-
const { fileName } = ctx.sourceFile;
|
|
217
|
-
const sourceFile = program.getSourceFile(fileName)!;
|
|
218
|
-
if (!sourceFile) {
|
|
219
|
-
ctx.addFailure(
|
|
220
|
-
0,
|
|
221
|
-
0,
|
|
222
|
-
`Program source files differ between TypeScript versions. This may be a dtslint bug.\n` +
|
|
223
|
-
`Expected to find a file '${fileName}' present in ${TsType.version}, but did not find it in ts@${versionName}.`,
|
|
224
|
-
);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const checker = program.getTypeChecker();
|
|
229
|
-
// Don't care about emit errors.
|
|
230
|
-
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
|
|
231
|
-
for (const diagnostic of diagnostics) {
|
|
232
|
-
addDiagnosticFailure(diagnostic);
|
|
233
|
-
}
|
|
234
|
-
if (sourceFile.isDeclarationFile || !sourceFile.text.includes("$ExpectType")) {
|
|
235
|
-
// Normal file.
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const { typeAssertions, duplicates } = parseAssertions(sourceFile);
|
|
240
|
-
|
|
241
|
-
for (const line of duplicates) {
|
|
242
|
-
addFailureAtLine(line, Rule.FAILURE_STRING_DUPLICATE_ASSERTION);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const { unmetExpectations, unusedAssertions } = getExpectTypeFailures(sourceFile, typeAssertions, checker, ts);
|
|
246
|
-
for (const { node, expected, actual } of unmetExpectations) {
|
|
247
|
-
ctx.addFailureAtNode(node, Rule.FAILURE_STRING(versionName, expected, actual));
|
|
248
|
-
}
|
|
249
|
-
for (const line of unusedAssertions) {
|
|
250
|
-
addFailureAtLine(line, Rule.FAILURE_STRING_ASSERTION_MISSING_NODE);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function addDiagnosticFailure(diagnostic: TsType.Diagnostic): void {
|
|
254
|
-
const intro = getIntro();
|
|
255
|
-
if (diagnostic.file === sourceFile) {
|
|
256
|
-
const msg = `${intro}\n${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`;
|
|
257
|
-
ctx.addFailureAt(diagnostic.start!, diagnostic.length!, msg);
|
|
258
|
-
} else {
|
|
259
|
-
ctx.addFailureAt(0, 0, `${intro}\n${fileName}${diagnostic.messageText}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function getIntro(): string {
|
|
264
|
-
if (nextHigherVersion === undefined) {
|
|
265
|
-
return `TypeScript@${versionName} compile error: `;
|
|
266
|
-
} else {
|
|
267
|
-
const msg = `Compile error in typescript@${versionName} but not in typescript@${nextHigherVersion}.\n`;
|
|
268
|
-
const explain =
|
|
269
|
-
nextHigherVersion === "next"
|
|
270
|
-
? "TypeScript@next features not yet supported."
|
|
271
|
-
: `Fix by adding '"minimumTypeScriptVersion": "${nextHigherVersion}"' to package.json.`;
|
|
272
|
-
return msg + explain;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function addFailureAtLine(line: number, failure: string): void {
|
|
277
|
-
const start = sourceFile.getPositionOfLineAndCharacter(line, 0);
|
|
278
|
-
let end = start + sourceFile.text.split("\n")[line].length;
|
|
279
|
-
if (sourceFile.text[end - 1] === "\r") {
|
|
280
|
-
end--;
|
|
281
|
-
}
|
|
282
|
-
ctx.addFailure(start, end, `TypeScript@${versionName}: ${failure}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
interface Assertions {
|
|
287
|
-
/** Map from a line number to the expected type at that line. */
|
|
288
|
-
readonly typeAssertions: Map<number, string>;
|
|
289
|
-
/** Lines with more than one assertion (these are errors). */
|
|
290
|
-
readonly duplicates: readonly number[];
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function parseAssertions(sourceFile: SourceFile): Assertions {
|
|
294
|
-
const typeAssertions = new Map<number, string>();
|
|
295
|
-
const duplicates: number[] = [];
|
|
296
|
-
|
|
297
|
-
const { text } = sourceFile;
|
|
298
|
-
const commentRegexp = /\/\/(.*)/g;
|
|
299
|
-
const lineStarts = sourceFile.getLineStarts();
|
|
300
|
-
let curLine = 0;
|
|
301
|
-
|
|
302
|
-
while (true) {
|
|
303
|
-
const commentMatch = commentRegexp.exec(text);
|
|
304
|
-
if (commentMatch === null) {
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
// Match on the contents of that comment so we do nothing in a commented-out assertion,
|
|
308
|
-
// i.e. `// foo; // $ExpectType number`
|
|
309
|
-
if (!commentMatch[1].startsWith(" $ExpectType ")) {
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
const line = getLine(commentMatch.index);
|
|
313
|
-
const expectedType = commentMatch[1].slice(" $ExpectType ".length);
|
|
314
|
-
// Don't bother with the assertion if there are 2 assertions on 1 line. Just fail for the duplicate.
|
|
315
|
-
if (typeAssertions.delete(line)) {
|
|
316
|
-
duplicates.push(line);
|
|
317
|
-
} else {
|
|
318
|
-
typeAssertions.set(line, expectedType);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return { typeAssertions, duplicates };
|
|
323
|
-
|
|
324
|
-
function getLine(pos: number): number {
|
|
325
|
-
// advance curLine to be the line preceding 'pos'
|
|
326
|
-
while (lineStarts[curLine + 1] <= pos) {
|
|
327
|
-
curLine++;
|
|
328
|
-
}
|
|
329
|
-
// If this is the first token on the line, it applies to the next line.
|
|
330
|
-
// Otherwise, it applies to the text to the left of it.
|
|
331
|
-
return isFirstOnLine(text, lineStarts[curLine], pos) ? curLine + 1 : curLine;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function isFirstOnLine(text: string, lineStart: number, pos: number): boolean {
|
|
336
|
-
for (let i = lineStart; i < pos; i++) {
|
|
337
|
-
if (text[i] !== " ") {
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return true;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
interface ExpectTypeFailures {
|
|
345
|
-
/** Lines with an $ExpectType, but a different type was there. */
|
|
346
|
-
readonly unmetExpectations: readonly { node: TsType.Node; expected: string; actual: string }[];
|
|
347
|
-
/** Lines with an $ExpectType, but no node could be found. */
|
|
348
|
-
readonly unusedAssertions: Iterable<number>;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function matchReadonlyArray(actual: string, expected: string) {
|
|
352
|
-
if (!(/\breadonly\b/.test(actual) && /\bReadonlyArray\b/.test(expected))) return false;
|
|
353
|
-
const readonlyArrayRegExp = /\bReadonlyArray</y;
|
|
354
|
-
const readonlyModifierRegExp = /\breadonly /y;
|
|
355
|
-
|
|
356
|
-
// A<ReadonlyArray<B<ReadonlyArray<C>>>>
|
|
357
|
-
// A<readonly B<readonly C[]>[]>
|
|
358
|
-
|
|
359
|
-
let expectedPos = 0;
|
|
360
|
-
let actualPos = 0;
|
|
361
|
-
let depth = 0;
|
|
362
|
-
while (expectedPos < expected.length && actualPos < actual.length) {
|
|
363
|
-
const expectedChar = expected.charAt(expectedPos);
|
|
364
|
-
const actualChar = actual.charAt(actualPos);
|
|
365
|
-
if (expectedChar === actualChar) {
|
|
366
|
-
expectedPos++;
|
|
367
|
-
actualPos++;
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// check for end of readonly array
|
|
372
|
-
if (
|
|
373
|
-
depth > 0 &&
|
|
374
|
-
expectedChar === ">" &&
|
|
375
|
-
actualChar === "[" &&
|
|
376
|
-
actualPos < actual.length - 1 &&
|
|
377
|
-
actual.charAt(actualPos + 1) === "]"
|
|
378
|
-
) {
|
|
379
|
-
depth--;
|
|
380
|
-
expectedPos++;
|
|
381
|
-
actualPos += 2;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// check for start of readonly array
|
|
386
|
-
readonlyArrayRegExp.lastIndex = expectedPos;
|
|
387
|
-
readonlyModifierRegExp.lastIndex = actualPos;
|
|
388
|
-
if (readonlyArrayRegExp.test(expected) && readonlyModifierRegExp.test(actual)) {
|
|
389
|
-
depth++;
|
|
390
|
-
expectedPos += 14; // "ReadonlyArray<".length;
|
|
391
|
-
actualPos += 9; // "readonly ".length;
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function getExpectTypeFailures(
|
|
402
|
-
sourceFile: SourceFile,
|
|
403
|
-
typeAssertions: Map<number, string>,
|
|
404
|
-
checker: TsType.TypeChecker,
|
|
405
|
-
ts: typeof TsType,
|
|
406
|
-
): ExpectTypeFailures {
|
|
407
|
-
const unmetExpectations: { node: TsType.Node; expected: string; actual: string }[] = [];
|
|
408
|
-
// Match assertions to the first node that appears on the line they apply to.
|
|
409
|
-
// `forEachChild` isn't available as a method in older TypeScript versions, so must use `ts.forEachChild` instead.
|
|
410
|
-
ts.forEachChild(sourceFile, function iterate(node) {
|
|
411
|
-
const line = lineOfPosition(node.getStart(sourceFile), sourceFile);
|
|
412
|
-
const expected = typeAssertions.get(line);
|
|
413
|
-
if (expected !== undefined) {
|
|
414
|
-
// https://github.com/Microsoft/TypeScript/issues/14077
|
|
415
|
-
if (node.kind === ts.SyntaxKind.ExpressionStatement) {
|
|
416
|
-
node = (node as TsType.ExpressionStatement).expression;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const type = checker.getTypeAtLocation(getNodeForExpectType(node, ts));
|
|
420
|
-
|
|
421
|
-
const actual = type
|
|
422
|
-
? checker.typeToString(type, /*enclosingDeclaration*/ undefined, ts.TypeFormatFlags.NoTruncation)
|
|
423
|
-
: "";
|
|
424
|
-
|
|
425
|
-
if (!expected.split(/\s*\|\|\s*/).some((s) => actual === s || matchReadonlyArray(actual, s))) {
|
|
426
|
-
unmetExpectations.push({ node, expected, actual });
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
typeAssertions.delete(line);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
ts.forEachChild(node, iterate);
|
|
433
|
-
});
|
|
434
|
-
return { unmetExpectations, unusedAssertions: typeAssertions.keys() };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function getNodeForExpectType(node: TsType.Node, ts: typeof TsType): TsType.Node {
|
|
438
|
-
if (node.kind === ts.SyntaxKind.VariableStatement) {
|
|
439
|
-
// ts2.0 doesn't have `isVariableStatement`
|
|
440
|
-
const {
|
|
441
|
-
declarationList: { declarations },
|
|
442
|
-
} = node as TsType.VariableStatement;
|
|
443
|
-
if (declarations.length === 1) {
|
|
444
|
-
const { initializer } = declarations[0];
|
|
445
|
-
if (initializer) {
|
|
446
|
-
return initializer;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return node;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function lineOfPosition(pos: number, sourceFile: SourceFile): number {
|
|
454
|
-
return sourceFile.getLineAndCharacterOfPosition(pos).line;
|
|
455
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// $ExpectType xxx
|
|
3
|
-
|
|
4
|
-
~nil [TypeScript@next: Can not match a node to this assertion. If this is a multiline function call, ensure the assertion is on the line above.]
|
|
5
|
-
|
|
6
|
-
// $ExpectType number[]
|
|
7
|
-
[1, 2];
|
|
8
|
-
|
|
9
|
-
// $ExpectType 1
|
|
10
|
-
1; // $ExpectType 2
|
|
11
|
-
~~~~~~~~~~~~~~~~~~~ [TypeScript@next: This line has 2 $ExpectType assertions.]
|
|
12
|
-
|
|
13
|
-
// Do nothing for commented-out comments
|
|
14
|
-
// f; // $ExpectType foo
|
|
15
|
-
|
|
16
|
-
declare function f(
|
|
17
|
-
one: number,
|
|
18
|
-
two: [number, number],
|
|
19
|
-
three: [number, number, number],
|
|
20
|
-
four: [number, number, number, number]): number;
|
|
21
|
-
|
|
22
|
-
// Test that we never truncate types.
|
|
23
|
-
f; // $ExpectType (one: number, two: [number, number], three: [number, number, number], four: [number, number, number, number]) => number
|
|
24
|
-
|
|
25
|
-
// Test assertion at the end of a multiline function call
|
|
26
|
-
f(
|
|
27
|
-
1,
|
|
28
|
-
[2, 2],
|
|
29
|
-
[3, 3, 3,],
|
|
30
|
-
[4, 4, 4, 4]
|
|
31
|
-
) // $ExpectType number;
|
|
32
|
-
~~~~~~~~~~~~~~~~~~~~~~~~ [TypeScript@next: Can not match a node to this assertion. If this is a multiline function call, ensure the assertion is on the line above.]
|
|
33
|
-
|
|
34
|
-
// Test that we get the type of the initializer on a variable declaration.
|
|
35
|
-
// $ExpectType 1
|
|
36
|
-
const x = 1;
|
|
37
|
-
|
|
38
|
-
declare const ar: readonly number[];
|
|
39
|
-
ar; // $ExpectType ReadonlyArray<number>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|