forked from tsdjs/tsd
/
compiler.ts
143 lines (119 loc) · 4.77 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import {
flattenDiagnosticMessageText,
createProgram,
Diagnostic as TSDiagnostic,
SourceFile
} from '@tsd/typescript';
import {ExpectedError, 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>([
// Older TS version report 'await expression only allowed within async function
DiagnosticCode.AwaitExpressionOnlyAllowedWithinAsyncFunction,
DiagnosticCode.TopLevelAwaitOnlyAllowedWhenModuleESNextOrSystem
]);
// List of diagnostic codes which should be ignored inside `expectError` statements
const expectErrordiagnosticCodesToIgnore = new Set<DiagnosticCode>([
DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
DiagnosticCode.PropertyDoesNotExistOnType,
DiagnosticCode.CannotAssignToReadOnlyProperty,
DiagnosticCode.TypeIsNotAssignableToOtherType,
DiagnosticCode.TypeDoesNotSatisfyTheConstraint,
DiagnosticCode.GenericTypeRequiresTypeArguments,
DiagnosticCode.ExpectedArgumentsButGotOther,
DiagnosticCode.NoOverloadExpectsCountOfArguments,
DiagnosticCode.NoOverloadExpectsCountOfTypeArguments,
DiagnosticCode.NoOverloadMatches,
DiagnosticCode.PropertyMissingInType1ButRequiredInType2,
DiagnosticCode.TypeHasNoPropertiesInCommonWith,
DiagnosticCode.ThisContextOfTypeNotAssignableToMethodOfThisType,
DiagnosticCode.ValueOfTypeNotCallable,
DiagnosticCode.ExpressionNotCallable,
DiagnosticCode.OnlyVoidFunctionIsNewCallable,
DiagnosticCode.ExpressionNotConstructable,
DiagnosticCode.NewExpressionTargetLackingConstructSignatureHasAnyType,
DiagnosticCode.MemberCannotHaveOverrideModifierBecauseItIsNotDeclaredInBaseClass,
DiagnosticCode.MemberMustHaveOverrideModifier,
]);
type IgnoreDiagnosticResult = 'preserve' | 'ignore' | Location;
/**
* Check if the provided diagnostic should be ignored.
*
* @param diagnostic - The diagnostic to validate.
* @param expectedErrors - Map of the expected errors.
* @returns Whether the diagnostic should be `'preserve'`d, `'ignore'`d or, in case that
* the diagnostic is reported from inside of an `expectError` assertion, the `Location`
* of the assertion.
*/
const ignoreDiagnostic = (
diagnostic: TSDiagnostic,
expectedErrors: Map<Location, ExpectedError>
): IgnoreDiagnosticResult => {
if (ignoredDiagnostics.has(diagnostic.code)) {
// Filter out diagnostics which are present in the `ignoredDiagnostics` set
return 'ignore';
}
if (!expectErrordiagnosticCodesToIgnore.has(diagnostic.code)) {
return 'preserve';
}
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) {
return location;
}
}
return 'preserve';
};
/**
* 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 diagnostics: Diagnostic[] = [];
const program = createProgram(context.testFiles, context.config.compilerOptions);
const tsDiagnostics = program
.getSemanticDiagnostics()
.concat(program.getSyntacticDiagnostics());
const assertions = extractAssertions(program);
diagnostics.push(...handle(program.getTypeChecker(), assertions));
const expectedErrors = parseErrorAssertionToLocation(assertions);
const expectedErrorsLocationsWithFoundDiagnostics: Location[] = [];
for (const diagnostic of tsDiagnostics) {
/* Filter out all diagnostic messages without a file or from node_modules directories, files under
* node_modules are most definitely not under test.
*/
if (!diagnostic.file || /[/\\]node_modules[/\\]/.test(diagnostic.file.fileName)) {
continue;
}
const ignoreDiagnosticResult = ignoreDiagnostic(diagnostic, expectedErrors);
if (ignoreDiagnosticResult !== 'preserve') {
if (ignoreDiagnosticResult !== 'ignore') {
expectedErrorsLocationsWithFoundDiagnostics.push(ignoreDiagnosticResult);
}
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 errorLocationToRemove of expectedErrorsLocationsWithFoundDiagnostics) {
expectedErrors.delete(errorLocationToRemove);
}
for (const [, diagnostic] of expectedErrors) {
diagnostics.push({
...diagnostic,
message: 'Expected an error, but found none.',
severity: 'error'
});
}
return diagnostics;
};