Skip to content

Commit 79247b7

Browse files
refactor(language-core): track directive comment infomation with stack mode (#5276)
Co-authored-by: Johnson Chu <[email protected]>
1 parent cfffc6e commit 79247b7

File tree

8 files changed

+117
-131
lines changed

8 files changed

+117
-131
lines changed

packages/language-core/lib/codegen/template/context.ts

+99-66
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type * as CompilerDOM from '@vue/compiler-dom';
1+
import * as CompilerDOM from '@vue/compiler-dom';
22
import type { Code, VueCodeInformation } from '../../types';
33
import { codeFeatures } from '../codeFeatures';
44
import { InlayHintInfo } from '../inlayHints';
@@ -8,6 +8,8 @@ import type { TemplateCodegenOptions } from './index';
88

99
export type TemplateCodegenContext = ReturnType<typeof createTemplateCodegenContext>;
1010

11+
const commentDirectiveRegex = /^<!--\s*@vue-(?<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
*/
108110
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;
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
}

packages/language-core/lib/codegen/template/element.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,8 @@ function* generateCanonicalComponentName(tagText: string, offset: number, featur
425425
function* generateComponentGeneric(
426426
ctx: TemplateCodegenContext
427427
): Generator<Code> {
428-
if (ctx.lastGenericComment) {
429-
const { content, offset } = ctx.lastGenericComment;
428+
if (ctx.currentInfo.generic) {
429+
const { content, offset } = ctx.currentInfo.generic;
430430
yield* wrapWith(
431431
offset,
432432
offset + content.length,
@@ -441,7 +441,6 @@ function* generateComponentGeneric(
441441
`>`
442442
);
443443
}
444-
ctx.lastGenericComment = undefined;
445444
}
446445

447446
function* generateElementReference(

packages/language-core/lib/codegen/template/elementChildren.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ export function* generateElementChildren(
99
ctx: TemplateCodegenContext,
1010
node: CompilerDOM.ElementNode
1111
): Generator<Code> {
12-
yield* ctx.resetDirectiveComments('end of element children start');
13-
let prev: CompilerDOM.TemplateChildNode | undefined;
1412
for (const childNode of node.children) {
15-
yield* generateTemplateChild(options, ctx, childNode, prev);
16-
prev = childNode;
13+
yield* generateTemplateChild(options, ctx, childNode);
1714
}
1815
yield* ctx.generateAutoImportCompletion();
1916
}

packages/language-core/lib/codegen/template/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator<Co
5050
}
5151

5252
if (options.template.ast) {
53-
yield* generateTemplateChild(options, ctx, options.template.ast, undefined);
53+
yield* generateTemplateChild(options, ctx, options.template.ast);
5454
}
5555

5656
yield* generateStyleScopedClassReferences(ctx);

0 commit comments

Comments
 (0)