-
-
Notifications
You must be signed in to change notification settings - Fork 64
/
compiler.ts
108 lines (88 loc) · 3.26 KB
/
compiler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import * as path from 'path';
import {
flattenDiagnosticMessageText,
createProgram,
Diagnostic as TSDiagnostic,
SourceFile
} from '../../libraries/typescript';
import {extractAssertions, parseErrorAssertionToLocation} from './parser';
import {Diagnostic, DiagnosticCode, Context, Location} from './interfaces';
import {handle} from './assertions';
// List of diagnostic codes that should be ignored in general
const ignoredDiagnostics = new Set<number>([
DiagnosticCode.AwaitIsOnlyAllowedInAsyncFunction
]);
// List of diagnostic codes which should be ignored inside `expectError` statements
const diagnosticCodesToIgnore = new Set<DiagnosticCode>([
DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
DiagnosticCode.PropertyDoesNotExistOnType,
DiagnosticCode.CannotAssignToReadOnlyProperty,
DiagnosticCode.TypeIsNotAssignableToOtherType,
DiagnosticCode.GenericTypeRequiresTypeArguments,
DiagnosticCode.ExpectedArgumentsButGotOther,
DiagnosticCode.NoOverloadMatches
]);
/**
* Check if the provided diagnostic should be ignored.
*
* @param diagnostic - The diagnostic to validate.
* @param expectedErrors - Map of the expected errors.
* @returns Boolean indicating if the diagnostic should be ignored or not.
*/
const ignoreDiagnostic = (diagnostic: TSDiagnostic, expectedErrors: Map<Location, any>): boolean => {
if (ignoredDiagnostics.has(diagnostic.code)) {
// Filter out diagnostics which are present in the `ignoredDiagnostics` set
return true;
}
if (!diagnosticCodesToIgnore.has(diagnostic.code)) {
return false;
}
const diagnosticFileName = (diagnostic.file as SourceFile).fileName;
for (const [location] of expectedErrors) {
const start = diagnostic.start as number;
if (diagnosticFileName === location.fileName && start > location.start && start < location.end) {
// Remove the expected error from the Map so it's not being reported as failure
expectedErrors.delete(location);
return true;
}
}
return false;
};
/**
* Get a list of TypeScript diagnostics within the current context.
*
* @param context - The context object.
* @returns List of diagnostics
*/
export const getDiagnostics = (context: Context): Diagnostic[] => {
const fileNames = context.testFiles.map(fileName => path.join(context.cwd, fileName));
const diagnostics: Diagnostic[] = [];
const program = createProgram(fileNames, context.config.compilerOptions);
const tsDiagnostics = program
.getSemanticDiagnostics()
.concat(program.getSyntacticDiagnostics());
const assertions = extractAssertions(program);
diagnostics.push(...handle(program.getTypeChecker(), assertions));
const expectedErrors = parseErrorAssertionToLocation(assertions);
for (const diagnostic of tsDiagnostics) {
if (!diagnostic.file || ignoreDiagnostic(diagnostic, expectedErrors)) {
continue;
}
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start as number);
diagnostics.push({
fileName: diagnostic.file.fileName,
message: flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
severity: 'error',
line: position.line + 1,
column: position.character
});
}
for (const [, diagnostic] of expectedErrors) {
diagnostics.push({
...diagnostic,
message: 'Expected an error, but found none.',
severity: 'error'
});
}
return diagnostics;
};