-
-
Notifications
You must be signed in to change notification settings - Fork 63
/
compiler.ts
180 lines (154 loc) · 6.71 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import {
flattenDiagnosticMessageText,
createProgram,
Diagnostic as TSDiagnostic
} 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.GenericTypeRequiresBetweenXAndYTypeArugments,
DiagnosticCode.ExpectedArgumentsButGotOther,
DiagnosticCode.ExpectedAtLeastArgumentsButGotOther,
DiagnosticCode.NoOverloadExpectsCountOfArguments,
DiagnosticCode.NoOverloadExpectsCountOfTypeArguments,
DiagnosticCode.NoOverloadMatches,
DiagnosticCode.Type1IsMissingPropertiesFromType2,
DiagnosticCode.PropertyMissingInType1ButRequiredInType2,
DiagnosticCode.TypeHasNoPropertiesInCommonWith,
DiagnosticCode.ThisContextOfTypeNotAssignableToMethodOfThisType,
DiagnosticCode.ValueOfTypeNotCallable,
DiagnosticCode.ExpressionNotCallable,
DiagnosticCode.TypeNotAssignableWithExactOptionalPropertyTypes,
DiagnosticCode.TypeNotAssignableToParameterWithExactOptionalPropertyTypes,
DiagnosticCode.TypeNotAssignableTypeOfTargetWithExactOptionalPropertyTypes,
DiagnosticCode.IndexSignatureOnlyPermitsReading,
DiagnosticCode.OnlyVoidFunctionIsNewCallable,
DiagnosticCode.ExpressionNotConstructable,
DiagnosticCode.NewExpressionTargetLackingConstructSignatureHasAnyType,
DiagnosticCode.MemberCannotHaveOverrideModifierBecauseItIsNotDeclaredInBaseClass,
DiagnosticCode.MemberMustHaveOverrideModifier,
DiagnosticCode.StringLiteralTypeIsNotAssignableToUnionTypeWithSuggestion,
DiagnosticCode.ObjectLiteralMayOnlySpecifyKnownProperties,
DiagnosticCode.ObjectLiteralMayOnlySpecifyKnownProperties2,
DiagnosticCode.UnableToResolveSignatureOfClassDecorator,
DiagnosticCode.UnableToResolveSignatureOfParameterDecorator,
DiagnosticCode.UnableToResolveSignatureOfPropertyDecorator,
DiagnosticCode.UnableToResolveSignatureOfMethodDecorator,
DiagnosticCode.DecoratorCanOnlyDecorateMethodImplementation,
DiagnosticCode.DecoratorFunctionReturnTypeNotAssignableToType,
DiagnosticCode.DecoratorFunctionReturnTypeExpectedToBeVoidOrAny,
DiagnosticCode.RuntimeWillInvokeDecoratorWithXArgumentsButDecoratorExpectsY,
DiagnosticCode.RuntimeWillInvokeDecoratorWithXArgumentsButDecoratorExpectsAtLeastY,
DiagnosticCode.AcceptsTooFewArgumentsToBeUsedAsDecoratorHere,
DiagnosticCode.PropertyDoesNotExistOnTypeDidYouMean,
DiagnosticCode.ErrorIsOfTypeUnknown,
]);
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';
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const diagnosticFileName = diagnostic.file!.fileName;
for (const [location, error] of expectedErrors) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const start = diagnostic.start!;
// Diagnostic is inside of `expectError` clause
if (diagnosticFileName === location.fileName && start > location.start && start < location.end) {
if (expectErrorDiagnosticCodesToIgnore.has(diagnostic.code)) {
return location;
}
// Ignore syntactical errors
if (diagnostic.code < 2000) {
expectedErrors.delete(location);
return 'preserve';
}
// Set diagnostic code on `ExpectedError` to log
error.code = diagnostic.code;
return 'preserve';
}
}
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;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
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) {
const message = diagnostic.code ?
`Found an error that tsd does not currently support (\`ts${diagnostic.code}\`), consider creating an issue on GitHub.` :
'Expected an error, but found none.';
diagnostics.push({
...diagnostic,
message,
severity: 'error'
});
}
return diagnostics;
};