diff --git a/packages/language-core/lib/codegen/template/context.ts b/packages/language-core/lib/codegen/template/context.ts index d0cc8f430f..0ef5884796 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,71 @@ 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/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( 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..23002ab020 100644 --- a/packages/language-core/lib/codegen/template/vFor.ts +++ b/packages/language-core/lib/codegen/template/vFor.ts @@ -81,13 +81,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, 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 21c03fe767..bff60bad8e 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, isFragment(node)); } yield* ctx.generateAutoImportCompletion(); yield `}${newLine}`; 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) {