diff --git a/packages/language-core/lib/codegen/template/vFor.ts b/packages/language-core/lib/codegen/template/vFor.ts index deb52c29a6..fe727b89d3 100644 --- a/packages/language-core/lib/codegen/template/vFor.ts +++ b/packages/language-core/lib/codegen/template/vFor.ts @@ -1,10 +1,11 @@ import * as CompilerDOM from '@vue/compiler-dom'; import type { Code } from '../../types'; -import { collectVars, createTsAst, endOfLine, newLine } from '../common'; +import { collectVars, createTsAst, newLine } from '../common'; import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generateTemplateChild } from './templateChild'; +import { generateElement } from './element'; export function* generateVFor( options: TemplateCodegenOptions, @@ -51,32 +52,96 @@ export function* generateVFor( ctx.addLocalVariable(varName); } let isFragment = true; - for (const argument of node.codegenNode?.children.arguments ?? []) { - if ( - argument.type === CompilerDOM.NodeTypes.JS_FUNCTION_EXPRESSION - && argument.returns?.type === CompilerDOM.NodeTypes.VNODE_CALL - && 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) { - if ( - prop.value.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION - && !prop.value.isStatic - ) { - yield* generateInterpolation( - options, - ctx, - prop.value.content, - prop.value.loc, - prop.value.loc.start.offset, - ctx.codeFeatures.all, - '(', - ')' - ); - yield endOfLine; + if (node.codegenNode) { + for (const argument of node.codegenNode.children.arguments) { + if ( + argument.type === CompilerDOM.NodeTypes.JS_FUNCTION_EXPRESSION + && argument.returns?.type === CompilerDOM.NodeTypes.VNODE_CALL + && argument.returns?.props?.type === CompilerDOM.NodeTypes.JS_OBJECT_EXPRESSION + ) { + if (argument.returns.tag !== CompilerDOM.FRAGMENT) { + isFragment = false; + continue; + } + // #4539, #329 + for (const prop of argument.returns.props.properties) { + if ( + prop.key.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + && prop.value.type === CompilerDOM.NodeTypes.SIMPLE_EXPRESSION + ) { + const codeBeforeKeyOffset = prop.value.loc.start.offset - node.codegenNode.loc.start.offset; + const codeBeforeKey = node.codegenNode.loc.source.slice(0, codeBeforeKeyOffset); + const isShorthand = codeBeforeKey[codeBeforeKey.length - 1] === ':'; + const lastKeyStartOffset = isShorthand ? codeBeforeKeyOffset : codeBeforeKey.lastIndexOf('key'); + let fakeProp: CompilerDOM.AttributeNode | CompilerDOM.DirectiveNode; + if (prop.value.isStatic) { + fakeProp = { + type: CompilerDOM.NodeTypes.ATTRIBUTE, + name: prop.key.content, + nameLoc: {} as any, + value: { + type: CompilerDOM.NodeTypes.TEXT, + content: prop.value.content, + loc: prop.value.loc, + }, + loc: { + ...prop.loc, + start: { + ...prop.loc.start, + offset: node.codegenNode.loc.start.offset + lastKeyStartOffset, + }, + end: { + ...prop.loc.end, + offset: node.codegenNode.loc.start.offset + lastKeyStartOffset + 3, + } + }, + }; + } + else { + fakeProp = { + type: CompilerDOM.NodeTypes.DIRECTIVE, + name: 'bind', + rawName: '', + arg: { + ...prop.key, + loc: { + ...prop.key.loc, + start: { + ...prop.key.loc.start, + offset: node.codegenNode.loc.start.offset + lastKeyStartOffset, + }, + end: { + ...prop.key.loc.end, + offset: node.codegenNode.loc.start.offset + lastKeyStartOffset + 3, + } + } + }, + exp: prop.value, + loc: { + ...prop.key.loc, + start: { + ...prop.key.loc.start, + offset: node.codegenNode.loc.start.offset + lastKeyStartOffset, + }, + end: { + ...prop.key.loc.end, + offset: node.codegenNode.loc.start.offset + lastKeyStartOffset + 3, + } + }, + modifiers: [], + }; + } + yield* generateElement(options, ctx, { + type: CompilerDOM.NodeTypes.ELEMENT, + tag: 'template', + tagType: CompilerDOM.ElementTypes.TEMPLATE, + ns: 0, + children: node.children, + loc: node.codegenNode.loc, + props: [fakeProp], + codegenNode: undefined, + }, currentComponent, componentCtxVar); + } } } } diff --git a/packages/language-core/lib/codegen/template/vIf.ts b/packages/language-core/lib/codegen/template/vIf.ts index e12e14b215..4f1a29b7c5 100644 --- a/packages/language-core/lib/codegen/template/vIf.ts +++ b/packages/language-core/lib/codegen/template/vIf.ts @@ -6,6 +6,7 @@ import type { TemplateCodegenContext } from './context'; import type { TemplateCodegenOptions } from './index'; import { generateInterpolation } from './interpolation'; import { generateTemplateChild } from './templateChild'; +import { generateElement } from './element'; export function* generateVIf( options: TemplateCodegenOptions, @@ -58,6 +59,23 @@ export function* generateVIf( if (isFragment(node)) { yield* ctx.resetDirectiveComments('end of v-if start'); } + // #4539 + if ( + node.codegenNode + && node.codegenNode.type === CompilerDOM.NodeTypes.JS_CONDITIONAL_EXPRESSION + && node.codegenNode.consequent.type === CompilerDOM.NodeTypes.VNODE_CALL + && node.codegenNode.consequent.tag === CompilerDOM.FRAGMENT + ) { + yield* generateElement(options, ctx, { + ...branch, + type: CompilerDOM.NodeTypes.ELEMENT, + tag: "template", + tagType: CompilerDOM.ElementTypes.TEMPLATE, + props: branch.userKey ? [branch.userKey] : [], + ns: 0, + codegenNode: undefined + }, currentComponent, componentCtxVar); + } let prev: CompilerDOM.TemplateChildNode | undefined; for (const childNode of branch.children) { yield* generateTemplateChild(options, ctx, childNode, currentComponent, prev, componentCtxVar); diff --git a/packages/tsc/tests/index.spec.ts b/packages/tsc/tests/index.spec.ts index c6d1c8b594..e946c8ff34 100644 --- a/packages/tsc/tests/index.spec.ts +++ b/packages/tsc/tests/index.spec.ts @@ -9,6 +9,7 @@ const shouldErrorDirs = [ 'should-error', 'should-error-2', 'should-error-3', + 'should-error-#4539', ]; function prettyPath(path: string, isRoot: boolean) { diff --git a/test-workspace/tsc/should-error-#4539/main.vue b/test-workspace/tsc/should-error-#4539/main.vue new file mode 100644 index 0000000000..bcc32e2fe1 --- /dev/null +++ b/test-workspace/tsc/should-error-#4539/main.vue @@ -0,0 +1,11 @@ + + + diff --git a/test-workspace/tsc/should-error-#4539/tsconfig.json b/test-workspace/tsc/should-error-#4539/tsconfig.json new file mode 100644 index 0000000000..a1e651d6e8 --- /dev/null +++ b/test-workspace/tsc/should-error-#4539/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": [ "**/*" ] +}