From 87729ae25099442e60b044b94bfbc3d1481d5013 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Mar 2025 03:58:55 +0800 Subject: [PATCH 1/6] refactor: track comment directives infomation with stack mode --- .../lib/codegen/template/context.ts | 164 +++++++++++------- .../lib/codegen/template/elementChildren.ts | 5 +- .../lib/codegen/template/index.ts | 2 +- .../lib/codegen/template/templateChild.ts | 51 ++---- .../lib/codegen/template/vFor.ts | 9 +- .../language-core/lib/codegen/template/vIf.ts | 14 +- .../lib/codegen/template/vSlot.ts | 6 +- 7 files changed, 114 insertions(+), 137 deletions(-) diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index d0cc8f430f..3341e77629 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -1,4 +1,4 @@ -import type * as CompilerDOM from '@vue/compiler-dom'; +import * as CompilerDOM from '@vue/compiler-dom'; import type { Code, VueCodeInformation } from '../../types'; import { codeFeatures } from '../codeFeatures'; import { InlayHintInfo } from '../inlayHints'; @@ -8,6 +8,8 @@ import type { TemplateCodegenOptions } from './index'; export type TemplateCodegenContext = ReturnType; +const commentDirectiveRegex = /^$/; + /** * Creates and returns a Context object used for generating type-checkable TS code * from the template section of a .vue file. @@ -106,20 +108,12 @@ export type TemplateCodegenContext = ReturnType) { - let ignoredError = false; - let expectErrorToken: { - errors: number; - node: CompilerDOM.CommentNode; - } | undefined; - let lastGenericComment: { - content: string; - offset: number; - } | undefined; let variableId = 0; function resolveCodeFeatures(features: VueCodeInformation) { - if (features.verification) { - if (ignoredError) { + if (features.verification && stack.length) { + const data = stack[stack.length - 1]; + if (data.ignoreError) { // We are currently in a region of code covered by a @vue-ignore directive, so don't // even bother performing any type-checking: set verification to false. return { @@ -127,17 +121,16 @@ export function createTemplateCodegenContext(options: Pick { - token.errors++; + data.expectError!.token++; return false; }, }, @@ -177,7 +170,23 @@ export function createTemplateCodegenContext(options: Pick(); + const stack: { + ignoreError?: boolean; + expectError?: { + token: number; + node: CompilerDOM.CommentNode; + }; + generic?: { + content: string; + offset: number; + }, + }[] = []; + const commentBuffer: CompilerDOM.CommentNode[] = []; + return { + get currentInfo() { + return stack[stack.length - 1]; + }, codeFeatures: new Proxy(codeFeatures, { get(target, key: keyof typeof codeFeatures) { const data = target[key]; @@ -189,7 +198,6 @@ export function createTemplateCodegenContext(options: Pick(), - addTemplateRef: (name: string, typeExp: string, offset: number) => { + addTemplateRef(name: string, typeExp: string, offset: number) { let refs = templateRefs.get(name); if (!refs) { templateRefs.set(name, refs = []); @@ -219,26 +227,26 @@ export function createTemplateCodegenContext(options: Pick { + hasLocalVariable(name: string) { return !!localVars.get(name); }, - addLocalVariable: (name: string) => { + addLocalVariable(name: string) { localVars.set(name, (localVars.get(name) ?? 0) + 1); }, - removeLocalVariable: (name: string) => { + removeLocalVariable(name: string) { localVars.set(name, localVars.get(name)! - 1); }, - getInternalVariable: () => { + getInternalVariable() { return `__VLS_${variableId++}`; }, - getHoistVariable: (originalVar: string) => { + getHoistVariable(originalVar: string) { let name = hoistVars.get(originalVar); if (name === undefined) { hoistVars.set(originalVar, name = `__VLS_${variableId++}`); } return name; }, - generateHoistVariables: function* () { + * generateHoistVariables() { // trick to avoid TS 4081 (#5186) if (hoistVars.size) { yield `// @ts-ignore${newLine}`; @@ -249,52 +257,12 @@ export function createTemplateCodegenContext(options: Pick { - if (!ignoredError) { - ignoredError = true; - yield `// @vue-ignore start${newLine}`; - } - }, - expectError: function* (prevNode: CompilerDOM.CommentNode): Generator { - if (!expectErrorToken) { - expectErrorToken = { - errors: 0, - node: prevNode, - }; - yield `// @vue-expect-error start${newLine}`; - } - }, - resetDirectiveComments: function* (endStr: string): Generator { - if (expectErrorToken) { - const token = expectErrorToken; - yield* wrapWith( - expectErrorToken.node.loc.start.offset, - expectErrorToken.node.loc.end.offset, - { - verification: { - // If no errors/warnings/diagnostics were reported within the region of code covered - // by the @vue-expect-error directive, then we should allow any `unused @ts-expect-error` - // diagnostics to be reported upward. - shouldReport: () => token.errors === 0, - }, - }, - `// @ts-expect-error __VLS_TS_EXPECT_ERROR` - ); - yield `${newLine}${endOfLine}`; - expectErrorToken = undefined; - yield `// @vue-expect-error ${endStr}${newLine}`; - } - if (ignoredError) { - ignoredError = false; - yield `// @vue-ignore ${endStr}${newLine}`; - } - }, - generateAutoImportCompletion: function* (): Generator { + * generateAutoImportCompletion(): Generator { const all = [...accessExternalVariables.entries()]; if (!all.some(([_, offsets]) => offsets.size)) { return; @@ -328,6 +296,70 @@ export function createTemplateCodegenContext(options: Pick { + const data = stack.pop()!; + commentBuffer.length = 0; + if (data.expectError !== undefined) { + yield* wrapWith( + data.expectError.node.loc.start.offset, + data.expectError.node.loc.end.offset, + { + verification: { + // If no errors/warnings/diagnostics were reported within the region of code covered + // by the @vue-expect-error directive, then we should allow any `unused @ts-expect-error` + // diagnostics to be reported upward. + shouldReport: () => data.expectError!.token === 0, + }, + }, + `// @ts-expect-error` + ); + yield `${newLine}${endOfLine}`; + } + }, }; } diff --git a/packages/language-core/lib/codegen/template/elementChildren.ts b/packages/language-core/lib/codegen/template/elementChildren.ts index 65de04b9ce..ea7a8a16b8 100644 --- a/packages/language-core/lib/codegen/template/elementChildren.ts +++ b/packages/language-core/lib/codegen/template/elementChildren.ts @@ -9,11 +9,8 @@ export function* generateElementChildren( ctx: TemplateCodegenContext, node: CompilerDOM.ElementNode ): Generator { - yield* ctx.resetDirectiveComments('end of element children start'); - let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, prev); - prev = childNode; + yield* generateTemplateChild(options, ctx, childNode); } yield* ctx.generateAutoImportCompletion(); } diff --git a/packages/language-core/lib/codegen/template/index.ts b/packages/language-core/lib/codegen/template/index.ts index 303c541ce7..747f9f8119 100644 --- a/packages/language-core/lib/codegen/template/index.ts +++ b/packages/language-core/lib/codegen/template/index.ts @@ -50,7 +50,7 @@ export function* generateTemplate(options: TemplateCodegenOptions): Generator[-\w]+)\b(?[\s\S]*)-->$/; - // @ts-ignore const transformContext: CompilerDOM.TransformContext = { onError: () => { }, @@ -33,38 +31,11 @@ export function* generateTemplateChild( options: TemplateCodegenOptions, ctx: TemplateCodegenContext, node: CompilerDOM.RootNode | CompilerDOM.TemplateChildNode | CompilerDOM.SimpleExpressionNode, - prevNode: CompilerDOM.TemplateChildNode | undefined, + enterNode: boolean = true, isVForChild: boolean = false ): Generator { - if (prevNode?.type === CompilerDOM.NodeTypes.COMMENT) { - const match = prevNode.loc.source.match(commentDirectiveRegex); - if (match) { - const { name, content } = match.groups!; - switch (name) { - case 'skip': { - yield `// @vue-skip${newLine}`; - return; - } - case 'ignore': { - yield* ctx.ignoreError(); - break; - } - case 'expect-error': { - yield* ctx.expectError(prevNode); - break; - } - case 'generic': { - const text = content.trim(); - if (text.startsWith('{') && text.endsWith('}')) { - ctx.lastGenericComment = { - content: text.slice(1, -1), - offset: prevNode.loc.start.offset + prevNode.loc.source.indexOf('{') + 1, - }; - } - break; - } - } - } + if (enterNode && !ctx.enter(node)) { + return; } const cur = node as CompilerDOM.ElementNode | CompilerDOM.IfNode | CompilerDOM.ForNode; @@ -76,12 +47,9 @@ export function* generateTemplateChild( for (const item of collectSingleRootNodes(options, node.children)) { ctx.singleRootNodes.add(item); } - let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, prev); - prev = childNode; + yield* generateTemplateChild(options, ctx, childNode); } - yield* ctx.resetDirectiveComments('end of root'); } else if (node.type === CompilerDOM.NodeTypes.ELEMENT) { const vForNode = getVForNode(node); @@ -119,13 +87,13 @@ export function* generateTemplateChild( } else if (node.type === CompilerDOM.NodeTypes.TEXT_CALL) { // {{ var }} - yield* generateTemplateChild(options, ctx, node.content, undefined); + yield* generateTemplateChild(options, ctx, node.content, false); } else if (node.type === CompilerDOM.NodeTypes.COMPOUND_EXPRESSION) { // {{ ... }} {{ ... }} for (const childNode of node.children) { if (typeof childNode === 'object') { - yield* generateTemplateChild(options, ctx, childNode, undefined); + yield* generateTemplateChild(options, ctx, childNode, false); } } } @@ -143,7 +111,6 @@ export function* generateTemplateChild( `(`, `)${endOfLine}` ); - yield* ctx.resetDirectiveComments('end of INTERPOLATION'); } else if (node.type === CompilerDOM.NodeTypes.IF) { // v-if / v-else-if / v-else @@ -156,6 +123,10 @@ export function* generateTemplateChild( else if (node.type === CompilerDOM.NodeTypes.TEXT) { // not needed progress } + + if (enterNode) { + yield* ctx.exit(); + } } function* collectSingleRootNodes( diff --git a/packages/language-core/lib/codegen/template/vFor.ts b/packages/language-core/lib/codegen/template/vFor.ts index 41ce8dced3..f8a76b6b03 100644 --- a/packages/language-core/lib/codegen/template/vFor.ts +++ b/packages/language-core/lib/codegen/template/vFor.ts @@ -49,7 +49,6 @@ export function* generateVFor( for (const varName of forBlockVars) { ctx.addLocalVariable(varName); } - let isFragment = true; for (const argument of node.codegenNode?.children.arguments ?? []) { if ( argument.type === CompilerDOM.NodeTypes.JS_FUNCTION_EXPRESSION @@ -57,7 +56,6 @@ export function* generateVFor( && argument.returns?.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION ) { if (argument.returns.tag !== CompilerDOM.FRAGMENT) { - isFragment = false; continue; } for (const prop of argument.returns.props.properties) { @@ -81,13 +79,8 @@ export function* generateVFor( } } } - if (isFragment) { - yield* ctx.resetDirectiveComments('end of v-for start'); - } - let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, prev, true); - prev = childNode; + yield* generateTemplateChild(options, ctx, childNode, false, true); } for (const varName of forBlockVars) { ctx.removeLocalVariable(varName); diff --git a/packages/language-core/lib/codegen/template/vIf.ts b/packages/language-core/lib/codegen/template/vIf.ts index 21c03fe767..bd3b554410 100644 --- a/packages/language-core/lib/codegen/template/vIf.ts +++ b/packages/language-core/lib/codegen/template/vIf.ts @@ -50,13 +50,8 @@ export function* generateVIf( } yield `{${newLine}`; - if (isFragment(node)) { - yield* ctx.resetDirectiveComments('end of v-if start'); - } - let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of branch.children) { - yield* generateTemplateChild(options, ctx, childNode, prev); - prev = childNode; + yield* generateTemplateChild(options, ctx, childNode, false); } yield* ctx.generateAutoImportCompletion(); yield `}${newLine}`; @@ -68,10 +63,3 @@ export function* generateVIf( ctx.blockConditions.length = originalBlockConditionsLength; } - -function isFragment(node: CompilerDOM.IfNode) { - return node.codegenNode - && 'consequent' in node.codegenNode - && 'tag' in node.codegenNode.consequent - && node.codegenNode.consequent.tag === CompilerDOM.FRAGMENT; -} diff --git a/packages/language-core/lib/codegen/template/vSlot.ts b/packages/language-core/lib/codegen/template/vSlot.ts index 0c87e879ff..078edc31d0 100644 --- a/packages/language-core/lib/codegen/template/vSlot.ts +++ b/packages/language-core/lib/codegen/template/vSlot.ts @@ -54,12 +54,8 @@ export function* generateVSlot( ctx.addLocalVariable(varName); } - yield* ctx.resetDirectiveComments('end of slot children start'); - - let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, prev); - prev = childNode; + yield* generateTemplateChild(options, ctx, childNode); } for (const varName of slotBlockVars) { From 3c6f6f263c3c390f8d1e68a9d1142af16eda0f0e Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Mar 2025 04:01:27 +0800 Subject: [PATCH 2/6] fix: update --- packages/language-core/lib/codegen/template/element.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/language-core/lib/codegen/template/element.ts b/packages/language-core/lib/codegen/template/element.ts index 6f87a814f4..e1ee400307 100644 --- a/packages/language-core/lib/codegen/template/element.ts +++ b/packages/language-core/lib/codegen/template/element.ts @@ -425,8 +425,8 @@ function* generateCanonicalComponentName(tagText: string, offset: number, featur function* generateComponentGeneric( ctx: TemplateCodegenContext ): Generator { - if (ctx.lastGenericComment) { - const { content, offset } = ctx.lastGenericComment; + if (ctx.currentInfo.generic) { + const { content, offset } = ctx.currentInfo.generic; yield* wrapWith( offset, offset + content.length, @@ -441,7 +441,6 @@ function* generateComponentGeneric( `>` ); } - ctx.lastGenericComment = undefined; } function* generateElementReference( From 620be112afdfc2f86abc9167ac033a073bea3754 Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Mar 2025 04:35:27 +0800 Subject: [PATCH 3/6] fix: restore `isFragment` --- packages/language-core/lib/codegen/template/vFor.ts | 4 +++- packages/language-core/lib/codegen/template/vIf.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/language-core/lib/codegen/template/vFor.ts b/packages/language-core/lib/codegen/template/vFor.ts index f8a76b6b03..23002ab020 100644 --- a/packages/language-core/lib/codegen/template/vFor.ts +++ b/packages/language-core/lib/codegen/template/vFor.ts @@ -49,6 +49,7 @@ export function* generateVFor( for (const varName of forBlockVars) { ctx.addLocalVariable(varName); } + let isFragment = true; for (const argument of node.codegenNode?.children.arguments ?? []) { if ( argument.type === CompilerDOM.NodeTypes.JS_FUNCTION_EXPRESSION @@ -56,6 +57,7 @@ export function* generateVFor( && argument.returns?.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION ) { if (argument.returns.tag !== CompilerDOM.FRAGMENT) { + isFragment = false; continue; } for (const prop of argument.returns.props.properties) { @@ -80,7 +82,7 @@ export function* generateVFor( } } for (const childNode of node.children) { - yield* generateTemplateChild(options, ctx, childNode, false, true); + yield* generateTemplateChild(options, ctx, childNode, isFragment, true); } for (const varName of forBlockVars) { ctx.removeLocalVariable(varName); diff --git a/packages/language-core/lib/codegen/template/vIf.ts b/packages/language-core/lib/codegen/template/vIf.ts index bd3b554410..bff60bad8e 100644 --- a/packages/language-core/lib/codegen/template/vIf.ts +++ b/packages/language-core/lib/codegen/template/vIf.ts @@ -51,7 +51,7 @@ export function* generateVIf( yield `{${newLine}`; for (const childNode of branch.children) { - yield* generateTemplateChild(options, ctx, childNode, false); + yield* generateTemplateChild(options, ctx, childNode, isFragment(node)); } yield* ctx.generateAutoImportCompletion(); yield `}${newLine}`; @@ -63,3 +63,10 @@ export function* generateVIf( ctx.blockConditions.length = originalBlockConditionsLength; } + +function isFragment(node: CompilerDOM.IfNode) { + return node.codegenNode + && 'consequent' in node.codegenNode + && 'tag' in node.codegenNode.consequent + && node.codegenNode.consequent.tag === CompilerDOM.FRAGMENT; +} From 7eb9d7942cc5253a02e10f4b97ce76501309288f Mon Sep 17 00:00:00 2001 From: KazariEX <1364035137@qq.com> Date: Sun, 16 Mar 2025 04:37:35 +0800 Subject: [PATCH 4/6] fix: clear comment buffer on skip --- packages/language-core/lib/codegen/template/context.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 3341e77629..c16d5709e7 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -310,6 +310,7 @@ export function createTemplateCodegenContext(options: Pick Date: Sun, 16 Mar 2025 04:38:51 +0800 Subject: [PATCH 5/6] refactor: simplify --- packages/language-core/lib/codegen/template/context.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index c16d5709e7..1d476cd4fe 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -304,13 +304,15 @@ export function createTemplateCodegenContext(options: Pick Date: Sun, 16 Mar 2025 04:46:06 +0800 Subject: [PATCH 6/6] refactor: remove else block --- .../lib/codegen/template/context.ts | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index 1d476cd4fe..0ef5884796 100644 --- a/packages/language-core/lib/codegen/template/context.ts +++ b/packages/language-core/lib/codegen/template/context.ts @@ -302,46 +302,45 @@ export function createTemplateCodegenContext(options: Pick { const data = stack.pop()!;