1- import type * as CompilerDOM from '@vue/compiler-dom' ;
1+ import * as CompilerDOM from '@vue/compiler-dom' ;
22import type { Code , VueCodeInformation } from '../../types' ;
33import { codeFeatures } from '../codeFeatures' ;
44import { InlayHintInfo } from '../inlayHints' ;
@@ -8,6 +8,8 @@ import type { TemplateCodegenOptions } from './index';
88
99export type TemplateCodegenContext = ReturnType < typeof createTemplateCodegenContext > ;
1010
11+ const commentDirectiveRegex = / ^ < ! - - \s * @ v u e - (?< name > [ - \w ] + ) \b (?< content > [ \s \S ] * ) - - > $ / ;
12+
1113/**
1214 * Creates and returns a Context object used for generating type-checkable TS code
1315 * from the template section of a .vue file.
@@ -106,38 +108,29 @@ export type TemplateCodegenContext = ReturnType<typeof createTemplateCodegenCont
106108 * and additionally how we use that to determine whether to propagate diagnostics back upward.
107109 */
108110export 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 ;
118111 let variableId = 0 ;
119112
120113 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 ) {
123117 // We are currently in a region of code covered by a @vue -ignore directive, so don't
124118 // even bother performing any type-checking: set verification to false.
125119 return {
126120 ...features ,
127121 verification : false ,
128122 } ;
129123 }
130- if ( expectErrorToken ) {
124+ if ( data . expectError !== undefined ) {
131125 // We are currently in a region of code covered by a @vue -expect-error directive. We need to
132126 // keep track of the number of errors encountered within this region so that we can know whether
133127 // we will need to propagate an "unused ts-expect-error" diagnostic back to the original
134128 // .vue file or not.
135- const token = expectErrorToken ;
136129 return {
137130 ...features ,
138131 verification : {
139132 shouldReport : ( ) => {
140- token . errors ++ ;
133+ data . expectError ! . token ++ ;
141134 return false ;
142135 } ,
143136 } ,
@@ -177,7 +170,23 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
177170 offset : number ;
178171 } [ ] > ( ) ;
179172
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+
180186 return {
187+ get currentInfo ( ) {
188+ return stack [ stack . length - 1 ] ;
189+ } ,
181190 codeFeatures : new Proxy ( codeFeatures , {
182191 get ( target , key : keyof typeof codeFeatures ) {
183192 const data = target [ key ] ;
@@ -189,7 +198,6 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
189198 dynamicSlots,
190199 dollarVars,
191200 accessExternalVariables,
192- lastGenericComment,
193201 blockConditions,
194202 scopedClasses,
195203 emptyClassOffsets,
@@ -203,7 +211,7 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
203211 } | undefined ,
204212 singleRootElTypes : [ ] as string [ ] ,
205213 singleRootNodes : new Set < CompilerDOM . ElementNode | null > ( ) ,
206- addTemplateRef : ( name : string , typeExp : string , offset : number ) => {
214+ addTemplateRef ( name : string , typeExp : string , offset : number ) {
207215 let refs = templateRefs . get ( name ) ;
208216 if ( ! refs ) {
209217 templateRefs . set ( name , refs = [ ] ) ;
@@ -219,26 +227,26 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
219227 arr . add ( offset ) ;
220228 }
221229 } ,
222- hasLocalVariable : ( name : string ) => {
230+ hasLocalVariable ( name : string ) {
223231 return ! ! localVars . get ( name ) ;
224232 } ,
225- addLocalVariable : ( name : string ) => {
233+ addLocalVariable ( name : string ) {
226234 localVars . set ( name , ( localVars . get ( name ) ?? 0 ) + 1 ) ;
227235 } ,
228- removeLocalVariable : ( name : string ) => {
236+ removeLocalVariable ( name : string ) {
229237 localVars . set ( name , localVars . get ( name ) ! - 1 ) ;
230238 } ,
231- getInternalVariable : ( ) => {
239+ getInternalVariable ( ) {
232240 return `__VLS_${ variableId ++ } ` ;
233241 } ,
234- getHoistVariable : ( originalVar : string ) => {
242+ getHoistVariable ( originalVar : string ) {
235243 let name = hoistVars . get ( originalVar ) ;
236244 if ( name === undefined ) {
237245 hoistVars . set ( originalVar , name = `__VLS_${ variableId ++ } ` ) ;
238246 }
239247 return name ;
240248 } ,
241- generateHoistVariables : function * ( ) {
249+ * generateHoistVariables ( ) {
242250 // trick to avoid TS 4081 (#5186)
243251 if ( hoistVars . size ) {
244252 yield `// @ts-ignore${ newLine } ` ;
@@ -249,52 +257,12 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
249257 yield endOfLine ;
250258 }
251259 } ,
252- generateConditionGuards : function * ( ) {
260+ * generateConditionGuards ( ) {
253261 for ( const condition of blockConditions ) {
254262 yield `if (!${ condition } ) return${ endOfLine } ` ;
255263 }
256264 } ,
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 > {
298266 const all = [ ...accessExternalVariables . entries ( ) ] ;
299267 if ( ! all . some ( ( [ _ , offsets ] ) => offsets . size ) ) {
300268 return ;
@@ -328,6 +296,71 @@ export function createTemplateCodegenContext(options: Pick<TemplateCodegenOption
328296 offsets . clear ( ) ;
329297 }
330298 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+ } ,
332365 } ;
333366}
0 commit comments