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": [ "**/*" ]
+}