From: Evan You Date: Sat, 28 Sep 2019 20:02:08 +0000 (-0400) Subject: feat(compiler): handle complex destructure expressions in v-for X-Git-Tag: v3.0.0-alpha.0~670 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=389a07835c716f1009d44b347e3519dd1f0a9f2f;p=thirdparty%2Fvuejs%2Fcore.git feat(compiler): handle complex destructure expressions in v-for --- diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 262c5f2dce..cf806f8a2d 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -4,13 +4,11 @@ import { ElementNode, DirectiveNode, NodeTypes, - ForNode, CompilerOptions, IfNode, InterpolationNode } from '../../src' import { transformIf } from '../../src/transforms/vIf' -import { transformFor } from '../../src/transforms/vFor' import { transformExpression } from '../../src/transforms/transformExpression' function parseWithExpressionTransform( @@ -20,7 +18,7 @@ function parseWithExpressionTransform( const ast = parse(template) transform(ast, { prefixIdentifiers: true, - nodeTransforms: [transformIf, transformFor, transformExpression], + nodeTransforms: [transformIf, transformExpression], ...options }) return ast.children[0] @@ -169,103 +167,6 @@ describe('compiler: expression transform', () => { }) }) - test('should prefix v-for source', () => { - const node = parseWithExpressionTransform( - `
` - ) as ForNode - expect(node.source).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_ctx.list` - }) - }) - - test('should prefix v-for source w/ complex expression', () => { - const node = parseWithExpressionTransform( - `
` - ) as ForNode - expect(node.source).toMatchObject({ - type: NodeTypes.COMPOUND_EXPRESSION, - children: [ - { content: `_ctx.list` }, - `.`, - { content: `concat` }, - `([`, - { content: `_ctx.foo` }, - `])` - ] - }) - }) - - test('should not prefix v-for alias', () => { - const node = parseWithExpressionTransform( - `
{{ i }}{{ j }}
` - ) as ForNode - const div = node.children[0] as ElementNode - expect((div.children[0] as InterpolationNode).content).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `i` - }) - expect((div.children[1] as InterpolationNode).content).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_ctx.j` - }) - }) - - test('should not prefix v-for aliases (multiple)', () => { - const node = parseWithExpressionTransform( - `
{{ i + j + k }}{{ l }}
` - ) as ForNode - const div = node.children[0] as ElementNode - expect((div.children[0] as InterpolationNode).content).toMatchObject({ - type: NodeTypes.COMPOUND_EXPRESSION, - children: [ - { content: `i` }, - ` + `, - { content: `j` }, - ` + `, - { content: `k` } - ] - }) - expect((div.children[1] as InterpolationNode).content).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_ctx.l` - }) - }) - - test('should prefix id outside of v-for', () => { - const node = parseWithExpressionTransform( - `
{{ i }}
` - ) as ElementNode - expect((node.children[1] as InterpolationNode).content).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `_ctx.i` - }) - }) - - test('nested v-for', () => { - const node = parseWithExpressionTransform( - `
-
{{ i + j }}
{{ i }} -
` - ) as ForNode - const outerDiv = node.children[0] as ElementNode - const innerFor = outerDiv.children[0] as ForNode - const innerExp = (innerFor.children[0] as ElementNode) - .children[0] as InterpolationNode - expect(innerExp.content).toMatchObject({ - type: NodeTypes.COMPOUND_EXPRESSION, - children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }] - }) - - // when an inner v-for shadows a variable of an outer v-for and exit, - // it should not cause the outer v-for's alias to be removed from known ids - const outerExp = outerDiv.children[1] as InterpolationNode - expect(outerExp.content).toMatchObject({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `i` - }) - }) - test('should not prefix whitelisted globals', () => { const node = parseWithExpressionTransform( `{{ Math.max(1, 2) }}` @@ -334,7 +235,9 @@ describe('compiler: expression transform', () => { expect(node.content).toMatchObject({ type: NodeTypes.COMPOUND_EXPRESSION, children: [ - `({ foo }) => `, + `({ `, + { content: `foo` }, + ` }) => `, { content: `foo` }, ` + `, { content: `_ctx.bar` } diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 255e6604e5..3356981c16 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -1,148 +1,185 @@ import { parse } from '../../src/parse' import { transform } from '../../src/transform' import { transformFor } from '../../src/transforms/vFor' -import { ForNode, NodeTypes, SimpleExpressionNode } from '../../src/ast' +import { + ForNode, + NodeTypes, + SimpleExpressionNode, + ElementNode, + InterpolationNode +} from '../../src/ast' import { ErrorCodes } from '../../src/errors' import { CompilerOptions } from '../../src' +import { transformExpression } from '../../src/transforms/transformExpression' function parseWithForTransform( template: string, options: CompilerOptions = {} -): ForNode { +) { const node = parse(template, options) - transform(node, { nodeTransforms: [transformFor], ...options }) - if (!options.onError) { - expect(node.children.length).toBe(1) - expect(node.children[0].type).toBe(NodeTypes.FOR) - } - return node.children[0] as ForNode + transform(node, { + nodeTransforms: [ + transformFor, + ...(options.prefixIdentifiers ? [transformExpression] : []) + ], + ...options + }) + return node.children[0] } describe('compiler: transform v-for', () => { test('number expression', () => { - const forNode = parseWithForTransform('') + const forNode = parseWithForTransform( + '' + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('index') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('index') expect((forNode.source as SimpleExpressionNode).content).toBe('5') }) test('value', () => { - const forNode = parseWithForTransform('') + const forNode = parseWithForTransform( + '' + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('item') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('object de-structured value', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('{ id, value }') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe( + '{ id, value }' + ) expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('array de-structured value', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('[ id, value ]') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe( + '[ id, value ]' + ) expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('value and key', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') + expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('item') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('value, key and index', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') + expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') + expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( + 'index' + ) + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('skipped key', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') + expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( + 'index' + ) + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('skipped value and key', () => { - const forNode = parseWithForTransform('') + const forNode = parseWithForTransform( + '' + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') + expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( + 'index' + ) expect(forNode.valueAlias).toBeUndefined() expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed value', () => { - const forNode = parseWithForTransform('') + const forNode = parseWithForTransform( + '' + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('item') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed value and key', () => { - const forNode = parseWithForTransform('') + const forNode = parseWithForTransform( + '' + ) as ForNode expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') + expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('item') + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed value, key and index', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') + expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') + expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( + 'index' + ) + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed skipped key', () => { const forNode = parseWithForTransform( '' - ) + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') + expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( + 'index' + ) + expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed skipped value and key', () => { - const forNode = parseWithForTransform('') + const forNode = parseWithForTransform( + '' + ) as ForNode expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') + expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( + 'index' + ) expect(forNode.valueAlias).toBeUndefined() expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) @@ -210,17 +247,16 @@ describe('compiler: transform v-for', () => { describe('source location', () => { test('value & source', () => { const source = '' - const forNode = parseWithForTransform(source) + const forNode = parseWithForTransform(source) as ForNode const itemOffset = source.indexOf('item') - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - itemOffset + 1 + `item`.length - ) + const value = forNode.valueAlias as SimpleExpressionNode + expect((forNode.valueAlias as SimpleExpressionNode).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 itemsOffset = source.indexOf('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items') @@ -235,17 +271,16 @@ describe('compiler: transform v-for', () => { test('bracketed value', () => { const source = '' - const forNode = parseWithForTransform(source) + const forNode = parseWithForTransform(source) as ForNode const itemOffset = source.indexOf('item') - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - itemOffset + 1 + `item`.length - ) + 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 itemsOffset = source.indexOf('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items') @@ -260,17 +295,16 @@ describe('compiler: transform v-for', () => { test('de-structured value', () => { const source = '' - const forNode = parseWithForTransform(source) + const forNode = parseWithForTransform(source) as ForNode + const value = forNode.valueAlias as SimpleExpressionNode const valueIndex = source.indexOf('{ id, key }') - expect(forNode.valueAlias!.content).toBe('{ id, key }') - expect(forNode.valueAlias!.loc.start.offset).toBe(valueIndex) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe(valueIndex + 1) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - valueIndex + 1 + '{ id, key }'.length - ) + expect(value.content).toBe('{ id, key }') + expect(value.loc.start.offset).toBe(valueIndex) + expect(value.loc.start.line).toBe(1) + expect(value.loc.start.column).toBe(valueIndex + 1) + expect(value.loc.end.line).toBe(1) + expect(value.loc.end.column).toBe(valueIndex + 1 + '{ id, key }'.length) const itemsOffset = source.indexOf('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items') @@ -285,37 +319,34 @@ describe('compiler: transform v-for', () => { test('bracketed value, key, index', () => { const source = '' - const forNode = parseWithForTransform(source) + const forNode = parseWithForTransform(source) as ForNode const itemOffset = source.indexOf('item') - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - itemOffset + 1 + `item`.length - ) + 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') - expect(forNode.keyAlias!.content).toBe('key') - expect(forNode.keyAlias!.loc.start.offset).toBe(keyOffset) - expect(forNode.keyAlias!.loc.start.line).toBe(1) - expect(forNode.keyAlias!.loc.start.column).toBe(keyOffset + 1) - expect(forNode.keyAlias!.loc.end.line).toBe(1) - expect(forNode.keyAlias!.loc.end.column).toBe( - keyOffset + 1 + `key`.length - ) + 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') - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.objectIndexAlias!.loc.start.offset).toBe(indexOffset) - expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.start.column).toBe(indexOffset + 1) - expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.end.column).toBe( - indexOffset + 1 + `index`.length - ) + 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') @@ -330,27 +361,25 @@ describe('compiler: transform v-for', () => { test('skipped key', () => { const source = '' - const forNode = parseWithForTransform(source) + const forNode = parseWithForTransform(source) as ForNode const itemOffset = source.indexOf('item') - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - itemOffset + 1 + `item`.length - ) + 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 indexOffset = source.indexOf('index') - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.objectIndexAlias!.loc.start.offset).toBe(indexOffset) - expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.start.column).toBe(indexOffset + 1) - expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.end.column).toBe( - indexOffset + 1 + `index`.length - ) + 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') @@ -363,4 +392,146 @@ describe('compiler: transform v-for', () => { ) }) }) + + describe('prefixIdentifiers: true', () => { + test('should prefix v-for source', () => { + const node = parseWithForTransform(`
`, { + prefixIdentifiers: true + }) as ForNode + expect(node.source).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.list` + }) + }) + + test('should prefix v-for source w/ complex expression', () => { + const node = parseWithForTransform( + `
`, + { prefixIdentifiers: true } + ) as ForNode + expect(node.source).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `_ctx.list` }, + `.`, + { content: `concat` }, + `([`, + { content: `_ctx.foo` }, + `])` + ] + }) + }) + + test('should not prefix v-for alias', () => { + const node = parseWithForTransform( + `
{{ i }}{{ j }}
`, + { prefixIdentifiers: true } + ) as ForNode + const div = node.children[0] as ElementNode + expect((div.children[0] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `i` + }) + expect((div.children[1] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.j` + }) + }) + + test('should not prefix v-for aliases (multiple)', () => { + const node = parseWithForTransform( + `
{{ i + j + k }}{{ l }}
`, + { prefixIdentifiers: true } + ) as ForNode + const div = node.children[0] as ElementNode + expect((div.children[0] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `i` }, + ` + `, + { content: `j` }, + ` + `, + { content: `k` } + ] + }) + expect((div.children[1] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.l` + }) + }) + + test('should prefix id outside of v-for', () => { + const node = parseWithForTransform( + `
{{ i }}
`, + { prefixIdentifiers: true } + ) as ElementNode + expect((node.children[1] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.i` + }) + }) + + test('nested v-for', () => { + const node = parseWithForTransform( + `
+
{{ i + j }}
{{ i }} +
`, + { prefixIdentifiers: true } + ) as ForNode + const outerDiv = node.children[0] as ElementNode + const innerFor = outerDiv.children[0] as ForNode + const innerExp = (innerFor.children[0] as ElementNode) + .children[0] as InterpolationNode + expect(innerExp.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }] + }) + + // when an inner v-for shadows a variable of an outer v-for and exit, + // it should not cause the outer v-for's alias to be removed from known ids + const outerExp = outerDiv.children[1] as InterpolationNode + expect(outerExp.content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `i` + }) + }) + + test('v-for aliases w/ complex expressions', () => { + const node = parseWithForTransform( + `
+ {{ foo + bar + baz + qux + quux }} +
`, + { prefixIdentifiers: true } + ) as ForNode + expect(node.valueAlias!).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `{ `, + { content: `foo` }, + ` = `, + { content: `_ctx.bar` }, + `, baz: [`, + { content: `qux` }, + ` = `, + { content: `_ctx.quux` }, + `] }` + ] + }) + const div = node.children[0] as ElementNode + expect((div.children[0] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `foo` }, + ` + `, + { content: `_ctx.bar` }, + ` + `, + { content: `_ctx.baz` }, + ` + `, + { content: `qux` }, + ` + `, + { content: `_ctx.quux` } + ] + }) + }) + }) }) diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index cdc54fc8f1..748ba3496f 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -83,9 +83,8 @@ describe('compiler: transform component slots', () => { default: { type: NodeTypes.JS_SLOT_FUNCTION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ foo }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `foo` }, ` }`] }, returns: [ { @@ -266,9 +265,8 @@ describe('compiler: transform component slots', () => { default: { type: NodeTypes.JS_SLOT_FUNCTION, params: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: `{ bar }`, - isStatic: false + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ `, { content: `bar` }, ` }`] }, returns: [ { diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index edb924c8aa..97a92f5b0f 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -150,9 +150,9 @@ export interface IfBranchNode extends Node { export interface ForNode extends Node { type: NodeTypes.FOR source: ExpressionNode - valueAlias: SimpleExpressionNode | undefined - keyAlias: SimpleExpressionNode | undefined - objectIndexAlias: SimpleExpressionNode | undefined + valueAlias: ExpressionNode | undefined + keyAlias: ExpressionNode | undefined + objectIndexAlias: ExpressionNode | undefined children: ChildNode[] } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 7c4e5ef365..0fb5a59546 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -509,14 +509,14 @@ function genFor(node: ForNode, context: CodegenContext) { genNode(source, context) push(`, (`) if (valueAlias) { - genExpression(valueAlias, context) + genNode(valueAlias, context) } if (keyAlias) { if (!valueAlias) { push(`__value`) } push(`, `) - genExpression(keyAlias, context) + genNode(keyAlias, context) } if (objectIndexAlias) { if (!keyAlias) { @@ -527,7 +527,7 @@ function genFor(node: ForNode, context: CodegenContext) { } } push(`, `) - genExpression(objectIndexAlias, context) + genNode(objectIndexAlias, context) } push(`) => {`) indent() diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 0587434d65..6a4f893a0e 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -63,8 +63,8 @@ export interface TransformContext extends Required { replaceNode(node: ChildNode): void removeNode(node?: ChildNode): void onNodeRemoved: () => void - addIdentifier(id: string): void - removeIdentifier(id: string): void + addIdentifiers(exp: ExpressionNode): void + removeIdentifiers(exp: ExpressionNode): void hoist(exp: JSChildNode): ExpressionNode } @@ -126,15 +126,24 @@ function createTransformContext( context.parent.children.splice(removalIndex, 1) }, onNodeRemoved: () => {}, - addIdentifier(id) { - const { identifiers } = context - if (identifiers[id] === undefined) { - identifiers[id] = 0 + addIdentifiers(exp) { + // identifier tracking only happens in non-browser builds. + if (!__BROWSER__) { + if (exp.identifiers) { + exp.identifiers.forEach(addId) + } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) { + addId(exp.content) + } } - ;(identifiers[id] as number)++ }, - removeIdentifier(id) { - ;(context.identifiers[id] as number)-- + removeIdentifiers(exp) { + if (!__BROWSER__) { + if (exp.identifiers) { + exp.identifiers.forEach(removeId) + } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) { + removeId(exp.content) + } + } }, hoist(exp) { context.hoists.push(exp) @@ -145,6 +154,19 @@ function createTransformContext( ) } } + + function addId(id: string) { + const { identifiers } = context + if (identifiers[id] === undefined) { + identifiers[id] = 0 + } + ;(identifiers[id] as number)++ + } + + function removeId(id: string) { + ;(context.identifiers[id] as number)-- + } + return context } diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 7b90f87412..b69f8caac7 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -67,6 +67,8 @@ interface PrefixMeta { export function processExpression( node: SimpleExpressionNode, context: TransformContext, + // some expressions like v-slot props & v-for aliases should be parsed as + // function params asParams: boolean = false ): ExpressionNode { if (!context.prefixIdentifiers) { @@ -75,7 +77,7 @@ export function processExpression( // fast path if expression is a simple identifier. if (isSimpleIdentifier(node.content)) { - if (!context.identifiers[node.content]) { + if (!asParams && !context.identifiers[node.content]) { node.content = `_ctx.${node.content}` } return node @@ -107,17 +109,14 @@ export function processExpression( if (node.type === 'Identifier') { if (!ids.includes(node)) { if (!knownIds[node.name] && shouldPrefix(node, parent)) { - if ( - isPropertyKey(node, parent) && - (parent as Property).value === node - ) { + if (isPropertyShorthand(node, parent)) { // property shorthand like { foo }, we need to add the key since we // rewrite the value node.prefix = `${node.name}: ` } node.name = `_ctx.${node.name}` ids.push(node) - } else if (!isPropertyKey(node, parent)) { + } else if (!isStaticPropertyKey(node, parent)) { // also generate sub-expressioms for other identifiers for better // source map support. (except for property keys which are static) ids.push(node) @@ -131,9 +130,11 @@ export function processExpression( enter(child, parent) { if ( child.type === 'Identifier' && - !// do not keep as scope variable if this is a default value - // assignment of a param - ( + // do not record as scope variable if is a destrcuture key + !isStaticPropertyKey(child, parent) && + // do not record if this is a default value + // assignment of a destructured variable + !( parent && parent.type === 'AssignmentPattern' && parent.right === child @@ -213,7 +214,16 @@ const isFunction = (node: Node): node is Function => /Function(Expression|Declaration)$/.test(node.type) const isPropertyKey = (node: Node, parent: Node) => - parent.type === 'Property' && parent.key === node && !parent.computed + parent && + parent.type === 'Property' && + parent.key === node && + !parent.computed + +const isPropertyShorthand = (node: Node, parent: Node) => + isPropertyKey(node, parent) && (parent as Property).value === node + +const isStaticPropertyKey = (node: Node, parent: Node) => + isPropertyKey(node, parent) && (parent as Property).value !== node const globals = new Set( ( @@ -236,11 +246,7 @@ function shouldPrefix(identifier: Identifier, parent: Node) { parent.params.includes(identifier)) ) && // not a key of Property - !( - isPropertyKey(identifier, parent) && - // shorthand keys should be prefixed - !((parent as Property).value === identifier) - ) && + !isStaticPropertyKey(identifier, parent) && // not a property of a MemberExpression !( parent.type === 'MemberExpression' && diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 794f3968b2..ddfca31f5e 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -39,19 +39,20 @@ export const transformFor = createStructuralDirectiveTransform( children: [node] }) - // scope management - const { addIdentifier, removeIdentifier } = context - - // inject identifiers to context - value && addIdentifier(value.content) - key && addIdentifier(key.content) - index && addIdentifier(index.content) - - return () => { - // remove injected identifiers on exit - value && removeIdentifier(value.content) - key && removeIdentifier(key.content) - index && removeIdentifier(index.content) + if (!__BROWSER__) { + // scope management + const { addIdentifiers, removeIdentifiers } = context + + // inject identifiers to context + value && addIdentifiers(value) + key && addIdentifiers(key) + index && addIdentifiers(index) + + return () => { + value && removeIdentifiers(value) + key && removeIdentifiers(key) + index && removeIdentifiers(index) + } } } else { context.onError( @@ -67,14 +68,16 @@ export const transformFor = createStructuralDirectiveTransform( ) const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ +// This regex doesn't cover the case if key or index aliases have destructuring, +// but those do not make sense in the first place, so this works in practice. const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ const stripParensRE = /^\(|\)$/g interface ForParseResult { source: ExpressionNode - value: SimpleExpressionNode | undefined - key: SimpleExpressionNode | undefined - index: SimpleExpressionNode | undefined + value: ExpressionNode | undefined + key: ExpressionNode | undefined + index: ExpressionNode | undefined } function parseForExpression( @@ -88,21 +91,22 @@ function parseForExpression( const [, LHS, RHS] = inMatch - let source: ExpressionNode = createAliasExpression( - loc, - RHS.trim(), - exp.indexOf(RHS, LHS.length) - ) - if (!__BROWSER__ && context.prefixIdentifiers) { - source = processExpression(source, context) - } - const result: ForParseResult = { - source, + source: createAliasExpression( + loc, + RHS.trim(), + exp.indexOf(RHS, LHS.length) + ), value: undefined, key: undefined, index: undefined } + if (!__BROWSER__ && context.prefixIdentifiers) { + result.source = processExpression( + result.source as SimpleExpressionNode, + context + ) + } let valueContent = LHS.trim() .replace(stripParensRE, '') @@ -118,6 +122,9 @@ function parseForExpression( if (keyContent) { keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length) result.key = createAliasExpression(loc, keyContent, keyOffset) + if (!__BROWSER__ && context.prefixIdentifiers) { + result.key = processExpression(result.key, context, true) + } } if (iteratorMatch[2]) { @@ -134,12 +141,18 @@ function parseForExpression( : trimmedOffset + valueContent.length ) ) + if (!__BROWSER__ && context.prefixIdentifiers) { + result.index = processExpression(result.index, context, true) + } } } } if (valueContent) { result.value = createAliasExpression(loc, valueContent, trimmedOffset) + if (!__BROWSER__ && context.prefixIdentifiers) { + result.value = processExpression(result.value, context, true) + } } return result diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index cf6a51fef7..44cadf23e0 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -31,12 +31,9 @@ export const trackSlotScopes: NodeTransform = (node, context) => { ) { const vSlot = node.props.find(isVSlot) if (vSlot && vSlot.exp) { - const { identifiers } = vSlot.exp - if (identifiers) { - identifiers.forEach(context.addIdentifier) - return () => { - identifiers.forEach(context.removeIdentifier) - } + context.addIdentifiers(vSlot.exp) + return () => { + context.removeIdentifiers(vSlot.exp!) } } }