]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): handle complex destructure expressions in v-for
authorEvan You <yyx990803@gmail.com>
Sat, 28 Sep 2019 20:02:08 +0000 (16:02 -0400)
committerEvan You <yyx990803@gmail.com>
Sat, 28 Sep 2019 20:02:08 +0000 (16:02 -0400)
packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vSlot.ts

index 262c5f2dcee8a9240ef85d5c9f0e964a3952b0fa..cf806f8a2d38b6b0789c1565b117d3ccaee87644 100644 (file)
@@ -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(
-      `<div v-for="i in list"/>`
-    ) 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(
-      `<div v-for="i in list.concat([foo])"/>`
-    ) 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(
-      `<div v-for="i in list">{{ i }}{{ j }}</div>`
-    ) 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(
-      `<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`
-    ) 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(
-      `<div><div v-for="i in list" />{{ i }}</div>`
-    ) as ElementNode
-    expect((node.children[1] as InterpolationNode).content).toMatchObject({
-      type: NodeTypes.SIMPLE_EXPRESSION,
-      content: `_ctx.i`
-    })
-  })
-
-  test('nested v-for', () => {
-    const node = parseWithExpressionTransform(
-      `<div v-for="i in list">
-        <div v-for="i in list">{{ i + j }}</div>{{ i }}
-      </div>`
-    ) 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` }
index 255e6604e51a0687d5e5de97263aeec69475a0ee..3356981c16b4151fc502a26a28a36ef0b12361e7 100644 (file)
 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('<span v-for="index in 5" />')
+    const forNode = parseWithForTransform(
+      '<span v-for="index in 5" />'
+    ) 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('<span v-for="(item) in items" />')
+    const forNode = parseWithForTransform(
+      '<span v-for="(item) in items" />'
+    ) 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(
       '<span v-for="({ id, value }) in items" />'
-    )
+    ) 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(
       '<span v-for="([ id, value ]) in items" />'
-    )
+    ) 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(
       '<span v-for="(item, key) in items" />'
-    )
+    ) 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(
       '<span v-for="(value, key, index) in items" />'
-    )
+    ) 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(
       '<span v-for="(value,,index) in items" />'
-    )
+    ) 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('<span v-for="(,,index) in items" />')
+    const forNode = parseWithForTransform(
+      '<span v-for="(,,index) in items" />'
+    ) 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('<span v-for="item in items" />')
+    const forNode = parseWithForTransform(
+      '<span v-for="item in items" />'
+    ) 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('<span v-for="item, key in items" />')
+    const forNode = parseWithForTransform(
+      '<span v-for="item, key in items" />'
+    ) 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(
       '<span v-for="value, key, index in items" />'
-    )
+    ) 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(
       '<span v-for="value, , index in items" />'
-    )
+    ) 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('<span v-for=", , index in items" />')
+    const forNode = parseWithForTransform(
+      '<span v-for=", , index in items" />'
+    ) 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 = '<span v-for="item in items" />'
-      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 = '<span v-for="( item ) in items" />'
-      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 = '<span v-for="(  { id, key }) in items" />'
-      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 = '<span v-for="( item, key, index ) in items" />'
-      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 = '<span v-for="( item,, index ) in items" />'
-      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(`<div v-for="i in list"/>`, {
+        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(
+        `<div v-for="i in list.concat([foo])"/>`,
+        { 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(
+        `<div v-for="i in list">{{ i }}{{ j }}</div>`,
+        { 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(
+        `<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`,
+        { 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(
+        `<div><div v-for="i in list" />{{ i }}</div>`,
+        { 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(
+        `<div v-for="i in list">
+          <div v-for="i in list">{{ i + j }}</div>{{ i }}
+        </div>`,
+        { 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(
+        `<div v-for="({ foo = bar, baz: [qux = quux] }) in list">
+          {{ foo + bar + baz + qux + quux }}
+        </div>`,
+        { 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` }
+        ]
+      })
+    })
+  })
 })
index cdc54fc8f11fa03bac7986c41c4232a7f81d2203..748ba3496f41e5e833701c5f11a38abebd4f1464 100644 (file)
@@ -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: [
                         {
index edb924c8aa804d79be581782f77bbf12441d169d..97a92f5b0f1f623425fb7832aede57e3b7510db5 100644 (file)
@@ -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[]
 }
 
index 7c4e5ef36504acea094ab031b3c07a0794702fe6..0fb5a5954685443da8874ecbc50c18ff5a736d61 100644 (file)
@@ -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()
index 0587434d65a6faf59cdb11412b2b73d114418c84..6a4f893a0ecbd579798f45122a7d86ba528489ea 100644 (file)
@@ -63,8 +63,8 @@ export interface TransformContext extends Required<TransformOptions> {
   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
 }
 
index 7b90f874120974d1cd7749d40992d4582c55e08d..b69f8caac78d51ce0bff085a66d3e400b90bd656 100644 (file)
@@ -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' &&
index 794f3968b2e6e139e2bb29baffbea310add6bd3f..ddfca31f5e9e27b1c855bf3385133e77e578c927 100644 (file)
@@ -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
index cf6a51fef79411ff2a06704bde6f378c638775d4..44cadf23e0f4426e704e5d2f1844ee5dbe3db595 100644 (file)
@@ -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!)
       }
     }
   }