@@ -3,16 +3,11 @@ import {
3
3
flattenDiagnosticMessageText ,
4
4
createProgram ,
5
5
Diagnostic as TSDiagnostic ,
6
- Program ,
7
- SourceFile ,
8
- Node ,
9
- forEachChild ,
10
- isCallExpression ,
11
- Identifier ,
12
- TypeChecker ,
13
- CallExpression
6
+ SourceFile
14
7
} from '../../libraries/typescript' ;
8
+ import { extractAssertions , parseErrorAssertionToLocation } from './parser' ;
15
9
import { Diagnostic , DiagnosticCode , Context , Location } from './interfaces' ;
10
+ import { handle } from './assertions' ;
16
11
17
12
// List of diagnostic codes that should be ignored in general
18
13
const ignoredDiagnostics = new Set < number > ( [
@@ -30,121 +25,12 @@ const diagnosticCodesToIgnore = new Set<DiagnosticCode>([
30
25
DiagnosticCode . NoOverloadMatches
31
26
] ) ;
32
27
33
- /**
34
- * Extract all assertions.
35
- *
36
- * @param program - TypeScript program.
37
- */
38
- const extractAssertions = ( program : Program ) => {
39
- const typeAssertions = new Set < CallExpression > ( ) ;
40
- const errorAssertions = new Set < CallExpression > ( ) ;
41
-
42
- function walkNodes ( node : Node ) {
43
- if ( isCallExpression ( node ) ) {
44
- const text = ( node . expression as Identifier ) . getText ( ) ;
45
-
46
- if ( text === 'expectType' ) {
47
- typeAssertions . add ( node ) ;
48
- } else if ( text === 'expectError' ) {
49
- errorAssertions . add ( node ) ;
50
- }
51
- }
52
-
53
- forEachChild ( node , walkNodes ) ;
54
- }
55
-
56
- for ( const sourceFile of program . getSourceFiles ( ) ) {
57
- walkNodes ( sourceFile ) ;
58
- }
59
-
60
- return {
61
- typeAssertions,
62
- errorAssertions
63
- } ;
64
- } ;
65
-
66
- /**
67
- * Loop over all the `expectError` nodes and convert them to a range map.
68
- *
69
- * @param nodes - Set of `expectError` nodes.
70
- */
71
- const extractExpectErrorRanges = ( nodes : Set < Node > ) => {
72
- const expectedErrors = new Map < Location , Pick < Diagnostic , 'fileName' | 'line' | 'column' > > ( ) ;
73
-
74
- // Iterate over the nodes and add the node range to the map
75
- for ( const node of nodes ) {
76
- const location = {
77
- fileName : node . getSourceFile ( ) . fileName ,
78
- start : node . getStart ( ) ,
79
- end : node . getEnd ( )
80
- } ;
81
-
82
- const pos = node
83
- . getSourceFile ( )
84
- . getLineAndCharacterOfPosition ( node . getStart ( ) ) ;
85
-
86
- expectedErrors . set ( location , {
87
- fileName : location . fileName ,
88
- line : pos . line + 1 ,
89
- column : pos . character
90
- } ) ;
91
- }
92
-
93
- return expectedErrors ;
94
- } ;
95
-
96
- /**
97
- * Assert the expected type from `expectType` calls with the provided type in the argument.
98
- * Returns a list of custom diagnostics.
99
- *
100
- * @param checker - The TypeScript type checker.
101
- * @param nodes - The `expectType` AST nodes.
102
- * @return List of custom diagnostics.
103
- */
104
- const assertTypes = ( checker : TypeChecker , nodes : Set < CallExpression > ) : Diagnostic [ ] => {
105
- const diagnostics : Diagnostic [ ] = [ ] ;
106
-
107
- for ( const node of nodes ) {
108
- if ( ! node . typeArguments ) {
109
- // Skip if the node does not have generics
110
- continue ;
111
- }
112
-
113
- // Retrieve the type to be expected. This is the type inside the generic.
114
- const expectedType = checker . getTypeFromTypeNode ( node . typeArguments [ 0 ] ) ;
115
- const argumentType = checker . getTypeAtLocation ( node . arguments [ 0 ] ) ;
116
-
117
- if ( ! checker . isAssignableTo ( argumentType , expectedType ) ) {
118
- // The argument type is not assignable to the expected type. TypeScript will catch this for us.
119
- continue ;
120
- }
121
-
122
- if ( ! checker . isAssignableTo ( expectedType , argumentType ) ) { // tslint:disable-line:early-exit
123
- /**
124
- * At this point, the expected type is not assignable to the argument type, but the argument type is
125
- * assignable to the expected type. This means our type is too wide.
126
- */
127
- const position = node . getSourceFile ( ) . getLineAndCharacterOfPosition ( node . getStart ( ) ) ;
128
-
129
- diagnostics . push ( {
130
- fileName : node . getSourceFile ( ) . fileName ,
131
- message : `Parameter type \`${ checker . typeToString ( expectedType ) } \` is declared too wide for argument type \`${ checker . typeToString ( argumentType ) } \`.` ,
132
- severity : 'error' ,
133
- line : position . line + 1 ,
134
- column : position . character ,
135
- } ) ;
136
- }
137
- }
138
-
139
- return diagnostics ;
140
- } ;
141
-
142
28
/**
143
29
* Check if the provided diagnostic should be ignored.
144
30
*
145
31
* @param diagnostic - The diagnostic to validate.
146
32
* @param expectedErrors - Map of the expected errors.
147
- * @return Boolean indicating if the diagnostic should be ignored or not.
33
+ * @returns Boolean indicating if the diagnostic should be ignored or not.
148
34
*/
149
35
const ignoreDiagnostic = ( diagnostic : TSDiagnostic , expectedErrors : Map < Location , any > ) : boolean => {
150
36
if ( ignoredDiagnostics . has ( diagnostic . code ) ) {
@@ -180,28 +66,28 @@ const ignoreDiagnostic = (diagnostic: TSDiagnostic, expectedErrors: Map<Location
180
66
export const getDiagnostics = ( context : Context ) : Diagnostic [ ] => {
181
67
const fileNames = context . testFiles . map ( fileName => path . join ( context . cwd , fileName ) ) ;
182
68
183
- const result : Diagnostic [ ] = [ ] ;
69
+ const diagnostics : Diagnostic [ ] = [ ] ;
184
70
185
71
const program = createProgram ( fileNames , context . config . compilerOptions ) ;
186
72
187
- const diagnostics = program
73
+ const tsDiagnostics = program
188
74
. getSemanticDiagnostics ( )
189
75
. concat ( program . getSyntacticDiagnostics ( ) ) ;
190
76
191
- const { typeAssertions , errorAssertions } = extractAssertions ( program ) ;
77
+ const assertions = extractAssertions ( program ) ;
192
78
193
- const expectedErrors = extractExpectErrorRanges ( errorAssertions ) ;
79
+ diagnostics . push ( ... handle ( program . getTypeChecker ( ) , assertions ) ) ;
194
80
195
- result . push ( ... assertTypes ( program . getTypeChecker ( ) , typeAssertions ) ) ;
81
+ const expectedErrors = parseErrorAssertionToLocation ( assertions ) ;
196
82
197
- for ( const diagnostic of diagnostics ) {
83
+ for ( const diagnostic of tsDiagnostics ) {
198
84
if ( ! diagnostic . file || ignoreDiagnostic ( diagnostic , expectedErrors ) ) {
199
85
continue ;
200
86
}
201
87
202
88
const position = diagnostic . file . getLineAndCharacterOfPosition ( diagnostic . start as number ) ;
203
89
204
- result . push ( {
90
+ diagnostics . push ( {
205
91
fileName : diagnostic . file . fileName ,
206
92
message : flattenDiagnosticMessageText ( diagnostic . messageText , '\n' ) ,
207
93
severity : 'error' ,
@@ -211,12 +97,12 @@ export const getDiagnostics = (context: Context): Diagnostic[] => {
211
97
}
212
98
213
99
for ( const [ , diagnostic ] of expectedErrors ) {
214
- result . push ( {
100
+ diagnostics . push ( {
215
101
...diagnostic ,
216
102
message : 'Expected an error, but found none.' ,
217
103
severity : 'error'
218
104
} ) ;
219
105
}
220
106
221
- return result ;
107
+ return diagnostics ;
222
108
} ;
0 commit comments