diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 947bfb04..5b7edf2b 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -232,7 +232,7 @@ Object { }, }, "name": "attr", - "type": 5, + "type": 6, "value": Object { "content": "c", "isEmpty": false, @@ -315,7 +315,7 @@ Object { }, }, "name": "attr", - "type": 5, + "type": 6, "value": Object { "content": "&#a;", "isEmpty": false, @@ -398,7 +398,7 @@ Object { }, }, "name": "attr", - "type": 5, + "type": 6, "value": Object { "content": "ΓΏ", "isEmpty": false, @@ -481,7 +481,7 @@ Object { }, }, "name": "attr", - "type": 5, + "type": 6, "value": Object { "content": "&#xg;", "isEmpty": false, @@ -1183,7 +1183,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "", "isEmpty": true, @@ -1218,7 +1218,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "", "isEmpty": true, @@ -2359,7 +2359,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": undefined, }, ], @@ -2449,7 +2449,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": undefined, }, ], @@ -2539,7 +2539,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": undefined, }, ], @@ -2629,7 +2629,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -2736,7 +2736,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -2843,7 +2843,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -2950,7 +2950,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -3057,7 +3057,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -3164,7 +3164,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -3271,7 +3271,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -3378,7 +3378,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "abc", "isEmpty": false, @@ -3884,23 +3884,38 @@ Object { Object { "children": Array [ Object { - "content": "a < b", - "isInterpolation": true, - "isStatic": false, + "content": Object { + "content": "a < b", + "isStatic": false, + "loc": Object { + "end": Object { + "column": 18, + "line": 1, + "offset": 17, + }, + "source": "a < b", + "start": Object { + "column": 13, + "line": 1, + "offset": 12, + }, + }, + "type": 4, + }, "loc": Object { "end": Object { - "column": 18, + "column": 20, "line": 1, - "offset": 17, + "offset": 19, }, - "source": "a < b", + "source": "{{a < b}}", "start": Object { - "column": 13, + "column": 11, "line": 1, - "offset": 12, + "offset": 10, }, }, - "type": 4, + "type": 5, }, ], "codegenNode": undefined, @@ -4133,7 +4148,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "/", "isEmpty": false, @@ -4240,7 +4255,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": undefined, }, ], @@ -4330,7 +4345,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": undefined, }, ], @@ -4666,7 +4681,7 @@ class=\\"bar\\">", }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "foo", "isEmpty": false, @@ -4701,7 +4716,7 @@ class=\\"bar\\">", }, }, "name": "class", - "type": 5, + "type": 6, "value": Object { "content": "bar", "isEmpty": false, @@ -4810,7 +4825,7 @@ Object { }, }, "name": "id", - "type": 5, + "type": 6, "value": Object { "content": "foo", "isEmpty": false, @@ -4845,7 +4860,7 @@ Object { }, }, "name": "class", - "type": 5, + "type": 6, "value": Object { "content": "bar", "isEmpty": false, @@ -5476,7 +5491,7 @@ Object { }, }, "name": "a\\"bc", - "type": 5, + "type": 6, "value": Object { "content": "", "isEmpty": true, @@ -5583,7 +5598,7 @@ Object { }, }, "name": "a'bc", - "type": 5, + "type": 6, "value": Object { "content": "", "isEmpty": true, @@ -5690,7 +5705,7 @@ Object { }, }, "name": "a'", - "isInterpolation": true, - "isStatic": false, + "content": Object { + "content": "''", + "isStatic": false, + "loc": Object { + "end": Object { + "column": 21, + "line": 1, + "offset": 20, + }, + "source": "''", + "start": Object { + "column": 13, + "line": 1, + "offset": 12, + }, + }, + "type": 4, + }, "loc": Object { "end": Object { - "column": 21, + "column": 23, "line": 1, - "offset": 20, + "offset": 22, }, - "source": "''", + "source": "{{''}}", "start": Object { - "column": 13, + "column": 11, "line": 1, - "offset": 12, + "offset": 10, }, }, - "type": 4, + "type": 5, }, ], "codegenNode": undefined, @@ -7317,23 +7347,38 @@ exports[`compiler: parse Errors X_MISSING_INTERPOLATION_END {{}} 1`] = ` Object { "children": Array [ Object { - "content": "", - "isInterpolation": true, - "isStatic": true, + "content": Object { + "content": "", + "isStatic": false, + "loc": Object { + "end": Object { + "column": 3, + "line": 1, + "offset": 2, + }, + "source": "", + "start": Object { + "column": 3, + "line": 1, + "offset": 2, + }, + }, + "type": 4, + }, "loc": Object { "end": Object { - "column": 3, + "column": 5, "line": 1, - "offset": 2, + "offset": 4, }, - "source": "", + "source": "{{}}", "start": Object { - "column": 3, + "column": 1, "line": 1, - "offset": 2, + "offset": 0, }, }, - "type": 4, + "type": 5, }, ], "hoists": Array [], @@ -7458,7 +7503,6 @@ Object { Object { "arg": Object { "content": "class", - "isInterpolation": false, "isStatic": true, "loc": Object { "end": Object { @@ -7477,7 +7521,6 @@ Object { }, "exp": Object { "content": "{ some: condition }", - "isInterpolation": false, "isStatic": false, "loc": Object { "end": Object { @@ -7509,7 +7552,7 @@ Object { }, "modifiers": Array [], "name": "bind", - "type": 6, + "type": 7, }, ], "tag": "div", @@ -7538,7 +7581,6 @@ Object { Object { "arg": Object { "content": "style", - "isInterpolation": false, "isStatic": true, "loc": Object { "end": Object { @@ -7557,7 +7599,6 @@ Object { }, "exp": Object { "content": "{ color: 'red' }", - "isInterpolation": false, "isStatic": false, "loc": Object { "end": Object { @@ -7589,7 +7630,7 @@ Object { }, "modifiers": Array [], "name": "bind", - "type": 6, + "type": 7, }, ], "tag": "p", @@ -7645,7 +7686,6 @@ Object { Object { "arg": Object { "content": "style", - "isInterpolation": false, "isStatic": true, "loc": Object { "end": Object { @@ -7664,7 +7704,6 @@ Object { }, "exp": Object { "content": "{ color: 'red' }", - "isInterpolation": false, "isStatic": false, "loc": Object { "end": Object { @@ -7696,7 +7735,7 @@ Object { }, "modifiers": Array [], "name": "bind", - "type": 6, + "type": 7, }, ], "tag": "p", @@ -7744,7 +7783,6 @@ Object { Object { "arg": Object { "content": "class", - "isInterpolation": false, "isStatic": true, "loc": Object { "end": Object { @@ -7763,7 +7801,6 @@ Object { }, "exp": Object { "content": "{ some: condition }", - "isInterpolation": false, "isStatic": false, "loc": Object { "end": Object { @@ -7795,7 +7832,7 @@ Object { }, "modifiers": Array [], "name": "bind", - "type": 6, + "type": 7, }, ], "tag": "div", diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index edc579f6..c4be89a1 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -3,14 +3,16 @@ import { NodeTypes, RootNode, SourceLocation, - createExpression, + createSimpleExpression, Namespaces, ElementTypes, CallExpression, createObjectExpression, createObjectProperty, createArrayExpression, - ElementNode + ElementNode, + createCompoundExpression, + createInterpolation } from '../src' import { CREATE_VNODE, @@ -93,12 +95,12 @@ describe('compiler: codegen', () => { test('hoists', () => { const root = createRoot({ hoists: [ - createExpression(`hello`, false, mockLoc), + createSimpleExpression(`hello`, false, mockLoc), createObjectExpression( [ createObjectProperty( - createExpression(`id`, true, mockLoc), - createExpression(`foo`, true, mockLoc), + createSimpleExpression(`id`, true, mockLoc), + createSimpleExpression(`foo`, true, mockLoc), mockLoc ) ], @@ -138,7 +140,7 @@ describe('compiler: codegen', () => { test('interpolation', () => { const { code } = generate( createRoot({ - children: [createExpression(`hello`, false, mockLoc, true)] + children: [createInterpolation(`hello`, mockLoc)] }) ) expect(code).toMatch(`return _${TO_STRING}(hello)`) @@ -171,7 +173,7 @@ describe('compiler: codegen', () => { isEmpty: false, loc: mockLoc }, - createExpression(`hello`, false, mockLoc, true), + createInterpolation(`hello`, mockLoc), { type: NodeTypes.COMMENT, content: 'foo', @@ -199,7 +201,7 @@ describe('compiler: codegen', () => { isEmpty: false, loc: mockLoc }, - createExpression(`hello`, false, mockLoc, true), + createInterpolation(`hello`, mockLoc), { type: NodeTypes.COMMENT, content: 'foo', @@ -224,14 +226,13 @@ describe('compiler: codegen', () => { const { code } = generate( createRoot({ children: [ - { - type: NodeTypes.EXPRESSION, - content: 'foo', - isStatic: false, - isInterpolation: true, - loc: mockLoc, - children: [`_ctx.`, createExpression(`foo`, false, mockLoc)] - } + createInterpolation( + createCompoundExpression( + [`_ctx.`, createSimpleExpression(`foo`, false, mockLoc)], + mockLoc + ), + mockLoc + ) ] }) ) @@ -249,7 +250,7 @@ describe('compiler: codegen', () => { branches: [ { type: NodeTypes.IF_BRANCH, - condition: createExpression('foo', false, mockLoc), + condition: createSimpleExpression('foo', false, mockLoc), loc: mockLoc, children: [ { @@ -262,9 +263,9 @@ describe('compiler: codegen', () => { }, { type: NodeTypes.IF_BRANCH, - condition: createExpression('a + b', false, mockLoc), + condition: createSimpleExpression('a + b', false, mockLoc), loc: mockLoc, - children: [createExpression(`bye`, false, mockLoc, true)] + children: [createInterpolation(`bye`, mockLoc)] }, { type: NodeTypes.IF_BRANCH, @@ -302,7 +303,7 @@ describe('compiler: codegen', () => { branches: [ { type: NodeTypes.IF_BRANCH, - condition: createExpression('foo', false, mockLoc), + condition: createSimpleExpression('foo', false, mockLoc), loc: mockLoc, children: [ { @@ -315,9 +316,9 @@ describe('compiler: codegen', () => { }, { type: NodeTypes.IF_BRANCH, - condition: createExpression('a + b', false, mockLoc), + condition: createSimpleExpression('a + b', false, mockLoc), loc: mockLoc, - children: [createExpression(`bye`, false, mockLoc, true)] + children: [createInterpolation(`bye`, mockLoc)] } ] } @@ -340,11 +341,11 @@ describe('compiler: codegen', () => { { type: NodeTypes.FOR, loc: mockLoc, - source: createExpression(`list`, false, mockLoc), - valueAlias: createExpression(`v`, false, mockLoc), - keyAlias: createExpression(`k`, false, mockLoc), - objectIndexAlias: createExpression(`i`, false, mockLoc), - children: [createExpression(`v`, false, mockLoc, true)] + source: createSimpleExpression(`list`, false, mockLoc), + valueAlias: createSimpleExpression(`v`, false, mockLoc), + keyAlias: createSimpleExpression(`k`, false, mockLoc), + objectIndexAlias: createSimpleExpression(`i`, false, mockLoc), + children: [createInterpolation(`v`, mockLoc)] } ] }) @@ -364,11 +365,11 @@ describe('compiler: codegen', () => { { type: NodeTypes.FOR, loc: mockLoc, - source: createExpression(`list`, false, mockLoc), - valueAlias: createExpression(`v`, false, mockLoc), - keyAlias: createExpression(`k`, false, mockLoc), - objectIndexAlias: createExpression(`i`, false, mockLoc), - children: [createExpression(`v`, false, mockLoc, true)] + source: createSimpleExpression(`list`, false, mockLoc), + valueAlias: createSimpleExpression(`v`, false, mockLoc), + keyAlias: createSimpleExpression(`k`, false, mockLoc), + objectIndexAlias: createSimpleExpression(`i`, false, mockLoc), + children: [createInterpolation(`v`, mockLoc)] } ] }), @@ -391,11 +392,11 @@ describe('compiler: codegen', () => { { type: NodeTypes.FOR, loc: mockLoc, - source: createExpression(`list`, false, mockLoc), + source: createSimpleExpression(`list`, false, mockLoc), valueAlias: undefined, - keyAlias: createExpression(`k`, false, mockLoc), - objectIndexAlias: createExpression(`i`, false, mockLoc), - children: [createExpression(`v`, false, mockLoc, true)] + keyAlias: createSimpleExpression(`k`, false, mockLoc), + objectIndexAlias: createSimpleExpression(`i`, false, mockLoc), + children: [createInterpolation(`v`, mockLoc)] } ] }) @@ -415,11 +416,11 @@ describe('compiler: codegen', () => { { type: NodeTypes.FOR, loc: mockLoc, - source: createExpression(`list`, false, mockLoc), - valueAlias: createExpression(`v`, false, mockLoc), + source: createSimpleExpression(`list`, false, mockLoc), + valueAlias: createSimpleExpression(`v`, false, mockLoc), keyAlias: undefined, - objectIndexAlias: createExpression(`i`, false, mockLoc), - children: [createExpression(`v`, false, mockLoc, true)] + objectIndexAlias: createSimpleExpression(`i`, false, mockLoc), + children: [createInterpolation(`v`, mockLoc)] } ] }) @@ -439,11 +440,11 @@ describe('compiler: codegen', () => { { type: NodeTypes.FOR, loc: mockLoc, - source: createExpression(`list`, false, mockLoc), + source: createSimpleExpression(`list`, false, mockLoc), valueAlias: undefined, keyAlias: undefined, - objectIndexAlias: createExpression(`i`, false, mockLoc), - children: [createExpression(`v`, false, mockLoc, true)] + objectIndexAlias: createSimpleExpression(`i`, false, mockLoc), + children: [createInterpolation(`v`, mockLoc)] } ] }) @@ -488,29 +489,26 @@ describe('compiler: codegen', () => { createObjectExpression( [ createObjectProperty( - createExpression(`id`, true, mockLoc), - createExpression(`foo`, true, mockLoc), + createSimpleExpression(`id`, true, mockLoc), + createSimpleExpression(`foo`, true, mockLoc), mockLoc ), createObjectProperty( - createExpression(`prop`, false, mockLoc), - createExpression(`bar`, false, mockLoc), + createSimpleExpression(`prop`, false, mockLoc), + createSimpleExpression(`bar`, false, mockLoc), mockLoc ), // compound expression as computed key createObjectProperty( { - type: NodeTypes.EXPRESSION, - content: ``, + type: NodeTypes.COMPOUND_EXPRESSION, loc: mockLoc, - isStatic: false, - isInterpolation: false, children: [ `foo + `, - createExpression(`bar`, false, mockLoc) + createSimpleExpression(`bar`, false, mockLoc) ] }, - createExpression(`bar`, false, mockLoc), + createSimpleExpression(`bar`, false, mockLoc), mockLoc ) ], @@ -524,8 +522,8 @@ describe('compiler: codegen', () => { [ createObjectProperty( // should quote the key! - createExpression(`some-key`, true, mockLoc), - createExpression(`foo`, true, mockLoc), + createSimpleExpression(`some-key`, true, mockLoc), + createSimpleExpression(`foo`, true, mockLoc), mockLoc ) ], diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index cd99f3b9..08cc2260 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -4,12 +4,12 @@ import { CommentNode, ElementNode, ElementTypes, - ExpressionNode, Namespaces, NodeTypes, Position, TextNode, - AttributeNode + AttributeNode, + InterpolationNode } from '../src/ast' describe('compiler: parse', () => { @@ -290,63 +290,92 @@ describe('compiler: parse', () => { describe('Interpolation', () => { test('simple interpolation', () => { const ast = parse('{{message}}') - const interpolation = ast.children[0] as ExpressionNode + const interpolation = ast.children[0] as InterpolationNode expect(interpolation).toStrictEqual({ - type: NodeTypes.EXPRESSION, - content: 'message', - isStatic: false, - isInterpolation: true, + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `message`, + isStatic: false, + loc: { + start: { offset: 2, line: 1, column: 3 }, + end: { offset: 9, line: 1, column: 10 }, + source: `message` + } + }, loc: { - start: { offset: 2, line: 1, column: 3 }, - end: { offset: 9, line: 1, column: 10 }, - source: 'message' + start: { offset: 0, line: 1, column: 1 }, + end: { offset: 11, line: 1, column: 12 }, + source: '{{message}}' } }) }) test('it can have tag-like notation', () => { const ast = parse('{{ a { const ast = parse('{{ ad }}') - const interpolation1 = ast.children[0] as ExpressionNode - const interpolation2 = ast.children[1] as ExpressionNode + const interpolation1 = ast.children[0] as InterpolationNode + const interpolation2 = ast.children[1] as InterpolationNode expect(interpolation1).toStrictEqual({ - type: NodeTypes.EXPRESSION, - content: 'ad', - isStatic: false, - isInterpolation: true, + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + isStatic: false, + content: 'c>d', + loc: { + start: { offset: 12, line: 1, column: 13 }, + end: { offset: 15, line: 1, column: 16 }, + source: 'c>d' + } + }, loc: { - start: { offset: 12, line: 1, column: 13 }, - end: { offset: 15, line: 1, column: 16 }, - source: 'c>d' + start: { offset: 9, line: 1, column: 10 }, + end: { offset: 18, line: 1, column: 19 }, + source: '{{ c>d }}' } }) }) @@ -354,17 +383,24 @@ describe('compiler: parse', () => { test('it can have tag-like notation (3)', () => { const ast = parse('
{{ "
" }}') const element = ast.children[0] as ElementNode - const interpolation = element.children[0] as ExpressionNode + const interpolation = element.children[0] as InterpolationNode expect(interpolation).toStrictEqual({ - type: NodeTypes.EXPRESSION, - content: '""', - isStatic: false, - isInterpolation: true, + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + isStatic: false, + content: '""', + loc: { + start: { offset: 8, line: 1, column: 9 }, + end: { offset: 16, line: 1, column: 17 }, + source: '""' + } + }, loc: { - start: { offset: 8, line: 1, column: 9 }, - end: { offset: 16, line: 1, column: 17 }, - source: '""' + start: { offset: 5, line: 1, column: 6 }, + end: { offset: 19, line: 1, column: 20 }, + source: '{{ "" }}' } }) }) @@ -889,10 +925,9 @@ describe('compiler: parse', () => { arg: undefined, modifiers: [], exp: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: false, - isInterpolation: false, loc: { start: { offset: 11, line: 1, column: 12 }, end: { offset: 12, line: 1, column: 13 }, @@ -915,10 +950,10 @@ describe('compiler: parse', () => { type: NodeTypes.DIRECTIVE, name: 'on', arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, - isInterpolation: false, + loc: { source: 'click', start: { @@ -987,10 +1022,10 @@ describe('compiler: parse', () => { type: NodeTypes.DIRECTIVE, name: 'on', arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'click', isStatic: true, - isInterpolation: false, + loc: { source: 'click', start: { @@ -1023,10 +1058,10 @@ describe('compiler: parse', () => { type: NodeTypes.DIRECTIVE, name: 'bind', arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isInterpolation: false, + loc: { source: 'a', start: { @@ -1043,10 +1078,10 @@ describe('compiler: parse', () => { }, modifiers: [], exp: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isInterpolation: false, + loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 9, line: 1, column: 10 }, @@ -1069,10 +1104,10 @@ describe('compiler: parse', () => { type: NodeTypes.DIRECTIVE, name: 'bind', arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isInterpolation: false, + loc: { source: 'a', start: { @@ -1089,10 +1124,10 @@ describe('compiler: parse', () => { }, modifiers: ['sync'], exp: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isInterpolation: false, + loc: { start: { offset: 13, line: 1, column: 14 }, end: { offset: 14, line: 1, column: 15 }, @@ -1115,10 +1150,10 @@ describe('compiler: parse', () => { type: NodeTypes.DIRECTIVE, name: 'on', arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isInterpolation: false, + loc: { source: 'a', start: { @@ -1135,10 +1170,10 @@ describe('compiler: parse', () => { }, modifiers: [], exp: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isInterpolation: false, + loc: { start: { offset: 8, line: 1, column: 9 }, end: { offset: 9, line: 1, column: 10 }, @@ -1161,10 +1196,10 @@ describe('compiler: parse', () => { type: NodeTypes.DIRECTIVE, name: 'on', arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'a', isStatic: true, - isInterpolation: false, + loc: { source: 'a', start: { @@ -1181,10 +1216,10 @@ describe('compiler: parse', () => { }, modifiers: ['enter'], exp: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: 'b', isStatic: false, - isInterpolation: false, + loc: { start: { offset: 14, line: 1, column: 15 }, end: { offset: 15, line: 1, column: 16 }, @@ -1313,20 +1348,27 @@ foo offset += foo.loc.source.length expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) + expect(bar.loc.start).toEqual({ line: 2, column: 5, offset }) + const barInner = (bar as InterpolationNode).content offset += 3 - expect(bar.loc.start).toEqual({ line: 2, column: 8, offset }) - offset += bar.loc.source.length - expect(bar.loc.end).toEqual({ line: 2, column: 11, offset }) + expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset }) + offset += barInner.loc.source.length + expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset }) offset += 3 + expect(bar.loc.end).toEqual({ line: 2, column: 14, offset }) expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) offset += but.loc.source.length expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) + expect(baz.loc.start).toEqual({ line: 2, column: 19, offset }) + const bazInner = (baz as InterpolationNode).content offset += 3 - expect(baz.loc.start).toEqual({ line: 2, column: 22, offset }) - offset += baz.loc.source.length - expect(baz.loc.end).toEqual({ line: 2, column: 25, offset }) + expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset }) + offset += bazInner.loc.source.length + expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset }) + offset += 3 + expect(baz.loc.end).toEqual({ line: 2, column: 28, offset }) }) describe('namedCharacterReferences option', () => { diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 138676b9..f35092e5 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -52,12 +52,12 @@ function createStaticObjectMatcher(obj: any) { properties: Object.keys(obj).map(key => ({ type: NodeTypes.JS_PROPERTY, key: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: key, isStatic: true }, value: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: obj[key], isStatic: true } @@ -87,7 +87,7 @@ describe('compiler: element transform', () => { expect(node.arguments).toMatchObject([ `"div"`, { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `_hoisted_1` } ]) @@ -106,7 +106,7 @@ describe('compiler: element transform', () => { expect(node.arguments).toMatchObject([ `"div"`, { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `_hoisted_1` }, [ @@ -148,7 +148,7 @@ describe('compiler: element transform', () => { expect(node.callee).toBe(`_${CREATE_VNODE}`) // should directly use `obj` in props position expect(node.arguments[1]).toMatchObject({ - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` }) }) @@ -167,7 +167,7 @@ describe('compiler: element transform', () => { id: 'foo' }), { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` } ] @@ -185,7 +185,7 @@ describe('compiler: element transform', () => { callee: `_${MERGE_PROPS}`, arguments: [ { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` }, createStaticObjectMatcher({ @@ -209,7 +209,7 @@ describe('compiler: element transform', () => { id: 'foo' }), { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` }, createStaticObjectMatcher({ @@ -237,7 +237,7 @@ describe('compiler: element transform', () => { callee: `_${TO_HANDLERS}`, arguments: [ { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` } ] @@ -267,13 +267,13 @@ describe('compiler: element transform', () => { callee: `_${TO_HANDLERS}`, arguments: [ { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `handlers` } ] }, { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` } ] @@ -363,14 +363,13 @@ describe('compiler: element transform', () => { `_directive_foo`, // exp { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `hello`, - isStatic: false, - isInterpolation: false + isStatic: false }, // arg { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `bar`, isStatic: true } @@ -408,7 +407,7 @@ describe('compiler: element transform', () => { `_directive_bar`, // exp { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `x` } ] @@ -419,14 +418,13 @@ describe('compiler: element transform', () => { `_directive_baz`, // exp { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `y`, - isStatic: false, - isInterpolation: false + isStatic: false }, // arg { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `arg`, isStatic: false }, @@ -437,12 +435,12 @@ describe('compiler: element transform', () => { { type: NodeTypes.JS_PROPERTY, key: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `mod`, isStatic: true }, value: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `true`, isStatic: false } @@ -450,12 +448,12 @@ describe('compiler: element transform', () => { { type: NodeTypes.JS_PROPERTY, key: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `mad`, isStatic: true }, value: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `true`, isStatic: false } @@ -484,7 +482,7 @@ describe('compiler: element transform', () => { { type: NodeTypes.JS_PROPERTY, key: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `onClick`, isStatic: true }, @@ -492,12 +490,12 @@ describe('compiler: element transform', () => { type: NodeTypes.JS_ARRAY_EXPRESSION, elements: [ { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `a`, isStatic: false }, { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `b`, isStatic: false } @@ -524,7 +522,7 @@ describe('compiler: element transform', () => { { type: NodeTypes.JS_PROPERTY, key: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `style`, isStatic: true }, @@ -532,12 +530,12 @@ describe('compiler: element transform', () => { type: NodeTypes.JS_ARRAY_EXPRESSION, elements: [ { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `_hoisted_1`, isStatic: false }, { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `{ color: 'red' }`, isStatic: false } diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 6129cfdd..2c9809a2 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -1,13 +1,13 @@ import { parse, transform, - ExpressionNode, ElementNode, DirectiveNode, NodeTypes, ForNode, CompilerOptions, - IfNode + IfNode, + InterpolationNode } from '../../src' import { transformIf } from '../../src/transforms/vIf' import { transformFor } from '../../src/transforms/vFor' @@ -28,28 +28,58 @@ function parseWithExpressionTransform( describe('compiler: expression transform', () => { test('interpolation (root)', () => { - const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode - expect(node.children).toBeUndefined() - expect(node.content).toBe(`_ctx.foo`) + const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.foo` + }) }) test('interpolation (children)', () => { const el = parseWithExpressionTransform( `
{{ foo }}
` ) as ElementNode - const node = el.children[0] as ExpressionNode - expect(node.children).toBeUndefined() - expect(node.content).toBe(`_ctx.foo`) + const node = el.children[0] as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.foo` + }) + }) + + test('interpolation (complex)', () => { + const el = parseWithExpressionTransform( + `
{{ foo + bar(baz.qux) }}
` + ) as ElementNode + const node = el.children[0] as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `_ctx.foo` }, + ` + `, + { content: `_ctx.bar` }, + `(`, + { content: `_ctx.baz` }, + `.`, + { content: `qux` }, + `)` + ] + }) }) test('directive value', () => { const node = parseWithExpressionTransform( `
` ) as ElementNode - expect((node.props[0] as DirectiveNode).arg!.children).toBeUndefined() + const arg = (node.props[0] as DirectiveNode).arg! + expect(arg).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `arg` + }) const exp = (node.props[0] as DirectiveNode).exp! - expect(exp.children).toBeUndefined() - expect(exp.content).toBe(`_ctx.baz`) + expect(exp).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.baz` + }) }) test('dynamic directive arg', () => { @@ -57,85 +87,113 @@ describe('compiler: expression transform', () => { `
` ) as ElementNode const arg = (node.props[0] as DirectiveNode).arg! + expect(arg).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.arg` + }) const exp = (node.props[0] as DirectiveNode).exp! - expect(arg.children).toBeUndefined() - expect(arg.content).toBe(`_ctx.arg`) - expect(exp.children).toBeUndefined() - expect(exp.content).toBe(`_ctx.baz`) + expect(exp).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.baz` + }) }) test('should prefix complex expressions', () => { const node = parseWithExpressionTransform( `{{ foo(baz + 1, { key: kuz }) }}` - ) as ExpressionNode + ) as InterpolationNode // should parse into compound expression - expect(node.children).toMatchObject([ - { - content: `_ctx.foo`, - loc: { - source: `foo`, - start: { - offset: 3, - line: 1, - column: 4 - }, - end: { - offset: 6, - line: 1, - column: 7 + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { + content: `_ctx.foo`, + loc: { + source: `foo`, + start: { + offset: 3, + line: 1, + column: 4 + }, + end: { + offset: 6, + line: 1, + column: 7 + } } - } - }, - `(`, - { - content: `_ctx.baz`, - loc: { - source: `baz`, - start: { - offset: 7, - line: 1, - column: 8 - }, - end: { - offset: 10, - line: 1, - column: 11 + }, + `(`, + { + content: `_ctx.baz`, + loc: { + source: `baz`, + start: { + offset: 7, + line: 1, + column: 8 + }, + end: { + offset: 10, + line: 1, + column: 11 + } } - } - }, - ` + 1, { key: `, - { - content: `_ctx.kuz`, - loc: { - source: `kuz`, - start: { - offset: 23, - line: 1, - column: 24 - }, - end: { - offset: 26, - line: 1, - column: 27 + }, + ` + 1, { key: `, + { + content: `_ctx.kuz`, + loc: { + source: `kuz`, + start: { + offset: 23, + line: 1, + column: 24 + }, + end: { + offset: 26, + line: 1, + column: 27 + } } - } - }, - ` })` - ]) + }, + ` })` + ] + }) }) test('should prefix v-if condition', () => { const node = parseWithExpressionTransform(`
`) as IfNode - expect(node.branches[0].condition!.children).toBeUndefined() - expect(node.branches[0].condition!.content).toBe(`_ctx.ok`) + expect(node.branches[0].condition).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.ok` + }) }) test('should prefix v-for source', () => { const node = parseWithExpressionTransform( `
` ) as ForNode - expect(node.source.children).toBeUndefined() - expect(node.source.content).toBe(`_ctx.list`) + 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', () => { @@ -143,16 +201,14 @@ describe('compiler: expression transform', () => { `
{{ i }}{{ j }}
` ) as ForNode const div = node.children[0] as ElementNode - - const i = div.children[0] as ExpressionNode - expect(i.type).toBe(NodeTypes.EXPRESSION) - expect(i.content).toBe(`i`) - expect(i.children).toBeUndefined() - - const j = div.children[1] as ExpressionNode - expect(j.type).toBe(NodeTypes.EXPRESSION) - expect(j.children).toBeUndefined() - expect(j.content).toBe(`_ctx.j`) + 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)', () => { @@ -160,32 +216,30 @@ describe('compiler: expression transform', () => { `
{{ i + j + k }}{{ l }}
` ) as ForNode const div = node.children[0] as ElementNode - - const exp = div.children[0] as ExpressionNode - expect(exp.type).toBe(NodeTypes.EXPRESSION) - // parsed for better source-map support - expect(exp.children).toMatchObject([ - { content: `i` }, - ` + `, - { content: `j` }, - ` + `, - { content: `k` } - ]) - - const l = div.children[1] as ExpressionNode - expect(l.type).toBe(NodeTypes.EXPRESSION) - expect(l.children).toBeUndefined() - expect(l.content).toBe(`_ctx.l`) + 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 - const exp = node.children[1] as ExpressionNode - expect(exp.type).toBe(NodeTypes.EXPRESSION) - expect(exp.children).toBeUndefined() - expect(exp.content).toBe(`_ctx.i`) + expect((node.children[1] as InterpolationNode).content).toMatchObject({ + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.i` + }) }) test('nested v-for', () => { @@ -197,123 +251,130 @@ describe('compiler: expression transform', () => { const outerDiv = node.children[0] as ElementNode const innerFor = outerDiv.children[0] as ForNode const innerExp = (innerFor.children[0] as ElementNode) - .children[0] as ExpressionNode - expect(innerExp.type).toBe(NodeTypes.EXPRESSION) - expect(innerExp.children).toMatchObject([ - { content: 'i' }, - ` + `, - { content: `_ctx.j` } - ]) + .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 ExpressionNode - expect(outerExp.type).toBe(NodeTypes.EXPRESSION) - expect(outerExp.content).toBe(`i`) - expect(outerExp.children).toBeUndefined() + 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) }}` - ) as ExpressionNode - expect(node.type).toBe(NodeTypes.EXPRESSION) - expect(node.children).toMatchObject([ - { content: `Math` }, - `.`, - { content: `max` }, - `(1, 2)` - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`] + }) }) test('should not prefix id of a function declaration', () => { const node = parseWithExpressionTransform( `{{ function foo() { return bar } }}` - ) as ExpressionNode - expect(node.type).toBe(NodeTypes.EXPRESSION) - expect(node.children).toMatchObject([ - `function `, - { content: `foo` }, - `() { return `, - { content: `_ctx.bar` }, - ` }` - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `function `, + { content: `foo` }, + `() { return `, + { content: `_ctx.bar` }, + ` }` + ] + }) }) test('should not prefix params of a function expression', () => { const node = parseWithExpressionTransform( `{{ foo => foo + bar }}` - ) as ExpressionNode - expect(node.type).toBe(NodeTypes.EXPRESSION) - expect(node.children).toMatchObject([ - { content: `foo` }, - ` => `, - { content: `foo` }, - ` + `, - { content: `_ctx.bar` } - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `foo` }, + ` => `, + { content: `foo` }, + ` + `, + { content: `_ctx.bar` } + ] + }) }) test('should not prefix an object property key', () => { const node = parseWithExpressionTransform( `{{ { foo: bar } }}` - ) as ExpressionNode - expect(node.type).toBe(NodeTypes.EXPRESSION) - expect(node.children).toMatchObject([ - `{ foo: `, - { content: `_ctx.bar` }, - ` }` - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ foo: `, { content: `_ctx.bar` }, ` }`] + }) }) test('should prefix a computed object property key', () => { const node = parseWithExpressionTransform( `{{ { [foo]: bar } }}` - ) as ExpressionNode - expect(node.type).toBe(NodeTypes.EXPRESSION) - expect(node.children).toMatchObject([ - `{ [`, - { content: `_ctx.foo` }, - `]: `, - { content: `_ctx.bar` }, - ` }` - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `{ [`, + { content: `_ctx.foo` }, + `]: `, + { content: `_ctx.bar` }, + ` }` + ] + }) }) test('should prefix object property shorthand value', () => { - const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode - expect(node.children).toMatchObject([ - `{ foo: `, - { content: `_ctx.foo` }, - ` }` - ]) + const node = parseWithExpressionTransform( + `{{ { foo } }}` + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`{ foo: `, { content: `_ctx.foo` }, ` }`] + }) }) test('should not prefix id in a member expression', () => { const node = parseWithExpressionTransform( `{{ foo.bar.baz }}` - ) as ExpressionNode - expect(node.children).toMatchObject([ - { content: `_ctx.foo` }, - `.`, - { content: `bar` }, - `.`, - { content: `baz` } - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `_ctx.foo` }, + `.`, + { content: `bar` }, + `.`, + { content: `baz` } + ] + }) }) test('should prefix computed id in a member expression', () => { const node = parseWithExpressionTransform( `{{ foo[bar][baz] }}` - ) as ExpressionNode - expect(node.children).toMatchObject([ - { content: `_ctx.foo` }, - `[`, - { content: `_ctx.bar` }, - `][`, - { content: '_ctx.baz' }, - `]` - ]) + ) as InterpolationNode + expect(node.content).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + { content: `_ctx.foo` }, + `[`, + { content: `_ctx.bar` }, + `][`, + { content: '_ctx.baz' }, + `]` + ] + }) }) test('should handle parse error', () => { diff --git a/packages/compiler-core/__tests__/transforms/transformStyle.spec.ts b/packages/compiler-core/__tests__/transforms/transformStyle.spec.ts index 1981277c..aeba73ff 100644 --- a/packages/compiler-core/__tests__/transforms/transformStyle.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformStyle.spec.ts @@ -31,7 +31,7 @@ describe('compiler: style transform', () => { ) expect(root.hoists).toMatchObject([ { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `{"color":"red"}`, isStatic: false } @@ -40,12 +40,12 @@ describe('compiler: style transform', () => { type: NodeTypes.DIRECTIVE, name: `bind`, arg: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `style`, isStatic: true }, exp: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `_hoisted_1`, isStatic: false } @@ -64,12 +64,12 @@ describe('compiler: style transform', () => { properties: [ { key: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `style`, isStatic: true }, value: { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: `_hoisted_1`, isStatic: false } diff --git a/packages/compiler-core/__tests__/transforms/vBind.spec.ts b/packages/compiler-core/__tests__/transforms/vBind.spec.ts index fb12b20a..5c3f252c 100644 --- a/packages/compiler-core/__tests__/transforms/vBind.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vBind.spec.ts @@ -8,6 +8,8 @@ import { } from '../../src' import { transformBind } from '../../src/transforms/vBind' import { transformElement } from '../../src/transforms/transformElement' +import { CAMELIZE } from '../../src/runtimeConstants' +import { transformExpression } from '../../src/transforms/transformExpression' function parseWithVBind( template: string, @@ -15,7 +17,10 @@ function parseWithVBind( ): ElementNode { const ast = parse(template) transform(ast, { - nodeTransforms: [transformElement], + nodeTransforms: [ + ...(options.prefixIdentifiers ? [transformExpression] : []), + transformElement + ], directiveTransforms: { bind: transformBind }, @@ -117,4 +122,42 @@ describe('compiler: transform v-bind', () => { } }) }) + + test('.camel modifier w/ dynamic arg', () => { + const node = parseWithVBind(`
`) + const props = node.codegenNode!.arguments[1] as ObjectExpression + expect(props.properties[0]).toMatchObject({ + key: { + content: `_${CAMELIZE}(foo)`, + isStatic: false + }, + value: { + content: `id`, + isStatic: false + } + }) + }) + + test('.camel modifier w/ dynamic arg + prefixIdentifiers', () => { + const node = parseWithVBind(`
`, { + prefixIdentifiers: true + }) + const props = node.codegenNode!.arguments[1] as ObjectExpression + expect(props.properties[0]).toMatchObject({ + key: { + children: [ + `${CAMELIZE}(`, + { content: `_ctx.foo` }, + `(`, + { content: `_ctx.bar` }, + `)`, + `)` + ] + }, + value: { + content: `_ctx.id`, + isStatic: false + } + }) + }) }) diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 5dfcc068..255e6604 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -1,7 +1,7 @@ import { parse } from '../../src/parse' import { transform } from '../../src/transform' import { transformFor } from '../../src/transforms/vFor' -import { ForNode, NodeTypes } from '../../src/ast' +import { ForNode, NodeTypes, SimpleExpressionNode } from '../../src/ast' import { ErrorCodes } from '../../src/errors' import { CompilerOptions } from '../../src' @@ -24,7 +24,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('index') - expect(forNode.source.content).toBe('5') + expect((forNode.source as SimpleExpressionNode).content).toBe('5') }) test('value', () => { @@ -32,7 +32,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('object de-structured value', () => { @@ -42,7 +42,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('{ id, value }') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('array de-structured value', () => { @@ -52,7 +52,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('[ id, value ]') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('value and key', () => { @@ -63,7 +63,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias!.content).toBe('key') expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('value, key and index', () => { @@ -75,7 +75,7 @@ describe('compiler: transform v-for', () => { expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.valueAlias!.content).toBe('value') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('skipped key', () => { @@ -86,7 +86,7 @@ describe('compiler: transform v-for', () => { expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.valueAlias!.content).toBe('value') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('skipped value and key', () => { @@ -95,7 +95,7 @@ describe('compiler: transform v-for', () => { expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.valueAlias).toBeUndefined() - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed value', () => { @@ -103,7 +103,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed value and key', () => { @@ -112,7 +112,7 @@ describe('compiler: transform v-for', () => { expect(forNode.keyAlias!.content).toBe('key') expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed value, key and index', () => { @@ -124,7 +124,7 @@ describe('compiler: transform v-for', () => { expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.valueAlias!.content).toBe('value') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed skipped key', () => { @@ -135,7 +135,7 @@ describe('compiler: transform v-for', () => { expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.valueAlias!.content).toBe('value') - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('unbracketed skipped value and key', () => { @@ -144,7 +144,7 @@ describe('compiler: transform v-for', () => { expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.valueAlias).toBeUndefined() - expect(forNode.source.content).toBe('items') + expect((forNode.source as SimpleExpressionNode).content).toBe('items') }) test('missing expression', () => { @@ -223,7 +223,7 @@ describe('compiler: transform v-for', () => { ) const itemsOffset = source.indexOf('items') - expect(forNode.source.content).toBe('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) @@ -248,7 +248,7 @@ describe('compiler: transform v-for', () => { ) const itemsOffset = source.indexOf('items') - expect(forNode.source.content).toBe('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) @@ -273,7 +273,7 @@ describe('compiler: transform v-for', () => { ) const itemsOffset = source.indexOf('items') - expect(forNode.source.content).toBe('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) @@ -318,7 +318,7 @@ describe('compiler: transform v-for', () => { ) const itemsOffset = source.indexOf('items') - expect(forNode.source.content).toBe('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) @@ -353,7 +353,7 @@ describe('compiler: transform v-for', () => { ) const itemsOffset = source.indexOf('items') - expect(forNode.source.content).toBe('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) diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index a9da4688..dcac2812 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -6,7 +6,8 @@ import { NodeTypes, ElementNode, TextNode, - CommentNode + CommentNode, + SimpleExpressionNode } from '../../src/ast' import { ErrorCodes } from '../../src/errors' import { CompilerOptions } from '../../src' @@ -30,7 +31,9 @@ describe('compiler: transform v-if', () => { const node = parseWithIfTransform(`
`) expect(node.type).toBe(NodeTypes.IF) expect(node.branches.length).toBe(1) - expect(node.branches[0].condition!.content).toBe(`ok`) + expect((node.branches[0].condition as SimpleExpressionNode).content).toBe( + `ok` + ) expect(node.branches[0].children.length).toBe(1) expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) @@ -42,7 +45,9 @@ describe('compiler: transform v-if', () => { ) expect(node.type).toBe(NodeTypes.IF) expect(node.branches.length).toBe(1) - expect(node.branches[0].condition!.content).toBe(`ok`) + expect((node.branches[0].condition as SimpleExpressionNode).content).toBe( + `ok` + ) expect(node.branches[0].children.length).toBe(3) expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) @@ -58,7 +63,7 @@ describe('compiler: transform v-if', () => { expect(node.branches.length).toBe(2) const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) + expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`) expect(b1.children.length).toBe(1) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect((b1.children[0] as ElementNode).tag).toBe(`div`) @@ -76,13 +81,13 @@ describe('compiler: transform v-if', () => { expect(node.branches.length).toBe(2) const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) + expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`) expect(b1.children.length).toBe(1) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect((b1.children[0] as ElementNode).tag).toBe(`div`) const b2 = node.branches[1] - expect(b2.condition!.content).toBe(`orNot`) + expect((b2.condition as SimpleExpressionNode).content).toBe(`orNot`) expect(b2.children.length).toBe(1) expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) expect((b2.children[0] as ElementNode).tag).toBe(`p`) @@ -96,13 +101,13 @@ describe('compiler: transform v-if', () => { expect(node.branches.length).toBe(3) const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) + expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`) expect(b1.children.length).toBe(1) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect((b1.children[0] as ElementNode).tag).toBe(`div`) const b2 = node.branches[1] - expect(b2.condition!.content).toBe(`orNot`) + expect((b2.condition as SimpleExpressionNode).content).toBe(`orNot`) expect(b2.children.length).toBe(1) expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) expect((b2.children[0] as ElementNode).tag).toBe(`p`) @@ -126,13 +131,13 @@ describe('compiler: transform v-if', () => { expect(node.branches.length).toBe(3) const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) + expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`) expect(b1.children.length).toBe(1) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect((b1.children[0] as ElementNode).tag).toBe(`div`) const b2 = node.branches[1] - expect(b2.condition!.content).toBe(`orNot`) + expect((b2.condition as SimpleExpressionNode).content).toBe(`orNot`) expect(b2.children.length).toBe(2) expect(b2.children[0].type).toBe(NodeTypes.COMMENT) expect((b2.children[0] as CommentNode).content).toBe(`foo`) diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts index b2b44e7b..c0cffa66 100644 --- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts @@ -4,7 +4,8 @@ import { ElementNode, ObjectExpression, CompilerOptions, - ErrorCodes + ErrorCodes, + NodeTypes } from '../../src' import { transformOn } from '../../src/transforms/vOn' import { transformElement } from '../../src/transforms/transformElement' @@ -76,10 +77,11 @@ describe('compiler: transform v-on', () => { const props = node.codegenNode!.arguments[1] as ObjectExpression expect(props.properties[0]).toMatchObject({ key: { - isStatic: false, - children: [`"on" + `, { content: `event` }] + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`"on" + (`, { content: `event` }, `)`] }, value: { + type: NodeTypes.SIMPLE_EXPRESSION, content: `handler`, isStatic: false } @@ -93,10 +95,36 @@ describe('compiler: transform v-on', () => { const props = node.codegenNode!.arguments[1] as ObjectExpression expect(props.properties[0]).toMatchObject({ key: { - isStatic: false, - children: [`"on" + `, { content: `_ctx.event` }] + type: NodeTypes.COMPOUND_EXPRESSION, + children: [`"on" + (`, { content: `_ctx.event` }, `)`] }, value: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `_ctx.handler`, + isStatic: false + } + }) + }) + + test('dynamic arg with complex exp prefixing', () => { + const node = parseWithVOn(`
`, { + prefixIdentifiers: true + }) + const props = node.codegenNode!.arguments[1] as ObjectExpression + expect(props.properties[0]).toMatchObject({ + key: { + type: NodeTypes.COMPOUND_EXPRESSION, + children: [ + `"on" + (`, + { content: `_ctx.event` }, + `(`, + { content: `_ctx.foo` }, + `)`, + `)` + ] + }, + value: { + type: NodeTypes.SIMPLE_EXPRESSION, content: `_ctx.handler`, isStatic: false } diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index d9b3cbc4..90817339 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -1,3 +1,5 @@ +import { isString } from '@vue/shared' + // Vue template is a platform-agnostic superset of HTML (syntax only). // More namespaces like SVG and MathML are declared by platform specific // compilers. @@ -12,7 +14,8 @@ export const enum NodeTypes { ELEMENT, TEXT, COMMENT, - EXPRESSION, + SIMPLE_EXPRESSION, + INTERPOLATION, ATTRIBUTE, DIRECTIVE, // containers @@ -55,9 +58,11 @@ export interface Position { export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode +export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode + export type ChildNode = | ElementNode - | ExpressionNode + | InterpolationNode | TextNode | CommentNode | IfNode @@ -107,12 +112,21 @@ export interface DirectiveNode extends Node { modifiers: string[] } -export interface ExpressionNode extends Node { - type: NodeTypes.EXPRESSION +export interface SimpleExpressionNode extends Node { + type: NodeTypes.SIMPLE_EXPRESSION content: string isStatic: boolean - isInterpolation: boolean - children?: (ExpressionNode | string)[] +} + +export interface InterpolationNode extends Node { + type: NodeTypes.INTERPOLATION + content: ExpressionNode +} + +// always dynamic +export interface CompoundExpressionNode extends Node { + type: NodeTypes.COMPOUND_EXPRESSION + children: (SimpleExpressionNode | string)[] } export interface IfNode extends Node { @@ -129,9 +143,9 @@ export interface IfBranchNode extends Node { export interface ForNode extends Node { type: NodeTypes.FOR source: ExpressionNode - valueAlias: ExpressionNode | undefined - keyAlias: ExpressionNode | undefined - objectIndexAlias: ExpressionNode | undefined + valueAlias: SimpleExpressionNode | undefined + keyAlias: SimpleExpressionNode | undefined + objectIndexAlias: SimpleExpressionNode | undefined children: ChildNode[] } @@ -190,7 +204,7 @@ export function createObjectExpression( export function createObjectProperty( key: ExpressionNode, - value: ExpressionNode, + value: JSChildNode, loc: SourceLocation ): Property { return { @@ -201,18 +215,40 @@ export function createObjectProperty( } } -export function createExpression( +export function createSimpleExpression( content: string, isStatic: boolean, - loc: SourceLocation, - isInterpolation = false -): ExpressionNode { + loc: SourceLocation +): SimpleExpressionNode { return { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, loc, content, - isStatic, - isInterpolation + isStatic + } +} + +export function createInterpolation( + content: string | CompoundExpressionNode, + loc: SourceLocation +): InterpolationNode { + return { + type: NodeTypes.INTERPOLATION, + loc, + content: isString(content) + ? createSimpleExpression(content, false, loc) + : content + } +} + +export function createCompoundExpression( + children: CompoundExpressionNode['children'], + loc: SourceLocation +): CompoundExpressionNode { + return { + type: NodeTypes.COMPOUND_EXPRESSION, + loc, + children } } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 8db31374..6dc09119 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -14,7 +14,10 @@ import { ObjectExpression, IfBranchNode, SourceLocation, - Position + Position, + InterpolationNode, + CompoundExpressionNode, + SimpleExpressionNode } from './ast' import { SourceMapGenerator, RawSourceMap } from 'source-map' import { @@ -110,11 +113,7 @@ function createCodegenContext( if (!__BROWSER__ && context.map) { if (node) { let name - if ( - node.type === NodeTypes.EXPRESSION && - !node.children && - !node.isStatic - ) { + if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) { const content = node.content.replace(/^_ctx\./, '') if (content !== node.content && isSimpleIdentifier(content)) { name = content @@ -263,7 +262,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) { // This will generate a single vnode call if: // - The target position explicitly allows a single node (root, if, for) -// - The list has length === 1, AND The only child is a text or expression. +// - The list has length === 1, AND The only child is a text, expression or comment. function genChildren( children: ChildNode[], context: CodegenContext, @@ -277,7 +276,8 @@ function genChildren( children.length === 1 && (allowSingle || child.type === NodeTypes.TEXT || - child.type == NodeTypes.EXPRESSION) + child.type === NodeTypes.INTERPOLATION || + child.type === NodeTypes.COMMENT) ) { genNode(child, context) } else { @@ -331,9 +331,15 @@ function genNode(node: CodegenNode, context: CodegenContext) { case NodeTypes.TEXT: genText(node, context) break - case NodeTypes.EXPRESSION: + case NodeTypes.SIMPLE_EXPRESSION: genExpression(node, context) break + case NodeTypes.INTERPOLATION: + genInterpolation(node, context) + break + case NodeTypes.COMPOUND_EXPRESSION: + genCompoundExpression(node, context) + break case NodeTypes.COMMENT: genComment(node, context) break @@ -369,23 +375,36 @@ function genElement(node: ElementNode, context: CodegenContext) { genCallExpression(node.codegenNode!, context, false) } -function genText(node: TextNode | ExpressionNode, context: CodegenContext) { +function genText( + node: TextNode | SimpleExpressionNode, + context: CodegenContext +) { context.push(JSON.stringify(node.content), node) } -function genExpression(node: ExpressionNode, context: CodegenContext) { +function genExpression(node: SimpleExpressionNode, context: CodegenContext) { + const { content, isStatic } = node + context.push(isStatic ? JSON.stringify(content) : content, node) +} + +function genInterpolation(node: InterpolationNode, context: CodegenContext) { const { push, helper } = context - const { content, children, isStatic, isInterpolation } = node - if (isInterpolation) { - push(`${helper(TO_STRING)}(`) - } - if (children) { - genCompoundExpression(node, context) - } else { - push(isStatic ? JSON.stringify(content) : content, node) - } - if (isInterpolation) { - push(`)`) + push(`${helper(TO_STRING)}(`) + genNode(node.content, context) + push(`)`) +} + +function genCompoundExpression( + node: CompoundExpressionNode, + context: CodegenContext +) { + for (let i = 0; i < node.children!.length; i++) { + const child = node.children![i] + if (isString(child)) { + context.push(child) + } else { + genExpression(child, context) + } } } @@ -394,28 +413,18 @@ function genExpressionAsPropertyKey( context: CodegenContext ) { const { push } = context - const { content, children, isStatic } = node - if (children) { + if (node.type === NodeTypes.COMPOUND_EXPRESSION) { push(`[`) genCompoundExpression(node, context) push(`]`) - } else if (isStatic) { + } else if (node.isStatic) { // only quote keys if necessary - const text = isSimpleIdentifier(content) ? content : JSON.stringify(content) + const text = isSimpleIdentifier(node.content) + ? node.content + : JSON.stringify(node.content) push(text, node) } else { - push(`[${content}]`, node) - } -} - -function genCompoundExpression(node: ExpressionNode, context: CodegenContext) { - for (let i = 0; i < node.children!.length; i++) { - const child = node.children![i] - if (isString(child)) { - context.push(child) - } else { - genExpression(child, context) - } + push(`[${node.content}]`, node) } } @@ -445,10 +454,14 @@ function genIfBranch( if (condition) { // v-if or v-else-if const { push, indent, deindent, newline } = context - const needsQuote = !isSimpleIdentifier(condition.content) - needsQuote && push(`(`) - genExpression(condition, context) - needsQuote && push(`)`) + if (condition.type === NodeTypes.SIMPLE_EXPRESSION) { + const needsQuote = !isSimpleIdentifier(condition.content) + needsQuote && push(`(`) + genExpression(condition, context) + needsQuote && push(`)`) + } else { + genCompoundExpression(condition, context) + } indent() context.indentLevel++ push(`? `) @@ -473,7 +486,7 @@ function genFor(node: ForNode, context: CodegenContext) { const { push, helper, indent, deindent } = context const { source, keyAlias, valueAlias, objectIndexAlias, children } = node push(`${helper(RENDER_LIST)}(`, node, true) - genExpression(source, context) + genNode(source, context) push(`, (`) if (valueAlias) { genExpression(valueAlias, context) diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index c077c1a8..28a982c7 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -128,7 +128,8 @@ export const errorMessages: { [code: number]: string } = { [ErrorCodes.X_MISSING_INTERPOLATION_END]: 'Interpolation end sign was not found.', [ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]: - 'End bracket for dynamic directive argument was not found.', + 'End bracket for dynamic directive argument was not found. ' + + 'Note that dynamic directive argument connot contain spaces.', // transform errors [ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`, diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 6dfde5a7..7343950c 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -23,7 +23,8 @@ import { RootNode, SourceLocation, TextNode, - ChildNode + ChildNode, + InterpolationNode } from './ast' export interface ParserOptions { @@ -122,7 +123,7 @@ function parseChildren( while (!isEnd(context, mode, ancestors)) { __DEV__ && assert(context.source.length > 0) const s = context.source - let node: any = null + let node: ChildNode | ChildNode[] | undefined = undefined if (startsWith(s, context.options.delimiters[0])) { // '{{' @@ -529,10 +530,9 @@ function parseAttribute( } arg = { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content, isStatic, - isInterpolation: false, loc } } @@ -555,10 +555,9 @@ function parseAttribute( ? 'on' : 'slot'), exp: value && { - type: NodeTypes.EXPRESSION, + type: NodeTypes.SIMPLE_EXPRESSION, content: value.content, isStatic: false, - isInterpolation: false, loc: value.loc }, arg, @@ -633,7 +632,7 @@ function parseAttributeValue( function parseInterpolation( context: ParserContext, mode: TextModes -): ExpressionNode | undefined { +): InterpolationNode | undefined { const [open, close] = context.options.delimiters __DEV__ && assert(startsWith(context.source, open)) @@ -643,28 +642,32 @@ function parseInterpolation( return undefined } - advanceBy(context, open.length) const start = getCursor(context) - const end = getCursor(context) + advanceBy(context, open.length) + const innerStart = getCursor(context) + const innerEnd = getCursor(context) const rawContentLength = closeIndex - open.length const rawContent = context.source.slice(0, rawContentLength) const preTrimContent = parseTextData(context, rawContentLength, mode) const content = preTrimContent.trim() const startOffset = preTrimContent.indexOf(content) if (startOffset > 0) { - advancePositionWithMutation(start, rawContent, startOffset) + advancePositionWithMutation(innerStart, rawContent, startOffset) } const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset) - advancePositionWithMutation(end, rawContent, endOffset) + advancePositionWithMutation(innerEnd, rawContent, endOffset) advanceBy(context, close.length) return { - type: NodeTypes.EXPRESSION, - content, - loc: getSelection(context, start, end), - isStatic: content === '', - isInterpolation: true + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + isStatic: false, + content, + loc: getSelection(context, innerStart, innerEnd) + }, + loc: getSelection(context, start) } } diff --git a/packages/compiler-core/src/runtimeConstants.ts b/packages/compiler-core/src/runtimeConstants.ts index a253b096..6510963f 100644 --- a/packages/compiler-core/src/runtimeConstants.ts +++ b/packages/compiler-core/src/runtimeConstants.ts @@ -13,3 +13,4 @@ export const RENDER_LIST = `renderList` export const TO_STRING = `toString` export const MERGE_PROPS = `mergeProps` export const TO_HANDLERS = `toHandlers` +export const CAMELIZE = `camelize` diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index a7714b5c..d5a45776 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -7,8 +7,9 @@ import { DirectiveNode, Property, ExpressionNode, - createExpression, - JSChildNode + createSimpleExpression, + JSChildNode, + SimpleExpressionNode } from './ast' import { isString, isArray } from '@vue/shared' import { CompilerError, defaultOnError } from './errors' @@ -63,8 +64,8 @@ export interface TransformContext extends Required { replaceNode(node: ChildNode): void removeNode(node?: ChildNode): void onNodeRemoved: () => void - addIdentifier(exp: ExpressionNode): void - removeIdentifier(exp: ExpressionNode): void + addIdentifier(exp: SimpleExpressionNode): void + removeIdentifier(exp: SimpleExpressionNode): void hoist(exp: JSChildNode): ExpressionNode } @@ -138,7 +139,7 @@ function createTransformContext( }, hoist(exp) { context.hoists.push(exp) - return createExpression( + return createSimpleExpression( `_hoisted_${context.hoists.length}`, false, exp.loc @@ -205,11 +206,9 @@ export function traverseNode(node: ChildNode, context: TransformContext) { // comment nodes with `createVNode` context.helper(COMMENT) break - case NodeTypes.EXPRESSION: + case NodeTypes.INTERPOLATION: // no need to traverse, but we need to inject toString helper - if (node.isInterpolation) { - context.helper(TO_STRING) - } + context.helper(TO_STRING) break // for container types, further traverse downwards diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 0d5cdd04..90245342 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -11,7 +11,7 @@ import { createCallExpression, createArrayExpression, createObjectProperty, - createExpression, + createSimpleExpression, createObjectExpression, Property } from '../ast' @@ -122,8 +122,12 @@ function buildProps( const { loc, name, value } = prop properties.push( createObjectProperty( - createExpression(name, true, getInnerRange(loc, 0, name.length)), - createExpression( + createSimpleExpression( + name, + true, + getInnerRange(loc, 0, name.length) + ), + createSimpleExpression( value ? value.content : '', true, value ? value.loc : loc @@ -236,8 +240,8 @@ function dedupeProperties(properties: Property[]): Property[] { const deduped: Property[] = [] for (let i = 0; i < properties.length; i++) { const prop = properties[i] - // dynamic key named are always allowed - if (!prop.key.isStatic) { + // dynamic keys are always allowed + if (prop.key.type === NodeTypes.COMPOUND_EXPRESSION || !prop.key.isStatic) { deduped.push(prop) continue } @@ -273,16 +277,7 @@ function mergeAsArray(existing: Property, incoming: Property) { // :class="expression" class="string" // -> class: expression + "string" function mergeClasses(existing: Property, incoming: Property) { - const e = existing.value as ExpressionNode - const children = - e.children || - (e.children = [ - { - ...e, - children: undefined - } - ]) - children.push(` + " " + `, incoming.value as ExpressionNode) + // TODO } function createDirectiveArgs( @@ -305,8 +300,8 @@ function createDirectiveArgs( createObjectExpression( dir.modifiers.map(modifier => createObjectProperty( - createExpression(modifier, true, loc), - createExpression(`true`, false, loc), + createSimpleExpression(modifier, true, loc), + createSimpleExpression(`true`, false, loc), loc ) ), diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 90aedf06..75558070 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -11,26 +11,39 @@ import { parseScript } from 'meriyah' import { walk } from 'estree-walker' import { NodeTransform, TransformContext } from '../transform' -import { NodeTypes, createExpression, ExpressionNode } from '../ast' +import { + NodeTypes, + createSimpleExpression, + ExpressionNode, + SimpleExpressionNode, + CompoundExpressionNode, + createCompoundExpression +} from '../ast' import { Node, Function, Identifier, Property } from 'estree' import { advancePositionWithClone } from '../utils' + export const transformExpression: NodeTransform = (node, context) => { - if (node.type === NodeTypes.EXPRESSION && !node.isStatic) { - processExpression(node, context) + if (node.type === NodeTypes.INTERPOLATION) { + node.content = processExpression( + node.content as SimpleExpressionNode, + context + ) } else if (node.type === NodeTypes.ELEMENT) { // handle directives on element for (let i = 0; i < node.props.length; i++) { const prop = node.props[i] if (prop.type === NodeTypes.DIRECTIVE) { - if (prop.exp) { - processExpression(prop.exp, context) + const exp = prop.exp as SimpleExpressionNode | undefined + const arg = prop.arg as SimpleExpressionNode | undefined + if (exp) { + prop.exp = processExpression(exp, context) } - if (prop.arg && !prop.arg.isStatic) { + if (arg && !arg.isStatic) { if (prop.name === 'class') { // TODO special expression optimization for classes - processExpression(prop.arg, context) + prop.arg = processExpression(arg, context) } else { - processExpression(prop.arg, context) + prop.arg = processExpression(arg, context) } } } @@ -60,32 +73,33 @@ interface PrefixMeta { // always be used with a leading !__BROWSER__ check so that it can be // tree-shaken from the browser build. export function processExpression( - node: ExpressionNode, + node: SimpleExpressionNode, context: TransformContext -) { +): ExpressionNode { if (!context.prefixIdentifiers) { - return + return node } - // lazy require dependencies so that they don't end up in rollup's dep graph - // and thus can be tree-shaken in browser builds. - const parseScript = - _parseScript || (_parseScript = require('meriyah').parseScript) - const walk = _walk || (_walk = require('estree-walker').walk) // fast path if expression is a simple identifier. if (simpleIdRE.test(node.content)) { if (!context.identifiers[node.content]) { node.content = `_ctx.${node.content}` } - return + return node } + // lazy require dependencies so that they don't end up in rollup's dep graph + // and thus can be tree-shaken in browser builds. + const parseScript = + _parseScript || (_parseScript = require('meriyah').parseScript) + const walk = _walk || (_walk = require('estree-walker').walk) + let ast try { ast = parseScript(`(${node.content})`, { ranges: true }) as any } catch (e) { context.onError(e) - return + return node } const ids: (Identifier & PrefixMeta)[] = [] @@ -145,7 +159,7 @@ export function processExpression( // an ExpressionNode has the `.children` property, it will be used instead of // `.content`. const full = node.content - const children: ExpressionNode['children'] = [] + const children: CompoundExpressionNode['children'] = [] ids.sort((a, b) => a.start - b.start) ids.forEach((id, i) => { const last = ids[i - 1] as any @@ -155,7 +169,7 @@ export function processExpression( } const source = full.slice(id.start - 1, id.end - 1) children.push( - createExpression(id.name, false, { + createSimpleExpression(id.name, false, { source, start: advancePositionWithClone(node.loc.start, source, id.start - 1), end: advancePositionWithClone(node.loc.start, source, id.end - 1) @@ -167,10 +181,9 @@ export function processExpression( }) if (children.length) { - // mark it empty so that it's more noticeable in case another transform or - // codegen forget to handle `.children` first. - node.content = __DEV__ ? `[[REMOVED]]` : `` - node.children = children + return createCompoundExpression(children, node.loc) + } else { + return node } } diff --git a/packages/compiler-core/src/transforms/transformStyle.ts b/packages/compiler-core/src/transforms/transformStyle.ts index 297b5e72..47ce0b2a 100644 --- a/packages/compiler-core/src/transforms/transformStyle.ts +++ b/packages/compiler-core/src/transforms/transformStyle.ts @@ -1,5 +1,5 @@ import { NodeTransform } from '../transform' -import { NodeTypes, createExpression } from '../ast' +import { NodeTypes, createSimpleExpression } from '../ast' // Parse inline CSS strings for static style attributes into an object. // This is a NodeTransform since it works on the static `style` attribute and @@ -13,11 +13,11 @@ export const transformStyle: NodeTransform = (node, context) => { if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) { // replace p with an expression node const parsed = JSON.stringify(parseInlineCSS(p.value.content)) - const exp = context.hoist(createExpression(parsed, false, p.loc)) + const exp = context.hoist(createSimpleExpression(parsed, false, p.loc)) node.props[i] = { type: NodeTypes.DIRECTIVE, name: `bind`, - arg: createExpression(`style`, true, p.loc), + arg: createSimpleExpression(`style`, true, p.loc), exp, modifiers: [], loc: p.loc diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts index acfff0f4..b8a71c4b 100644 --- a/packages/compiler-core/src/transforms/vBind.ts +++ b/packages/compiler-core/src/transforms/vBind.ts @@ -1,27 +1,36 @@ import { DirectiveTransform } from '../transform' -import { createObjectProperty, createExpression } from '../ast' +import { createObjectProperty, createSimpleExpression, NodeTypes } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { camelize } from '@vue/shared' +import { CAMELIZE } from '../runtimeConstants' // v-bind without arg is handled directly in ./element.ts due to it affecting // codegen for the entire props object. This transform here is only for v-bind // *with* args. -export const transformBind: DirectiveTransform = ( - { exp, arg, modifiers, loc }, - context -) => { +export const transformBind: DirectiveTransform = (dir, context) => { + const { exp, modifiers, loc } = dir + const arg = dir.arg! if (!exp) { context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc)) } // .prop is no longer necessary due to new patch behavior // .sync is replced by v-model:arg if (modifiers.includes('camel')) { - arg!.content = camelize(arg!.content) + if (arg.type === NodeTypes.SIMPLE_EXPRESSION) { + if (arg.isStatic) { + arg.content = camelize(arg.content) + } else { + arg.content = `${context.helper(CAMELIZE)}(${arg.content})` + } + } else { + arg.children.unshift(`${context.helper(CAMELIZE)}(`) + arg.children.push(`)`) + } } return { props: createObjectProperty( arg!, - exp || createExpression('', true, loc), + exp || createSimpleExpression('', true, loc), loc ), needRuntime: false diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 8e436dc4..a045f227 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -5,8 +5,9 @@ import { import { NodeTypes, ExpressionNode, - createExpression, - SourceLocation + createSimpleExpression, + SourceLocation, + SimpleExpressionNode } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { getInnerRange } from '../utils' @@ -17,7 +18,12 @@ export const transformFor = createStructuralDirectiveTransform( 'for', (node, dir, context) => { if (dir.exp) { - const parseResult = parseForExpression(dir.exp, context) + const parseResult = parseForExpression( + // can only be simple expression because vFor transform is applied + // before expression transform. + dir.exp as SimpleExpressionNode, + context + ) if (parseResult) { context.helper(RENDER_LIST) @@ -66,29 +72,33 @@ const stripParensRE = /^\(|\)$/g interface ForParseResult { source: ExpressionNode - value: ExpressionNode | undefined - key: ExpressionNode | undefined - index: ExpressionNode | undefined + value: SimpleExpressionNode | undefined + key: SimpleExpressionNode | undefined + index: SimpleExpressionNode | undefined } function parseForExpression( - input: ExpressionNode, + input: SimpleExpressionNode, context: TransformContext ): ForParseResult | null { const loc = input.loc - const source = input.content - const inMatch = source.match(forAliasRE) + const exp = input.content + const inMatch = exp.match(forAliasRE) if (!inMatch) return null 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: createAliasExpression( - loc, - RHS.trim(), - source.indexOf(RHS, LHS.length), - context, - context.prefixIdentifiers - ), + source, value: undefined, key: undefined, index: undefined @@ -106,11 +116,8 @@ function parseForExpression( const keyContent = iteratorMatch[1].trim() let keyOffset: number | undefined if (keyContent) { - keyOffset = source.indexOf( - keyContent, - trimmedOffset + valueContent.length - ) - result.key = createAliasExpression(loc, keyContent, keyOffset, context) + keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length) + result.key = createAliasExpression(loc, keyContent, keyOffset) } if (iteratorMatch[2]) { @@ -120,25 +127,19 @@ function parseForExpression( result.index = createAliasExpression( loc, indexContent, - source.indexOf( + exp.indexOf( indexContent, result.key ? keyOffset! + keyContent.length : trimmedOffset + valueContent.length - ), - context + ) ) } } } if (valueContent) { - result.value = createAliasExpression( - loc, - valueContent, - trimmedOffset, - context - ) + result.value = createAliasExpression(loc, valueContent, trimmedOffset) } return result @@ -147,17 +148,11 @@ function parseForExpression( function createAliasExpression( range: SourceLocation, content: string, - offset: number, - context: TransformContext, - process: boolean = false -): ExpressionNode { - const exp = createExpression( + offset: number +): SimpleExpressionNode { + return createSimpleExpression( content, false, getInnerRange(range, offset, content.length) ) - if (!__BROWSER__ && process) { - processExpression(exp, context) - } - return exp } diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 1d8fe08a..96d340f3 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -7,7 +7,8 @@ import { ElementTypes, ElementNode, DirectiveNode, - IfBranchNode + IfBranchNode, + SimpleExpressionNode } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { processExpression } from './transformExpression' @@ -16,7 +17,9 @@ export const transformIf = createStructuralDirectiveTransform( /^(if|else|else-if)$/, (node, dir, context) => { if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) { - processExpression(dir.exp, context) + // dir.exp can only be simple expression because vIf transform is applied + // before expression transform. + processExpression(dir.exp as SimpleExpressionNode, context) } if (dir.name === 'if') { context.replaceNode({ diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index 019bd6ed..9cc2f63c 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -1,37 +1,46 @@ import { DirectiveTransform } from '../transform' -import { createObjectProperty, createExpression, ExpressionNode } from '../ast' +import { + createObjectProperty, + createSimpleExpression, + ExpressionNode, + NodeTypes, + createCompoundExpression +} from '../ast' import { capitalize } from '@vue/shared' import { createCompilerError, ErrorCodes } from '../errors' // v-on without arg is handled directly in ./element.ts due to it affecting // codegen for the entire props object. This transform here is only for v-on // *with* args. -export const transformOn: DirectiveTransform = ( - { arg, exp, loc, modifiers }, - context -) => { +export const transformOn: DirectiveTransform = (dir, context) => { + const { exp, loc, modifiers } = dir + const arg = dir.arg! if (!exp && !modifiers.length) { context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc)) } - const { content, isStatic, loc: argLoc } = arg! let eventName: ExpressionNode - if (isStatic) { - // static arg - eventName = createExpression(`on${capitalize(content)}`, true, argLoc) + if (arg.type === NodeTypes.SIMPLE_EXPRESSION) { + if (arg.isStatic) { + eventName = createSimpleExpression( + `on${capitalize(arg.content)}`, + true, + arg.loc + ) + } else { + eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc) + } } else { - // dynamic arg. turn it into a compound expression. - eventName = arg! - ;( - eventName.children || - (eventName.children = [{ ...eventName, children: undefined }]) - ).unshift(`"on" + `) + // already a compound epxression. + eventName = arg + eventName.children.unshift(`"on" + (`) + eventName.children.push(`)`) } // TODO .once modifier handling since it is platform agnostic // other modifiers are handled in compiler-dom return { props: createObjectProperty( eventName, - exp || createExpression(`() => {}`, false, loc), + exp || createSimpleExpression(`() => {}`, false, loc), loc ), needRuntime: false diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts index 6913514f..fc7c5352 100644 --- a/packages/compiler-dom/__tests__/parse.spec.ts +++ b/packages/compiler-dom/__tests__/parse.spec.ts @@ -4,8 +4,8 @@ import { ElementNode, TextNode, ErrorCodes, - ExpressionNode, - ElementTypes + ElementTypes, + InterpolationNode } from '@vue/compiler-core' import { parserOptionsMinimal as parserOptions, @@ -109,17 +109,24 @@ describe('DOM parser', () => { test('HTML entities in interpolation should be translated for backward compatibility.', () => { const ast = parse('
{{ a < b }}
', parserOptions) const element = ast.children[0] as ElementNode - const interpolation = element.children[0] as ExpressionNode + const interpolation = element.children[0] as InterpolationNode expect(interpolation).toStrictEqual({ - type: NodeTypes.EXPRESSION, - content: 'a < b', - isStatic: false, - isInterpolation: true, + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `a < b`, + isStatic: false, + loc: { + start: { offset: 8, line: 1, column: 9 }, + end: { offset: 16, line: 1, column: 17 }, + source: 'a < b' + } + }, loc: { - start: { offset: 8, line: 1, column: 9 }, - end: { offset: 16, line: 1, column: 17 }, - source: 'a < b' + start: { offset: 5, line: 1, column: 6 }, + end: { offset: 19, line: 1, column: 20 }, + source: '{{ a < b }}' } }) }) diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 0844627b..9082b859 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -42,6 +42,7 @@ export { resolveComponent, resolveDirective } from './helpers/resolveAssets' export { renderList } from './helpers/renderList' export { toString } from './helpers/toString' export { toHandlers } from './helpers/toHandlers' +export { capitalize, camelize } from '@vue/shared' // Internal, for integration with runtime compiler export { registerRuntimeCompiler } from './component'