diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
index 3da778eb6..2be889209 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -45,6 +45,20 @@ return function render(_ctx, _cache) {
}"
`;
+exports[`compiler: v-for > codegen > no whitespace around (in|of) with basic v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList([items], (item) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
exports[`compiler: v-for > codegen > skipped key 1`] = `
"const _Vue = Vue
diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
index 3da778eb6..2be889209 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
@@ -45,6 +45,20 @@ return function render(_ctx, _cache) {
}"
`;
+exports[`compiler: v-for > codegen > no whitespace around (in|of) with basic v-for 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList([items], (item) => {
+ return (_openBlock(), _createElementBlock("span"))
+ }), 256 /* UNKEYED_FRAGMENT */))
+ }
+}"
+`;
+
exports[`compiler: v-for > codegen > skipped key 1`] = `
"const _Vue = Vue
diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
index fead2476a..6a5c74382 100644
--- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
@@ -218,6 +218,43 @@ describe('compiler: v-for', () => {
"state ['my items']",
)
})
+
+ test('no whitespace around (in|of) with simple expression', () => {
+ const { node: forNode } = parseWithForTransform(
+ '',
+ )
+
+ expect(forNode.keyAlias).toBeUndefined()
+ expect(forNode.objectIndexAlias).toBeUndefined()
+ expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('[items]')
+ })
+
+ test('no whitespace around (in|of) with object de-structured value', () => {
+ const { node: forNode } = parseWithForTransform(
+ '',
+ )
+
+ expect(forNode.keyAlias).toBeUndefined()
+ expect(forNode.objectIndexAlias).toBeUndefined()
+ expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
+ '{ id, value }',
+ )
+ expect((forNode.source as SimpleExpressionNode).content).toBe('[item]')
+ })
+
+ test('no whitespace around (in|of) with array de-structured value', () => {
+ const { node: forNode } = parseWithForTransform(
+ '',
+ )
+
+ expect(forNode.keyAlias).toBeUndefined()
+ expect(forNode.objectIndexAlias).toBeUndefined()
+ expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
+ '[ id ]',
+ )
+ expect((forNode.source as SimpleExpressionNode).content).toBe('[item]')
+ })
})
describe('errors', () => {
@@ -257,6 +294,18 @@ describe('compiler: v-for', () => {
)
})
+ test('invalid expression containing (in|of)', () => {
+ const onError = vi.fn()
+ parseWithForTransform('', { onError })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
+ )
+ })
+
test('missing source', () => {
const onError = vi.fn()
parseWithForTransform('', { onError })
@@ -293,6 +342,18 @@ describe('compiler: v-for', () => {
)
})
+ test('missing source and value', () => {
+ const onError = vi.fn()
+ parseWithForTransform('', { onError })
+
+ expect(onError).toHaveBeenCalledTimes(1)
+ expect(onError).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
+ }),
+ )
+ })
+
test(' key placement', () => {
const onError = vi.fn()
parseWithForTransform(
@@ -437,6 +498,48 @@ describe('compiler: v-for', () => {
)
})
+ test('no whitespace around (in|of) with bracketed value, key, index', () => {
+ const source = ''
+ const { node: forNode } = parseWithForTransform(source)
+
+ const itemOffset = source.indexOf('item')
+ const value = forNode.valueAlias as SimpleExpressionNode
+ expect(value.content).toBe('item')
+ expect(value.loc.start.offset).toBe(itemOffset)
+ expect(value.loc.start.line).toBe(1)
+ expect(value.loc.start.column).toBe(itemOffset + 1)
+ expect(value.loc.end.line).toBe(1)
+ expect(value.loc.end.column).toBe(itemOffset + 1 + `item`.length)
+
+ const keyOffset = source.indexOf('key')
+ const key = forNode.keyAlias as SimpleExpressionNode
+ expect(key.content).toBe('key')
+ expect(key.loc.start.offset).toBe(keyOffset)
+ expect(key.loc.start.line).toBe(1)
+ expect(key.loc.start.column).toBe(keyOffset + 1)
+ expect(key.loc.end.line).toBe(1)
+ expect(key.loc.end.column).toBe(keyOffset + 1 + `key`.length)
+
+ const indexOffset = source.indexOf('index')
+ const index = forNode.objectIndexAlias as SimpleExpressionNode
+ expect(index.content).toBe('index')
+ expect(index.loc.start.offset).toBe(indexOffset)
+ expect(index.loc.start.line).toBe(1)
+ expect(index.loc.start.column).toBe(indexOffset + 1)
+ expect(index.loc.end.line).toBe(1)
+ expect(index.loc.end.column).toBe(indexOffset + 1 + `index`.length)
+
+ const itemsOffset = source.indexOf('[items]')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('[items]')
+ expect(forNode.source.loc.start.offset).toBe(itemsOffset)
+ expect(forNode.source.loc.start.line).toBe(1)
+ expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
+ expect(forNode.source.loc.end.line).toBe(1)
+ expect(forNode.source.loc.end.column).toBe(
+ itemsOffset + 1 + `[items]`.length,
+ )
+ })
+
test('skipped key', () => {
const source = ''
const { node: forNode } = parseWithForTransform(source)
@@ -747,6 +850,21 @@ describe('compiler: v-for', () => {
expect(generate(root).code).toMatchSnapshot()
})
+ test('no whitespace around (in|of) with basic v-for', () => {
+ const {
+ root,
+ node: { codegenNode },
+ } = parseWithForTransform('')
+ expect(assertSharedCodegen(codegenNode)).toMatchObject({
+ source: { content: `[items]` },
+ params: [{ content: `item` }],
+ innerVNodeCall: {
+ tag: `"span"`,
+ },
+ })
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
test('value + key + index', () => {
const {
root,
diff --git a/packages/compiler-core/src/parser.ts b/packages/compiler-core/src/parser.ts
index 3eb3a976f..5e8039129 100644
--- a/packages/compiler-core/src/parser.ts
+++ b/packages/compiler-core/src/parser.ts
@@ -491,15 +491,25 @@ const tokenizer = new Tokenizer(stack, {
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
+function matchForAlias(exp: string) {
+ const inMatch = exp.match(forAliasRE)
+ if (!inMatch) return
+
+ const LHS = inMatch[1].trim()
+ const RHS = inMatch[2].trim()
+
+ if (LHS && RHS) return { LHS, RHS }
+}
+
function parseForExpression(
input: SimpleExpressionNode,
): ForParseResult | undefined {
const loc = input.loc
const exp = input.content
- const inMatch = exp.match(forAliasRE)
+ const inMatch = matchForAlias(exp)
if (!inMatch) return
- const [, LHS, RHS] = inMatch
+ const { LHS, RHS } = inMatch
const createAliasExpression = (
content: string,
@@ -518,14 +528,14 @@ function parseForExpression(
}
const result: ForParseResult = {
- source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
+ source: createAliasExpression(RHS, exp.indexOf(RHS, LHS.length)),
value: undefined,
key: undefined,
index: undefined,
finalized: false,
}
- let valueContent = LHS.trim().replace(stripParensRE, '').trim()
+ let valueContent = LHS.replace(stripParensRE, '').trim()
const trimmedOffset = LHS.indexOf(valueContent)
const iteratorMatch = valueContent.match(forIteratorRE)
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index b49d70bb2..051a44fa1 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -563,4 +563,5 @@ export function getMemoedVNodeCall(
}
}
-export const forAliasRE: RegExp = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/
+export const forAliasRE: RegExp =
+ /([\s\S]*?[\s\)\}\]]+)(?:in|of)([\s\[]+[\s\S]*)/