1
- import type * as CompilerDOM from '@vue/compiler-dom' ;
1
+ import * as CompilerDOM from '@vue/compiler-dom' ;
2
2
import type { Code , VueCodeInformation } from '../../types' ;
3
3
import { codeFeatures } from '../codeFeatures' ;
4
4
import { InlayHintInfo } from '../inlayHints' ;
@@ -8,6 +8,8 @@ import type { TemplateCodegenOptions } from './index';
8
8
9
9
export type TemplateCodegenContext = ReturnType < typeof createTemplateCodegenContext > ;
10
10
11
+ const commentDirectiveRegex = / ^ < ! - - \s * @ v u e - (?< name > [ - \w ] + ) \b (?< content > [ \s \S ] * ) - - > $ / ;
12
+
11
13
/**
12
14
* Creates and returns a Context object used for generating type-checkable TS code
13
15
* from the template section of a .vue file.
@@ -106,38 +108,29 @@ export type TemplateCodegenContext = ReturnType<typeof createTemplateCodegenCont
106
108
* and additionally how we use that to determine whether to propagate diagnostics back upward.
107
109
*/
108
110
export function createTemplateCodegenContext ( options : Pick < TemplateCodegenOptions , 'scriptSetupBindingNames' > ) {
109
- let ignoredError = false ;
110
- let expectErrorToken : {
111
- errors : number ;
112
- node : CompilerDOM . CommentNode ;
113
- } | undefined ;
114
- let lastGenericComment : {
115
- content : string ;
116
- offset : number ;
117
- } | undefined ;
118
111
let variableId = 0 ;
119
112
120
113
function resolveCodeFeatures ( features : VueCodeInformation ) {
121
- if ( features . verification ) {
122
- if ( ignoredError ) {
114
+ if ( features . verification && stack . length ) {
115
+ const data = stack [ stack . length - 1 ] ;
116
+ if ( data . ignoreError ) {
123
117
// We are currently in a region of code covered by a @vue -ignore directive, so don't
124
118
// even bother performing any type-checking: set verification to false.
125
119
return {
126
120
...features ,
127
121
verification : false ,
128
122
} ;
129
123
}
130
- if ( expectErrorToken ) {
124
+ if ( data . expectError !== undefined ) {
131
125
// We are currently in a region of code covered by a @vue -expect-error directive. We need to
132
126
// keep track of the number of errors encountered within this region so that we can know whether
133
127
// we will need to propagate an "unused ts-expect-error" diagnostic back to the original
134
128
// .vue file or not.
135
- const token = expectErrorToken ;
136
129
return {
137
130
...features ,
138
131
verification : {
139
132
shouldReport : ( ) => {
140
- token . errors ++ ;
133
+ data . expectError ! . token ++ ;
141
134
return false ;
142
135
} ,
143
136
} ,
@@ -177,7 +170,23 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
177
170
offset : number ;
178
171
} [ ] > ( ) ;
179
172
173
+ const stack : {
174
+ ignoreError ?: boolean ;
175
+ expectError ?: {
176
+ token : number ;
177
+ node : CompilerDOM . CommentNode ;
178
+ } ;
179
+ generic ?: {
180
+ content : string ;
181
+ offset : number ;
182
+ } ,
183
+ } [ ] = [ ] ;
184
+ const commentBuffer : CompilerDOM . CommentNode [ ] = [ ] ;
185
+
180
186
return {
187
+ get currentInfo ( ) {
188
+ return stack [ stack . length - 1 ] ;
189
+ } ,
181
190
codeFeatures : new Proxy ( codeFeatures , {
182
191
get ( target , key : keyof typeof codeFeatures ) {
183
192
const data = target [ key ] ;
@@ -189,7 +198,6 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
189
198
dynamicSlots,
190
199
dollarVars,
191
200
accessExternalVariables,
192
- lastGenericComment,
193
201
blockConditions,
194
202
scopedClasses,
195
203
emptyClassOffsets,
@@ -203,7 +211,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
203
211
} | undefined ,
204
212
singleRootElTypes : [ ] as string [ ] ,
205
213
singleRootNodes : new Set < CompilerDOM . ElementNode | null > ( ) ,
206
- addTemplateRef : ( name : string , typeExp : string , offset : number ) => {
214
+ addTemplateRef ( name : string , typeExp : string , offset : number ) {
207
215
let refs = templateRefs . get ( name ) ;
208
216
if ( ! refs ) {
209
217
templateRefs . set ( name , refs = [ ] ) ;
@@ -219,26 +227,26 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
219
227
arr . add ( offset ) ;
220
228
}
221
229
} ,
222
- hasLocalVariable : ( name : string ) => {
230
+ hasLocalVariable ( name : string ) {
223
231
return ! ! localVars . get ( name ) ;
224
232
} ,
225
- addLocalVariable : ( name : string ) => {
233
+ addLocalVariable ( name : string ) {
226
234
localVars . set ( name , ( localVars . get ( name ) ?? 0 ) + 1 ) ;
227
235
} ,
228
- removeLocalVariable : ( name : string ) => {
236
+ removeLocalVariable ( name : string ) {
229
237
localVars . set ( name , localVars . get ( name ) ! - 1 ) ;
230
238
} ,
231
- getInternalVariable : ( ) => {
239
+ getInternalVariable ( ) {
232
240
return `__VLS_${ variableId ++ } ` ;
233
241
} ,
234
- getHoistVariable : ( originalVar : string ) => {
242
+ getHoistVariable ( originalVar : string ) {
235
243
let name = hoistVars . get ( originalVar ) ;
236
244
if ( name === undefined ) {
237
245
hoistVars . set ( originalVar , name = `__VLS_${ variableId ++ } ` ) ;
238
246
}
239
247
return name ;
240
248
} ,
241
- generateHoistVariables : function * ( ) {
249
+ * generateHoistVariables ( ) {
242
250
// trick to avoid TS 4081 (#5186)
243
251
if ( hoistVars . size ) {
244
252
yield `// @ts-ignore${ newLine } ` ;
@@ -249,52 +257,12 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
249
257
yield endOfLine ;
250
258
}
251
259
} ,
252
- generateConditionGuards : function * ( ) {
260
+ * generateConditionGuards ( ) {
253
261
for ( const condition of blockConditions ) {
254
262
yield `if (!${ condition } ) return${ endOfLine } ` ;
255
263
}
256
264
} ,
257
- ignoreError : function * ( ) : Generator < Code > {
258
- if ( ! ignoredError ) {
259
- ignoredError = true ;
260
- yield `// @vue-ignore start${ newLine } ` ;
261
- }
262
- } ,
263
- expectError : function * ( prevNode : CompilerDOM . CommentNode ) : Generator < Code > {
264
- if ( ! expectErrorToken ) {
265
- expectErrorToken = {
266
- errors : 0 ,
267
- node : prevNode ,
268
- } ;
269
- yield `// @vue-expect-error start${ newLine } ` ;
270
- }
271
- } ,
272
- resetDirectiveComments : function * ( endStr : string ) : Generator < Code > {
273
- if ( expectErrorToken ) {
274
- const token = expectErrorToken ;
275
- yield * wrapWith (
276
- expectErrorToken . node . loc . start . offset ,
277
- expectErrorToken . node . loc . end . offset ,
278
- {
279
- verification : {
280
- // If no errors/warnings/diagnostics were reported within the region of code covered
281
- // by the @vue -expect-error directive, then we should allow any `unused @ts-expect-error`
282
- // diagnostics to be reported upward.
283
- shouldReport : ( ) => token . errors === 0 ,
284
- } ,
285
- } ,
286
- `// @ts-expect-error __VLS_TS_EXPECT_ERROR`
287
- ) ;
288
- yield `${ newLine } ${ endOfLine } ` ;
289
- expectErrorToken = undefined ;
290
- yield `// @vue-expect-error ${ endStr } ${ newLine } ` ;
291
- }
292
- if ( ignoredError ) {
293
- ignoredError = false ;
294
- yield `// @vue-ignore ${ endStr } ${ newLine } ` ;
295
- }
296
- } ,
297
- generateAutoImportCompletion : function * ( ) : Generator < Code > {
265
+ * generateAutoImportCompletion ( ) : Generator < Code > {
298
266
const all = [ ...accessExternalVariables . entries ( ) ] ;
299
267
if ( ! all . some ( ( [ _ , offsets ] ) => offsets . size ) ) {
300
268
return ;
@@ -328,6 +296,71 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
328
296
offsets . clear ( ) ;
329
297
}
330
298
yield `]${ endOfLine } ` ;
331
- }
299
+ } ,
300
+ enter ( node : CompilerDOM . RootNode | CompilerDOM . TemplateChildNode | CompilerDOM . SimpleExpressionNode ) {
301
+ if ( node . type === CompilerDOM . NodeTypes . COMMENT ) {
302
+ commentBuffer . push ( node ) ;
303
+ return false ;
304
+ }
305
+
306
+ const data : typeof stack [ number ] = { } ;
307
+ const comments = [ ...commentBuffer ] ;
308
+ commentBuffer . length = 0 ;
309
+
310
+ for ( const comment of comments ) {
311
+ const match = comment . loc . source . match ( commentDirectiveRegex ) ;
312
+ if ( match ) {
313
+ const { name, content } = match . groups ! ;
314
+ switch ( name ) {
315
+ case 'skip' : {
316
+ return false ;
317
+ }
318
+ case 'ignore' : {
319
+ data . ignoreError = true ;
320
+ break ;
321
+ }
322
+ case 'expect-error' : {
323
+ data . expectError = {
324
+ token : 0 ,
325
+ node : comment ,
326
+ } ;
327
+ break ;
328
+ }
329
+ case 'generic' : {
330
+ const text = content . trim ( ) ;
331
+ if ( text . startsWith ( '{' ) && text . endsWith ( '}' ) ) {
332
+ data . generic = {
333
+ content : text . slice ( 1 , - 1 ) ,
334
+ offset : comment . loc . start . offset + comment . loc . source . indexOf ( '{' ) + 1 ,
335
+ } ;
336
+ }
337
+ break ;
338
+ }
339
+ }
340
+ }
341
+ }
342
+ stack . push ( data ) ;
343
+ return true ;
344
+ } ,
345
+ * exit ( ) : Generator < Code > {
346
+ const data = stack . pop ( ) ! ;
347
+ commentBuffer . length = 0 ;
348
+ if ( data . expectError !== undefined ) {
349
+ yield * wrapWith (
350
+ data . expectError . node . loc . start . offset ,
351
+ data . expectError . node . loc . end . offset ,
352
+ {
353
+ verification : {
354
+ // If no errors/warnings/diagnostics were reported within the region of code covered
355
+ // by the @vue -expect-error directive, then we should allow any `unused @ts-expect-error`
356
+ // diagnostics to be reported upward.
357
+ shouldReport : ( ) => data . expectError ! . token === 0 ,
358
+ } ,
359
+ } ,
360
+ `// @ts-expect-error`
361
+ ) ;
362
+ yield `${ newLine } ${ endOfLine } ` ;
363
+ }
364
+ } ,
332
365
} ;
333
366
}
0 commit comments