refactor(compiler): separate Interpolation, SimpleExpression & CompoundExpression types

This commit is contained in:
Evan You 2019-09-27 11:42:02 -04:00
parent d491a022a7
commit d900c13efb
25 changed files with 947 additions and 650 deletions

View File

@ -232,7 +232,7 @@ Object {
}, },
}, },
"name": "attr", "name": "attr",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "c", "content": "c",
"isEmpty": false, "isEmpty": false,
@ -315,7 +315,7 @@ Object {
}, },
}, },
"name": "attr", "name": "attr",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "&#a;", "content": "&#a;",
"isEmpty": false, "isEmpty": false,
@ -398,7 +398,7 @@ Object {
}, },
}, },
"name": "attr", "name": "attr",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "ÿ", "content": "ÿ",
"isEmpty": false, "isEmpty": false,
@ -481,7 +481,7 @@ Object {
}, },
}, },
"name": "attr", "name": "attr",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "&#xg;", "content": "&#xg;",
"isEmpty": false, "isEmpty": false,
@ -1183,7 +1183,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "", "content": "",
"isEmpty": true, "isEmpty": true,
@ -1218,7 +1218,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "", "content": "",
"isEmpty": true, "isEmpty": true,
@ -2359,7 +2359,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -2449,7 +2449,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -2539,7 +2539,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -2629,7 +2629,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -2736,7 +2736,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -2843,7 +2843,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -2950,7 +2950,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -3057,7 +3057,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -3164,7 +3164,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -3271,7 +3271,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -3378,7 +3378,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "abc", "content": "abc",
"isEmpty": false, "isEmpty": false,
@ -3884,8 +3884,8 @@ Object {
Object { Object {
"children": Array [ "children": Array [
Object { Object {
"content": Object {
"content": "a < b", "content": "a < b",
"isInterpolation": true,
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -3902,6 +3902,21 @@ Object {
}, },
"type": 4, "type": 4,
}, },
"loc": Object {
"end": Object {
"column": 20,
"line": 1,
"offset": 19,
},
"source": "{{a < b}}",
"start": Object {
"column": 11,
"line": 1,
"offset": 10,
},
},
"type": 5,
},
], ],
"codegenNode": undefined, "codegenNode": undefined,
"isSelfClosing": false, "isSelfClosing": false,
@ -4133,7 +4148,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "/", "content": "/",
"isEmpty": false, "isEmpty": false,
@ -4240,7 +4255,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -4330,7 +4345,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -4666,7 +4681,7 @@ class=\\"bar\\"></div>",
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "foo", "content": "foo",
"isEmpty": false, "isEmpty": false,
@ -4701,7 +4716,7 @@ class=\\"bar\\"></div>",
}, },
}, },
"name": "class", "name": "class",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar", "content": "bar",
"isEmpty": false, "isEmpty": false,
@ -4810,7 +4825,7 @@ Object {
}, },
}, },
"name": "id", "name": "id",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "foo", "content": "foo",
"isEmpty": false, "isEmpty": false,
@ -4845,7 +4860,7 @@ Object {
}, },
}, },
"name": "class", "name": "class",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar", "content": "bar",
"isEmpty": false, "isEmpty": false,
@ -5476,7 +5491,7 @@ Object {
}, },
}, },
"name": "a\\"bc", "name": "a\\"bc",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "", "content": "",
"isEmpty": true, "isEmpty": true,
@ -5583,7 +5598,7 @@ Object {
}, },
}, },
"name": "a'bc", "name": "a'bc",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "", "content": "",
"isEmpty": true, "isEmpty": true,
@ -5690,7 +5705,7 @@ Object {
}, },
}, },
"name": "a<bc", "name": "a<bc",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "", "content": "",
"isEmpty": true, "isEmpty": true,
@ -5797,7 +5812,7 @@ Object {
}, },
}, },
"name": "foo", "name": "foo",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar\\"", "content": "bar\\"",
"isEmpty": false, "isEmpty": false,
@ -5904,7 +5919,7 @@ Object {
}, },
}, },
"name": "foo", "name": "foo",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar'", "content": "bar'",
"isEmpty": false, "isEmpty": false,
@ -6011,7 +6026,7 @@ Object {
}, },
}, },
"name": "foo", "name": "foo",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar<div", "content": "bar<div",
"isEmpty": false, "isEmpty": false,
@ -6118,7 +6133,7 @@ Object {
}, },
}, },
"name": "foo", "name": "foo",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar=baz", "content": "bar=baz",
"isEmpty": false, "isEmpty": false,
@ -6225,7 +6240,7 @@ Object {
}, },
}, },
"name": "foo", "name": "foo",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar\`", "content": "bar\`",
"isEmpty": false, "isEmpty": false,
@ -6332,7 +6347,7 @@ Object {
}, },
}, },
"name": "=", "name": "=",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -6422,7 +6437,7 @@ Object {
}, },
}, },
"name": "=foo", "name": "=foo",
"type": 5, "type": 6,
"value": Object { "value": Object {
"content": "bar", "content": "bar",
"isEmpty": false, "isEmpty": false,
@ -6594,7 +6609,7 @@ Object {
}, },
}, },
"name": "a", "name": "a",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
Object { Object {
@ -6612,7 +6627,7 @@ Object {
}, },
}, },
"name": "b", "name": "b",
"type": 5, "type": 6,
"value": undefined, "value": undefined,
}, },
], ],
@ -6960,8 +6975,8 @@ Object {
Object { Object {
"children": Array [ "children": Array [
Object { Object {
"content": Object {
"content": "'</div>'", "content": "'</div>'",
"isInterpolation": true,
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -6978,6 +6993,21 @@ Object {
}, },
"type": 4, "type": 4,
}, },
"loc": Object {
"end": Object {
"column": 23,
"line": 1,
"offset": 22,
},
"source": "{{'</div>'}}",
"start": Object {
"column": 11,
"line": 1,
"offset": 10,
},
},
"type": 5,
},
], ],
"codegenNode": undefined, "codegenNode": undefined,
"isSelfClosing": false, "isSelfClosing": false,
@ -7317,9 +7347,9 @@ exports[`compiler: parse Errors X_MISSING_INTERPOLATION_END {{}} 1`] = `
Object { Object {
"children": Array [ "children": Array [
Object { Object {
"content": Object {
"content": "", "content": "",
"isInterpolation": true, "isStatic": false,
"isStatic": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 3, "column": 3,
@ -7335,6 +7365,21 @@ Object {
}, },
"type": 4, "type": 4,
}, },
"loc": Object {
"end": Object {
"column": 5,
"line": 1,
"offset": 4,
},
"source": "{{}}",
"start": Object {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": 5,
},
], ],
"hoists": Array [], "hoists": Array [],
"imports": Array [], "imports": Array [],
@ -7458,7 +7503,6 @@ Object {
Object { Object {
"arg": Object { "arg": Object {
"content": "class", "content": "class",
"isInterpolation": false,
"isStatic": true, "isStatic": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7477,7 +7521,6 @@ Object {
}, },
"exp": Object { "exp": Object {
"content": "{ some: condition }", "content": "{ some: condition }",
"isInterpolation": false,
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7509,7 +7552,7 @@ Object {
}, },
"modifiers": Array [], "modifiers": Array [],
"name": "bind", "name": "bind",
"type": 6, "type": 7,
}, },
], ],
"tag": "div", "tag": "div",
@ -7538,7 +7581,6 @@ Object {
Object { Object {
"arg": Object { "arg": Object {
"content": "style", "content": "style",
"isInterpolation": false,
"isStatic": true, "isStatic": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7557,7 +7599,6 @@ Object {
}, },
"exp": Object { "exp": Object {
"content": "{ color: 'red' }", "content": "{ color: 'red' }",
"isInterpolation": false,
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7589,7 +7630,7 @@ Object {
}, },
"modifiers": Array [], "modifiers": Array [],
"name": "bind", "name": "bind",
"type": 6, "type": 7,
}, },
], ],
"tag": "p", "tag": "p",
@ -7645,7 +7686,6 @@ Object {
Object { Object {
"arg": Object { "arg": Object {
"content": "style", "content": "style",
"isInterpolation": false,
"isStatic": true, "isStatic": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7664,7 +7704,6 @@ Object {
}, },
"exp": Object { "exp": Object {
"content": "{ color: 'red' }", "content": "{ color: 'red' }",
"isInterpolation": false,
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7696,7 +7735,7 @@ Object {
}, },
"modifiers": Array [], "modifiers": Array [],
"name": "bind", "name": "bind",
"type": 6, "type": 7,
}, },
], ],
"tag": "p", "tag": "p",
@ -7744,7 +7783,6 @@ Object {
Object { Object {
"arg": Object { "arg": Object {
"content": "class", "content": "class",
"isInterpolation": false,
"isStatic": true, "isStatic": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7763,7 +7801,6 @@ Object {
}, },
"exp": Object { "exp": Object {
"content": "{ some: condition }", "content": "{ some: condition }",
"isInterpolation": false,
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
@ -7795,7 +7832,7 @@ Object {
}, },
"modifiers": Array [], "modifiers": Array [],
"name": "bind", "name": "bind",
"type": 6, "type": 7,
}, },
], ],
"tag": "div", "tag": "div",

View File

@ -3,14 +3,16 @@ import {
NodeTypes, NodeTypes,
RootNode, RootNode,
SourceLocation, SourceLocation,
createExpression, createSimpleExpression,
Namespaces, Namespaces,
ElementTypes, ElementTypes,
CallExpression, CallExpression,
createObjectExpression, createObjectExpression,
createObjectProperty, createObjectProperty,
createArrayExpression, createArrayExpression,
ElementNode ElementNode,
createCompoundExpression,
createInterpolation
} from '../src' } from '../src'
import { import {
CREATE_VNODE, CREATE_VNODE,
@ -93,12 +95,12 @@ describe('compiler: codegen', () => {
test('hoists', () => { test('hoists', () => {
const root = createRoot({ const root = createRoot({
hoists: [ hoists: [
createExpression(`hello`, false, mockLoc), createSimpleExpression(`hello`, false, mockLoc),
createObjectExpression( createObjectExpression(
[ [
createObjectProperty( createObjectProperty(
createExpression(`id`, true, mockLoc), createSimpleExpression(`id`, true, mockLoc),
createExpression(`foo`, true, mockLoc), createSimpleExpression(`foo`, true, mockLoc),
mockLoc mockLoc
) )
], ],
@ -138,7 +140,7 @@ describe('compiler: codegen', () => {
test('interpolation', () => { test('interpolation', () => {
const { code } = generate( const { code } = generate(
createRoot({ createRoot({
children: [createExpression(`hello`, false, mockLoc, true)] children: [createInterpolation(`hello`, mockLoc)]
}) })
) )
expect(code).toMatch(`return _${TO_STRING}(hello)`) expect(code).toMatch(`return _${TO_STRING}(hello)`)
@ -171,7 +173,7 @@ describe('compiler: codegen', () => {
isEmpty: false, isEmpty: false,
loc: mockLoc loc: mockLoc
}, },
createExpression(`hello`, false, mockLoc, true), createInterpolation(`hello`, mockLoc),
{ {
type: NodeTypes.COMMENT, type: NodeTypes.COMMENT,
content: 'foo', content: 'foo',
@ -199,7 +201,7 @@ describe('compiler: codegen', () => {
isEmpty: false, isEmpty: false,
loc: mockLoc loc: mockLoc
}, },
createExpression(`hello`, false, mockLoc, true), createInterpolation(`hello`, mockLoc),
{ {
type: NodeTypes.COMMENT, type: NodeTypes.COMMENT,
content: 'foo', content: 'foo',
@ -224,14 +226,13 @@ describe('compiler: codegen', () => {
const { code } = generate( const { code } = generate(
createRoot({ createRoot({
children: [ children: [
{ createInterpolation(
type: NodeTypes.EXPRESSION, createCompoundExpression(
content: 'foo', [`_ctx.`, createSimpleExpression(`foo`, false, mockLoc)],
isStatic: false, mockLoc
isInterpolation: true, ),
loc: mockLoc, mockLoc
children: [`_ctx.`, createExpression(`foo`, false, mockLoc)] )
}
] ]
}) })
) )
@ -249,7 +250,7 @@ describe('compiler: codegen', () => {
branches: [ branches: [
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('foo', false, mockLoc), condition: createSimpleExpression('foo', false, mockLoc),
loc: mockLoc, loc: mockLoc,
children: [ children: [
{ {
@ -262,9 +263,9 @@ describe('compiler: codegen', () => {
}, },
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('a + b', false, mockLoc), condition: createSimpleExpression('a + b', false, mockLoc),
loc: mockLoc, loc: mockLoc,
children: [createExpression(`bye`, false, mockLoc, true)] children: [createInterpolation(`bye`, mockLoc)]
}, },
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
@ -302,7 +303,7 @@ describe('compiler: codegen', () => {
branches: [ branches: [
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('foo', false, mockLoc), condition: createSimpleExpression('foo', false, mockLoc),
loc: mockLoc, loc: mockLoc,
children: [ children: [
{ {
@ -315,9 +316,9 @@ describe('compiler: codegen', () => {
}, },
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('a + b', false, mockLoc), condition: createSimpleExpression('a + b', false, mockLoc),
loc: mockLoc, loc: mockLoc,
children: [createExpression(`bye`, false, mockLoc, true)] children: [createInterpolation(`bye`, mockLoc)]
} }
] ]
} }
@ -340,11 +341,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.FOR, type: NodeTypes.FOR,
loc: mockLoc, loc: mockLoc,
source: createExpression(`list`, false, mockLoc), source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: createExpression(`v`, false, mockLoc), valueAlias: createSimpleExpression(`v`, false, mockLoc),
keyAlias: createExpression(`k`, false, mockLoc), keyAlias: createSimpleExpression(`k`, false, mockLoc),
objectIndexAlias: createExpression(`i`, false, mockLoc), objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
children: [createExpression(`v`, false, mockLoc, true)] children: [createInterpolation(`v`, mockLoc)]
} }
] ]
}) })
@ -364,11 +365,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.FOR, type: NodeTypes.FOR,
loc: mockLoc, loc: mockLoc,
source: createExpression(`list`, false, mockLoc), source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: createExpression(`v`, false, mockLoc), valueAlias: createSimpleExpression(`v`, false, mockLoc),
keyAlias: createExpression(`k`, false, mockLoc), keyAlias: createSimpleExpression(`k`, false, mockLoc),
objectIndexAlias: createExpression(`i`, false, mockLoc), objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
children: [createExpression(`v`, false, mockLoc, true)] children: [createInterpolation(`v`, mockLoc)]
} }
] ]
}), }),
@ -391,11 +392,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.FOR, type: NodeTypes.FOR,
loc: mockLoc, loc: mockLoc,
source: createExpression(`list`, false, mockLoc), source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: undefined, valueAlias: undefined,
keyAlias: createExpression(`k`, false, mockLoc), keyAlias: createSimpleExpression(`k`, false, mockLoc),
objectIndexAlias: createExpression(`i`, false, mockLoc), objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
children: [createExpression(`v`, false, mockLoc, true)] children: [createInterpolation(`v`, mockLoc)]
} }
] ]
}) })
@ -415,11 +416,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.FOR, type: NodeTypes.FOR,
loc: mockLoc, loc: mockLoc,
source: createExpression(`list`, false, mockLoc), source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: createExpression(`v`, false, mockLoc), valueAlias: createSimpleExpression(`v`, false, mockLoc),
keyAlias: undefined, keyAlias: undefined,
objectIndexAlias: createExpression(`i`, false, mockLoc), objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
children: [createExpression(`v`, false, mockLoc, true)] children: [createInterpolation(`v`, mockLoc)]
} }
] ]
}) })
@ -439,11 +440,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.FOR, type: NodeTypes.FOR,
loc: mockLoc, loc: mockLoc,
source: createExpression(`list`, false, mockLoc), source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: undefined, valueAlias: undefined,
keyAlias: undefined, keyAlias: undefined,
objectIndexAlias: createExpression(`i`, false, mockLoc), objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
children: [createExpression(`v`, false, mockLoc, true)] children: [createInterpolation(`v`, mockLoc)]
} }
] ]
}) })
@ -488,29 +489,26 @@ describe('compiler: codegen', () => {
createObjectExpression( createObjectExpression(
[ [
createObjectProperty( createObjectProperty(
createExpression(`id`, true, mockLoc), createSimpleExpression(`id`, true, mockLoc),
createExpression(`foo`, true, mockLoc), createSimpleExpression(`foo`, true, mockLoc),
mockLoc mockLoc
), ),
createObjectProperty( createObjectProperty(
createExpression(`prop`, false, mockLoc), createSimpleExpression(`prop`, false, mockLoc),
createExpression(`bar`, false, mockLoc), createSimpleExpression(`bar`, false, mockLoc),
mockLoc mockLoc
), ),
// compound expression as computed key // compound expression as computed key
createObjectProperty( createObjectProperty(
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
content: ``,
loc: mockLoc, loc: mockLoc,
isStatic: false,
isInterpolation: false,
children: [ children: [
`foo + `, `foo + `,
createExpression(`bar`, false, mockLoc) createSimpleExpression(`bar`, false, mockLoc)
] ]
}, },
createExpression(`bar`, false, mockLoc), createSimpleExpression(`bar`, false, mockLoc),
mockLoc mockLoc
) )
], ],
@ -524,8 +522,8 @@ describe('compiler: codegen', () => {
[ [
createObjectProperty( createObjectProperty(
// should quote the key! // should quote the key!
createExpression(`some-key`, true, mockLoc), createSimpleExpression(`some-key`, true, mockLoc),
createExpression(`foo`, true, mockLoc), createSimpleExpression(`foo`, true, mockLoc),
mockLoc mockLoc
) )
], ],

View File

@ -4,12 +4,12 @@ import {
CommentNode, CommentNode,
ElementNode, ElementNode,
ElementTypes, ElementTypes,
ExpressionNode,
Namespaces, Namespaces,
NodeTypes, NodeTypes,
Position, Position,
TextNode, TextNode,
AttributeNode AttributeNode,
InterpolationNode
} from '../src/ast' } from '../src/ast'
describe('compiler: parse', () => { describe('compiler: parse', () => {
@ -290,82 +290,118 @@ describe('compiler: parse', () => {
describe('Interpolation', () => { describe('Interpolation', () => {
test('simple interpolation', () => { test('simple interpolation', () => {
const ast = parse('{{message}}') const ast = parse('{{message}}')
const interpolation = ast.children[0] as ExpressionNode const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({ expect(interpolation).toStrictEqual({
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: 'message', content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `message`,
isStatic: false, isStatic: false,
isInterpolation: true,
loc: { loc: {
start: { offset: 2, line: 1, column: 3 }, start: { offset: 2, line: 1, column: 3 },
end: { offset: 9, line: 1, column: 10 }, end: { offset: 9, line: 1, column: 10 },
source: 'message' source: `message`
}
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 },
source: '{{message}}'
} }
}) })
}) })
test('it can have tag-like notation', () => { test('it can have tag-like notation', () => {
const ast = parse('{{ a<b }}') const ast = parse('{{ a<b }}')
const interpolation = ast.children[0] as ExpressionNode const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({ expect(interpolation).toStrictEqual({
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: 'a<b', content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a<b`,
isStatic: false, isStatic: false,
isInterpolation: true,
loc: { loc: {
start: { offset: 3, line: 1, column: 4 }, start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 }, end: { offset: 6, line: 1, column: 7 },
source: 'a<b' source: 'a<b'
} }
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
source: '{{ a<b }}'
}
}) })
}) })
test('it can have tag-like notation (2)', () => { test('it can have tag-like notation (2)', () => {
const ast = parse('{{ a<b }}{{ c>d }}') const ast = parse('{{ a<b }}{{ c>d }}')
const interpolation1 = ast.children[0] as ExpressionNode const interpolation1 = ast.children[0] as InterpolationNode
const interpolation2 = ast.children[1] as ExpressionNode const interpolation2 = ast.children[1] as InterpolationNode
expect(interpolation1).toStrictEqual({ expect(interpolation1).toStrictEqual({
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: 'a<b', content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a<b`,
isStatic: false, isStatic: false,
isInterpolation: true,
loc: { loc: {
start: { offset: 3, line: 1, column: 4 }, start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 }, end: { offset: 6, line: 1, column: 7 },
source: 'a<b' source: 'a<b'
} }
},
loc: {
start: { offset: 0, line: 1, column: 1 },
end: { offset: 9, line: 1, column: 10 },
source: '{{ a<b }}'
}
}) })
expect(interpolation2).toStrictEqual({ expect(interpolation2).toStrictEqual({
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: 'c>d', content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false, isStatic: false,
isInterpolation: true, content: 'c>d',
loc: { loc: {
start: { offset: 12, line: 1, column: 13 }, start: { offset: 12, line: 1, column: 13 },
end: { offset: 15, line: 1, column: 16 }, end: { offset: 15, line: 1, column: 16 },
source: 'c>d' source: 'c>d'
} }
},
loc: {
start: { offset: 9, line: 1, column: 10 },
end: { offset: 18, line: 1, column: 19 },
source: '{{ c>d }}'
}
}) })
}) })
test('it can have tag-like notation (3)', () => { test('it can have tag-like notation (3)', () => {
const ast = parse('<div>{{ "</div>" }}</div>') const ast = parse('<div>{{ "</div>" }}</div>')
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const interpolation = element.children[0] as ExpressionNode const interpolation = element.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({ expect(interpolation).toStrictEqual({
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: '"</div>"', content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false, isStatic: false,
isInterpolation: true, content: '"</div>"',
loc: { loc: {
start: { offset: 8, line: 1, column: 9 }, start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 }, end: { offset: 16, line: 1, column: 17 },
source: '"</div>"' source: '"</div>"'
} }
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
source: '{{ "</div>" }}'
}
}) })
}) })
}) })
@ -889,10 +925,9 @@ describe('compiler: parse', () => {
arg: undefined, arg: undefined,
modifiers: [], modifiers: [],
exp: { exp: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a', content: 'a',
isStatic: false, isStatic: false,
isInterpolation: false,
loc: { loc: {
start: { offset: 11, line: 1, column: 12 }, start: { offset: 11, line: 1, column: 12 },
end: { offset: 12, line: 1, column: 13 }, end: { offset: 12, line: 1, column: 13 },
@ -915,10 +950,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: 'on', name: 'on',
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click', content: 'click',
isStatic: true, isStatic: true,
isInterpolation: false,
loc: { loc: {
source: 'click', source: 'click',
start: { start: {
@ -987,10 +1022,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: 'on', name: 'on',
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click', content: 'click',
isStatic: true, isStatic: true,
isInterpolation: false,
loc: { loc: {
source: 'click', source: 'click',
start: { start: {
@ -1023,10 +1058,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: 'bind', name: 'bind',
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a', content: 'a',
isStatic: true, isStatic: true,
isInterpolation: false,
loc: { loc: {
source: 'a', source: 'a',
start: { start: {
@ -1043,10 +1078,10 @@ describe('compiler: parse', () => {
}, },
modifiers: [], modifiers: [],
exp: { exp: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b', content: 'b',
isStatic: false, isStatic: false,
isInterpolation: false,
loc: { loc: {
start: { offset: 8, line: 1, column: 9 }, start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 }, end: { offset: 9, line: 1, column: 10 },
@ -1069,10 +1104,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: 'bind', name: 'bind',
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a', content: 'a',
isStatic: true, isStatic: true,
isInterpolation: false,
loc: { loc: {
source: 'a', source: 'a',
start: { start: {
@ -1089,10 +1124,10 @@ describe('compiler: parse', () => {
}, },
modifiers: ['sync'], modifiers: ['sync'],
exp: { exp: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b', content: 'b',
isStatic: false, isStatic: false,
isInterpolation: false,
loc: { loc: {
start: { offset: 13, line: 1, column: 14 }, start: { offset: 13, line: 1, column: 14 },
end: { offset: 14, line: 1, column: 15 }, end: { offset: 14, line: 1, column: 15 },
@ -1115,10 +1150,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: 'on', name: 'on',
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a', content: 'a',
isStatic: true, isStatic: true,
isInterpolation: false,
loc: { loc: {
source: 'a', source: 'a',
start: { start: {
@ -1135,10 +1170,10 @@ describe('compiler: parse', () => {
}, },
modifiers: [], modifiers: [],
exp: { exp: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b', content: 'b',
isStatic: false, isStatic: false,
isInterpolation: false,
loc: { loc: {
start: { offset: 8, line: 1, column: 9 }, start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 }, end: { offset: 9, line: 1, column: 10 },
@ -1161,10 +1196,10 @@ describe('compiler: parse', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: 'on', name: 'on',
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a', content: 'a',
isStatic: true, isStatic: true,
isInterpolation: false,
loc: { loc: {
source: 'a', source: 'a',
start: { start: {
@ -1181,10 +1216,10 @@ describe('compiler: parse', () => {
}, },
modifiers: ['enter'], modifiers: ['enter'],
exp: { exp: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b', content: 'b',
isStatic: false, isStatic: false,
isInterpolation: false,
loc: { loc: {
start: { offset: 14, line: 1, column: 15 }, start: { offset: 14, line: 1, column: 15 },
end: { offset: 15, line: 1, column: 16 }, end: { offset: 15, line: 1, column: 16 },
@ -1313,20 +1348,27 @@ foo
offset += foo.loc.source.length offset += foo.loc.source.length
expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) 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 offset += 3
expect(bar.loc.start).toEqual({ line: 2, column: 8, offset }) expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
offset += bar.loc.source.length offset += barInner.loc.source.length
expect(bar.loc.end).toEqual({ line: 2, column: 11, offset }) expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
offset += 3 offset += 3
expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
offset += but.loc.source.length offset += but.loc.source.length
expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) 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 offset += 3
expect(baz.loc.start).toEqual({ line: 2, column: 22, offset }) expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
offset += baz.loc.source.length offset += bazInner.loc.source.length
expect(baz.loc.end).toEqual({ line: 2, column: 25, offset }) 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', () => { describe('namedCharacterReferences option', () => {

View File

@ -52,12 +52,12 @@ function createStaticObjectMatcher(obj: any) {
properties: Object.keys(obj).map(key => ({ properties: Object.keys(obj).map(key => ({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: key, content: key,
isStatic: true isStatic: true
}, },
value: { value: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: obj[key], content: obj[key],
isStatic: true isStatic: true
} }
@ -87,7 +87,7 @@ describe('compiler: element transform', () => {
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
`"div"`, `"div"`,
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1` content: `_hoisted_1`
} }
]) ])
@ -106,7 +106,7 @@ describe('compiler: element transform', () => {
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
`"div"`, `"div"`,
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1` content: `_hoisted_1`
}, },
[ [
@ -148,7 +148,7 @@ describe('compiler: element transform', () => {
expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.callee).toBe(`_${CREATE_VNODE}`)
// should directly use `obj` in props position // should directly use `obj` in props position
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
}) })
}) })
@ -167,7 +167,7 @@ describe('compiler: element transform', () => {
id: 'foo' id: 'foo'
}), }),
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
} }
] ]
@ -185,7 +185,7 @@ describe('compiler: element transform', () => {
callee: `_${MERGE_PROPS}`, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
}, },
createStaticObjectMatcher({ createStaticObjectMatcher({
@ -209,7 +209,7 @@ describe('compiler: element transform', () => {
id: 'foo' id: 'foo'
}), }),
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
}, },
createStaticObjectMatcher({ createStaticObjectMatcher({
@ -237,7 +237,7 @@ describe('compiler: element transform', () => {
callee: `_${TO_HANDLERS}`, callee: `_${TO_HANDLERS}`,
arguments: [ arguments: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
} }
] ]
@ -267,13 +267,13 @@ describe('compiler: element transform', () => {
callee: `_${TO_HANDLERS}`, callee: `_${TO_HANDLERS}`,
arguments: [ arguments: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `handlers` content: `handlers`
} }
] ]
}, },
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
} }
] ]
@ -363,14 +363,13 @@ describe('compiler: element transform', () => {
`_directive_foo`, `_directive_foo`,
// exp // exp
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `hello`, content: `hello`,
isStatic: false, isStatic: false
isInterpolation: false
}, },
// arg // arg
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`, content: `bar`,
isStatic: true isStatic: true
} }
@ -408,7 +407,7 @@ describe('compiler: element transform', () => {
`_directive_bar`, `_directive_bar`,
// exp // exp
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `x` content: `x`
} }
] ]
@ -419,14 +418,13 @@ describe('compiler: element transform', () => {
`_directive_baz`, `_directive_baz`,
// exp // exp
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `y`, content: `y`,
isStatic: false, isStatic: false
isInterpolation: false
}, },
// arg // arg
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `arg`, content: `arg`,
isStatic: false isStatic: false
}, },
@ -437,12 +435,12 @@ describe('compiler: element transform', () => {
{ {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `mod`, content: `mod`,
isStatic: true isStatic: true
}, },
value: { value: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`, content: `true`,
isStatic: false isStatic: false
} }
@ -450,12 +448,12 @@ describe('compiler: element transform', () => {
{ {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `mad`, content: `mad`,
isStatic: true isStatic: true
}, },
value: { value: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`, content: `true`,
isStatic: false isStatic: false
} }
@ -484,7 +482,7 @@ describe('compiler: element transform', () => {
{ {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `onClick`, content: `onClick`,
isStatic: true isStatic: true
}, },
@ -492,12 +490,12 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [ elements: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `a`, content: `a`,
isStatic: false isStatic: false
}, },
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `b`, content: `b`,
isStatic: false isStatic: false
} }
@ -524,7 +522,7 @@ describe('compiler: element transform', () => {
{ {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`, content: `style`,
isStatic: true isStatic: true
}, },
@ -532,12 +530,12 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [ elements: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`, content: `_hoisted_1`,
isStatic: false isStatic: false
}, },
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ color: 'red' }`, content: `{ color: 'red' }`,
isStatic: false isStatic: false
} }

View File

@ -1,13 +1,13 @@
import { import {
parse, parse,
transform, transform,
ExpressionNode,
ElementNode, ElementNode,
DirectiveNode, DirectiveNode,
NodeTypes, NodeTypes,
ForNode, ForNode,
CompilerOptions, CompilerOptions,
IfNode IfNode,
InterpolationNode
} from '../../src' } from '../../src'
import { transformIf } from '../../src/transforms/vIf' import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor' import { transformFor } from '../../src/transforms/vFor'
@ -28,28 +28,58 @@ function parseWithExpressionTransform(
describe('compiler: expression transform', () => { describe('compiler: expression transform', () => {
test('interpolation (root)', () => { test('interpolation (root)', () => {
const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
expect(node.children).toBeUndefined() expect(node.content).toMatchObject({
expect(node.content).toBe(`_ctx.foo`) type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`
})
}) })
test('interpolation (children)', () => { test('interpolation (children)', () => {
const el = parseWithExpressionTransform( const el = parseWithExpressionTransform(
`<div>{{ foo }}</div>` `<div>{{ foo }}</div>`
) as ElementNode ) as ElementNode
const node = el.children[0] as ExpressionNode const node = el.children[0] as InterpolationNode
expect(node.children).toBeUndefined() expect(node.content).toMatchObject({
expect(node.content).toBe(`_ctx.foo`) type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`
})
})
test('interpolation (complex)', () => {
const el = parseWithExpressionTransform(
`<div>{{ foo + bar(baz.qux) }}</div>`
) 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', () => { test('directive value', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`<div v-foo:arg="baz"/>` `<div v-foo:arg="baz"/>`
) as ElementNode ) 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! const exp = (node.props[0] as DirectiveNode).exp!
expect(exp.children).toBeUndefined() expect(exp).toMatchObject({
expect(exp.content).toBe(`_ctx.baz`) type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.baz`
})
}) })
test('dynamic directive arg', () => { test('dynamic directive arg', () => {
@ -57,19 +87,25 @@ describe('compiler: expression transform', () => {
`<div v-foo:[arg]="baz"/>` `<div v-foo:[arg]="baz"/>`
) as ElementNode ) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg! 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! const exp = (node.props[0] as DirectiveNode).exp!
expect(arg.children).toBeUndefined() expect(exp).toMatchObject({
expect(arg.content).toBe(`_ctx.arg`) type: NodeTypes.SIMPLE_EXPRESSION,
expect(exp.children).toBeUndefined() content: `_ctx.baz`
expect(exp.content).toBe(`_ctx.baz`) })
}) })
test('should prefix complex expressions', () => { test('should prefix complex expressions', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo(baz + 1, { key: kuz }) }}` `{{ foo(baz + 1, { key: kuz }) }}`
) as ExpressionNode ) as InterpolationNode
// should parse into compound expression // should parse into compound expression
expect(node.children).toMatchObject([ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ {
content: `_ctx.foo`, content: `_ctx.foo`,
loc: { loc: {
@ -121,21 +157,43 @@ describe('compiler: expression transform', () => {
} }
}, },
` })` ` })`
]) ]
})
}) })
test('should prefix v-if condition', () => { test('should prefix v-if condition', () => {
const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode
expect(node.branches[0].condition!.children).toBeUndefined() expect(node.branches[0].condition).toMatchObject({
expect(node.branches[0].condition!.content).toBe(`_ctx.ok`) type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.ok`
})
}) })
test('should prefix v-for source', () => { test('should prefix v-for source', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`<div v-for="i in list"/>` `<div v-for="i in list"/>`
) as ForNode ) as ForNode
expect(node.source.children).toBeUndefined() expect(node.source).toMatchObject({
expect(node.source.content).toBe(`_ctx.list`) 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', () => { test('should not prefix v-for alias', () => {
@ -143,16 +201,14 @@ describe('compiler: expression transform', () => {
`<div v-for="i in list">{{ i }}{{ j }}</div>` `<div v-for="i in list">{{ i }}{{ j }}</div>`
) as ForNode ) as ForNode
const div = node.children[0] as ElementNode const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
const i = div.children[0] as ExpressionNode type: NodeTypes.SIMPLE_EXPRESSION,
expect(i.type).toBe(NodeTypes.EXPRESSION) content: `i`
expect(i.content).toBe(`i`) })
expect(i.children).toBeUndefined() expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION,
const j = div.children[1] as ExpressionNode content: `_ctx.j`
expect(j.type).toBe(NodeTypes.EXPRESSION) })
expect(j.children).toBeUndefined()
expect(j.content).toBe(`_ctx.j`)
}) })
test('should not prefix v-for aliases (multiple)', () => { test('should not prefix v-for aliases (multiple)', () => {
@ -160,32 +216,30 @@ describe('compiler: expression transform', () => {
`<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>` `<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`
) as ForNode ) as ForNode
const div = node.children[0] as ElementNode const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({
const exp = div.children[0] as ExpressionNode type: NodeTypes.COMPOUND_EXPRESSION,
expect(exp.type).toBe(NodeTypes.EXPRESSION) children: [
// parsed for better source-map support
expect(exp.children).toMatchObject([
{ content: `i` }, { content: `i` },
` + `, ` + `,
{ content: `j` }, { content: `j` },
` + `, ` + `,
{ content: `k` } { content: `k` }
]) ]
})
const l = div.children[1] as ExpressionNode expect((div.children[1] as InterpolationNode).content).toMatchObject({
expect(l.type).toBe(NodeTypes.EXPRESSION) type: NodeTypes.SIMPLE_EXPRESSION,
expect(l.children).toBeUndefined() content: `_ctx.l`
expect(l.content).toBe(`_ctx.l`) })
}) })
test('should prefix id outside of v-for', () => { test('should prefix id outside of v-for', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`<div><div v-for="i in list" />{{ i }}</div>` `<div><div v-for="i in list" />{{ i }}</div>`
) as ElementNode ) as ElementNode
const exp = node.children[1] as ExpressionNode expect((node.children[1] as InterpolationNode).content).toMatchObject({
expect(exp.type).toBe(NodeTypes.EXPRESSION) type: NodeTypes.SIMPLE_EXPRESSION,
expect(exp.children).toBeUndefined() content: `_ctx.i`
expect(exp.content).toBe(`_ctx.i`) })
}) })
test('nested v-for', () => { test('nested v-for', () => {
@ -197,123 +251,130 @@ describe('compiler: expression transform', () => {
const outerDiv = node.children[0] as ElementNode const outerDiv = node.children[0] as ElementNode
const innerFor = outerDiv.children[0] as ForNode const innerFor = outerDiv.children[0] as ForNode
const innerExp = (innerFor.children[0] as ElementNode) const innerExp = (innerFor.children[0] as ElementNode)
.children[0] as ExpressionNode .children[0] as InterpolationNode
expect(innerExp.type).toBe(NodeTypes.EXPRESSION) expect(innerExp.content).toMatchObject({
expect(innerExp.children).toMatchObject([ type: NodeTypes.COMPOUND_EXPRESSION,
{ content: 'i' }, children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }]
` + `, })
{ content: `_ctx.j` }
])
// when an inner v-for shadows a variable of an outer v-for and exit, // 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 // it should not cause the outer v-for's alias to be removed from known ids
const outerExp = outerDiv.children[1] as ExpressionNode const outerExp = outerDiv.children[1] as InterpolationNode
expect(outerExp.type).toBe(NodeTypes.EXPRESSION) expect(outerExp.content).toMatchObject({
expect(outerExp.content).toBe(`i`) type: NodeTypes.SIMPLE_EXPRESSION,
expect(outerExp.children).toBeUndefined() content: `i`
})
}) })
test('should not prefix whitelisted globals', () => { test('should not prefix whitelisted globals', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ Math.max(1, 2) }}` `{{ Math.max(1, 2) }}`
) as ExpressionNode ) as InterpolationNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.content).toMatchObject({
expect(node.children).toMatchObject([ type: NodeTypes.COMPOUND_EXPRESSION,
{ content: `Math` }, children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`]
`.`, })
{ content: `max` },
`(1, 2)`
])
}) })
test('should not prefix id of a function declaration', () => { test('should not prefix id of a function declaration', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ function foo() { return bar } }}` `{{ function foo() { return bar } }}`
) as ExpressionNode ) as InterpolationNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.content).toMatchObject({
expect(node.children).toMatchObject([ type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`function `, `function `,
{ content: `foo` }, { content: `foo` },
`() { return `, `() { return `,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
` }` ` }`
]) ]
})
}) })
test('should not prefix params of a function expression', () => { test('should not prefix params of a function expression', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo => foo + bar }}` `{{ foo => foo + bar }}`
) as ExpressionNode ) as InterpolationNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.content).toMatchObject({
expect(node.children).toMatchObject([ type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `foo` }, { content: `foo` },
` => `, ` => `,
{ content: `foo` }, { content: `foo` },
` + `, ` + `,
{ content: `_ctx.bar` } { content: `_ctx.bar` }
]) ]
})
}) })
test('should not prefix an object property key', () => { test('should not prefix an object property key', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { foo: bar } }}` `{{ { foo: bar } }}`
) as ExpressionNode ) as InterpolationNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.content).toMatchObject({
expect(node.children).toMatchObject([ type: NodeTypes.COMPOUND_EXPRESSION,
`{ foo: `, children: [`{ foo: `, { content: `_ctx.bar` }, ` }`]
{ content: `_ctx.bar` }, })
` }`
])
}) })
test('should prefix a computed object property key', () => { test('should prefix a computed object property key', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { [foo]: bar } }}` `{{ { [foo]: bar } }}`
) as ExpressionNode ) as InterpolationNode
expect(node.type).toBe(NodeTypes.EXPRESSION) expect(node.content).toMatchObject({
expect(node.children).toMatchObject([ type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`{ [`, `{ [`,
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`]: `, `]: `,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
` }` ` }`
]) ]
})
}) })
test('should prefix object property shorthand value', () => { test('should prefix object property shorthand value', () => {
const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode const node = parseWithExpressionTransform(
expect(node.children).toMatchObject([ `{{ { foo } }}`
`{ foo: `, ) as InterpolationNode
{ content: `_ctx.foo` }, expect(node.content).toMatchObject({
` }` type: NodeTypes.COMPOUND_EXPRESSION,
]) children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
})
}) })
test('should not prefix id in a member expression', () => { test('should not prefix id in a member expression', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo.bar.baz }}` `{{ foo.bar.baz }}`
) as ExpressionNode ) as InterpolationNode
expect(node.children).toMatchObject([ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`.`, `.`,
{ content: `bar` }, { content: `bar` },
`.`, `.`,
{ content: `baz` } { content: `baz` }
]) ]
})
}) })
test('should prefix computed id in a member expression', () => { test('should prefix computed id in a member expression', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo[bar][baz] }}` `{{ foo[bar][baz] }}`
) as ExpressionNode ) as InterpolationNode
expect(node.children).toMatchObject([ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`[`, `[`,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
`][`, `][`,
{ content: '_ctx.baz' }, { content: '_ctx.baz' },
`]` `]`
]) ]
})
}) })
test('should handle parse error', () => { test('should handle parse error', () => {

View File

@ -31,7 +31,7 @@ describe('compiler: style transform', () => {
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`, content: `{"color":"red"}`,
isStatic: false isStatic: false
} }
@ -40,12 +40,12 @@ describe('compiler: style transform', () => {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: `bind`, name: `bind`,
arg: { arg: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`, content: `style`,
isStatic: true isStatic: true
}, },
exp: { exp: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`, content: `_hoisted_1`,
isStatic: false isStatic: false
} }
@ -64,12 +64,12 @@ describe('compiler: style transform', () => {
properties: [ properties: [
{ {
key: { key: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`, content: `style`,
isStatic: true isStatic: true
}, },
value: { value: {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`, content: `_hoisted_1`,
isStatic: false isStatic: false
} }

View File

@ -8,6 +8,8 @@ import {
} from '../../src' } from '../../src'
import { transformBind } from '../../src/transforms/vBind' import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { CAMELIZE } from '../../src/runtimeConstants'
import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVBind( function parseWithVBind(
template: string, template: string,
@ -15,7 +17,10 @@ function parseWithVBind(
): ElementNode { ): ElementNode {
const ast = parse(template) const ast = parse(template)
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement
],
directiveTransforms: { directiveTransforms: {
bind: transformBind bind: transformBind
}, },
@ -117,4 +122,42 @@ describe('compiler: transform v-bind', () => {
} }
}) })
}) })
test('.camel modifier w/ dynamic arg', () => {
const node = parseWithVBind(`<div v-bind:[foo].camel="id"/>`)
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(`<div v-bind:[foo(bar)].camel="id"/>`, {
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
}
})
})
}) })

View File

@ -1,7 +1,7 @@
import { parse } from '../../src/parse' import { parse } from '../../src/parse'
import { transform } from '../../src/transform' import { transform } from '../../src/transform'
import { transformFor } from '../../src/transforms/vFor' 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 { ErrorCodes } from '../../src/errors'
import { CompilerOptions } from '../../src' import { CompilerOptions } from '../../src'
@ -24,7 +24,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('index') expect(forNode.valueAlias!.content).toBe('index')
expect(forNode.source.content).toBe('5') expect((forNode.source as SimpleExpressionNode).content).toBe('5')
}) })
test('value', () => { test('value', () => {
@ -32,7 +32,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item') 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', () => { test('object de-structured value', () => {
@ -42,7 +42,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('{ id, value }') 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', () => { test('array de-structured value', () => {
@ -52,7 +52,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('[ id, value ]') 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', () => { test('value and key', () => {
@ -63,7 +63,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias!.content).toBe('key') expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item') 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', () => { test('value, key and index', () => {
@ -75,7 +75,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value') expect(forNode.valueAlias!.content).toBe('value')
expect(forNode.source.content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
}) })
test('skipped key', () => { test('skipped key', () => {
@ -86,7 +86,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value') 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', () => { test('skipped value and key', () => {
@ -95,7 +95,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias).toBeUndefined() expect(forNode.valueAlias).toBeUndefined()
expect(forNode.source.content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
}) })
test('unbracketed value', () => { test('unbracketed value', () => {
@ -103,7 +103,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item') 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', () => { test('unbracketed value and key', () => {
@ -112,7 +112,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.keyAlias!.content).toBe('key') expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item') 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', () => { test('unbracketed value, key and index', () => {
@ -124,7 +124,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value') expect(forNode.valueAlias!.content).toBe('value')
expect(forNode.source.content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
}) })
test('unbracketed skipped key', () => { test('unbracketed skipped key', () => {
@ -135,7 +135,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value') 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', () => { test('unbracketed skipped value and key', () => {
@ -144,7 +144,7 @@ describe('compiler: transform v-for', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias).toBeUndefined() expect(forNode.valueAlias).toBeUndefined()
expect(forNode.source.content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
}) })
test('missing expression', () => { test('missing expression', () => {
@ -223,7 +223,7 @@ describe('compiler: transform v-for', () => {
) )
const itemsOffset = source.indexOf('items') 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.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1) expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
@ -248,7 +248,7 @@ describe('compiler: transform v-for', () => {
) )
const itemsOffset = source.indexOf('items') 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.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1) expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
@ -273,7 +273,7 @@ describe('compiler: transform v-for', () => {
) )
const itemsOffset = source.indexOf('items') 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.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1) expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
@ -318,7 +318,7 @@ describe('compiler: transform v-for', () => {
) )
const itemsOffset = source.indexOf('items') 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.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1) expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
@ -353,7 +353,7 @@ describe('compiler: transform v-for', () => {
) )
const itemsOffset = source.indexOf('items') 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.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1) expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)

View File

@ -6,7 +6,8 @@ import {
NodeTypes, NodeTypes,
ElementNode, ElementNode,
TextNode, TextNode,
CommentNode CommentNode,
SimpleExpressionNode
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
import { CompilerOptions } from '../../src' import { CompilerOptions } from '../../src'
@ -30,7 +31,9 @@ describe('compiler: transform v-if', () => {
const node = parseWithIfTransform(`<div v-if="ok"/>`) const node = parseWithIfTransform(`<div v-if="ok"/>`)
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1) 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.length).toBe(1)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) 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.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1) 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.length).toBe(3)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) 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) expect(node.branches.length).toBe(2)
const b1 = node.branches[0] 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.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`) expect((b1.children[0] as ElementNode).tag).toBe(`div`)
@ -76,13 +81,13 @@ describe('compiler: transform v-if', () => {
expect(node.branches.length).toBe(2) expect(node.branches.length).toBe(2)
const b1 = node.branches[0] 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.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`) expect((b1.children[0] as ElementNode).tag).toBe(`div`)
const b2 = node.branches[1] 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.length).toBe(1)
expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) expect(b2.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b2.children[0] as ElementNode).tag).toBe(`p`) expect((b2.children[0] as ElementNode).tag).toBe(`p`)
@ -96,13 +101,13 @@ describe('compiler: transform v-if', () => {
expect(node.branches.length).toBe(3) expect(node.branches.length).toBe(3)
const b1 = node.branches[0] 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.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`) expect((b1.children[0] as ElementNode).tag).toBe(`div`)
const b2 = node.branches[1] 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.length).toBe(1)
expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) expect(b2.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b2.children[0] as ElementNode).tag).toBe(`p`) expect((b2.children[0] as ElementNode).tag).toBe(`p`)
@ -126,13 +131,13 @@ describe('compiler: transform v-if', () => {
expect(node.branches.length).toBe(3) expect(node.branches.length).toBe(3)
const b1 = node.branches[0] 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.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`) expect((b1.children[0] as ElementNode).tag).toBe(`div`)
const b2 = node.branches[1] 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.length).toBe(2)
expect(b2.children[0].type).toBe(NodeTypes.COMMENT) expect(b2.children[0].type).toBe(NodeTypes.COMMENT)
expect((b2.children[0] as CommentNode).content).toBe(`foo`) expect((b2.children[0] as CommentNode).content).toBe(`foo`)

View File

@ -4,7 +4,8 @@ import {
ElementNode, ElementNode,
ObjectExpression, ObjectExpression,
CompilerOptions, CompilerOptions,
ErrorCodes ErrorCodes,
NodeTypes
} from '../../src' } from '../../src'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
@ -76,10 +77,11 @@ describe('compiler: transform v-on', () => {
const props = node.codegenNode!.arguments[1] as ObjectExpression const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
isStatic: false, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + `, { content: `event` }] children: [`"on" + (`, { content: `event` }, `)`]
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`, content: `handler`,
isStatic: false isStatic: false
} }
@ -93,10 +95,36 @@ describe('compiler: transform v-on', () => {
const props = node.codegenNode!.arguments[1] as ObjectExpression const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
isStatic: false, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`"on" + `, { content: `_ctx.event` }] children: [`"on" + (`, { content: `_ctx.event` }, `)`]
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`,
isStatic: false
}
})
})
test('dynamic arg with complex exp prefixing', () => {
const node = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
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`, content: `_ctx.handler`,
isStatic: false isStatic: false
} }

View File

@ -1,3 +1,5 @@
import { isString } from '@vue/shared'
// Vue template is a platform-agnostic superset of HTML (syntax only). // Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces like SVG and MathML are declared by platform specific // More namespaces like SVG and MathML are declared by platform specific
// compilers. // compilers.
@ -12,7 +14,8 @@ export const enum NodeTypes {
ELEMENT, ELEMENT,
TEXT, TEXT,
COMMENT, COMMENT,
EXPRESSION, SIMPLE_EXPRESSION,
INTERPOLATION,
ATTRIBUTE, ATTRIBUTE,
DIRECTIVE, DIRECTIVE,
// containers // containers
@ -55,9 +58,11 @@ export interface Position {
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
export type ChildNode = export type ChildNode =
| ElementNode | ElementNode
| ExpressionNode | InterpolationNode
| TextNode | TextNode
| CommentNode | CommentNode
| IfNode | IfNode
@ -107,12 +112,21 @@ export interface DirectiveNode extends Node {
modifiers: string[] modifiers: string[]
} }
export interface ExpressionNode extends Node { export interface SimpleExpressionNode extends Node {
type: NodeTypes.EXPRESSION type: NodeTypes.SIMPLE_EXPRESSION
content: string content: string
isStatic: boolean 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 { export interface IfNode extends Node {
@ -129,9 +143,9 @@ export interface IfBranchNode extends Node {
export interface ForNode extends Node { export interface ForNode extends Node {
type: NodeTypes.FOR type: NodeTypes.FOR
source: ExpressionNode source: ExpressionNode
valueAlias: ExpressionNode | undefined valueAlias: SimpleExpressionNode | undefined
keyAlias: ExpressionNode | undefined keyAlias: SimpleExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined objectIndexAlias: SimpleExpressionNode | undefined
children: ChildNode[] children: ChildNode[]
} }
@ -190,7 +204,7 @@ export function createObjectExpression(
export function createObjectProperty( export function createObjectProperty(
key: ExpressionNode, key: ExpressionNode,
value: ExpressionNode, value: JSChildNode,
loc: SourceLocation loc: SourceLocation
): Property { ): Property {
return { return {
@ -201,18 +215,40 @@ export function createObjectProperty(
} }
} }
export function createExpression( export function createSimpleExpression(
content: string, content: string,
isStatic: boolean, isStatic: boolean,
loc: SourceLocation, loc: SourceLocation
isInterpolation = false ): SimpleExpressionNode {
): ExpressionNode {
return { return {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
loc, loc,
content, content,
isStatic, isStatic
isInterpolation }
}
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
} }
} }

View File

@ -14,7 +14,10 @@ import {
ObjectExpression, ObjectExpression,
IfBranchNode, IfBranchNode,
SourceLocation, SourceLocation,
Position Position,
InterpolationNode,
CompoundExpressionNode,
SimpleExpressionNode
} from './ast' } from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map' import { SourceMapGenerator, RawSourceMap } from 'source-map'
import { import {
@ -110,11 +113,7 @@ function createCodegenContext(
if (!__BROWSER__ && context.map) { if (!__BROWSER__ && context.map) {
if (node) { if (node) {
let name let name
if ( if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
node.type === NodeTypes.EXPRESSION &&
!node.children &&
!node.isStatic
) {
const content = node.content.replace(/^_ctx\./, '') const content = node.content.replace(/^_ctx\./, '')
if (content !== node.content && isSimpleIdentifier(content)) { if (content !== node.content && isSimpleIdentifier(content)) {
name = content name = content
@ -263,7 +262,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
// This will generate a single vnode call if: // This will generate a single vnode call if:
// - The target position explicitly allows a single node (root, if, for) // - 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( function genChildren(
children: ChildNode[], children: ChildNode[],
context: CodegenContext, context: CodegenContext,
@ -277,7 +276,8 @@ function genChildren(
children.length === 1 && children.length === 1 &&
(allowSingle || (allowSingle ||
child.type === NodeTypes.TEXT || child.type === NodeTypes.TEXT ||
child.type == NodeTypes.EXPRESSION) child.type === NodeTypes.INTERPOLATION ||
child.type === NodeTypes.COMMENT)
) { ) {
genNode(child, context) genNode(child, context)
} else { } else {
@ -331,9 +331,15 @@ function genNode(node: CodegenNode, context: CodegenContext) {
case NodeTypes.TEXT: case NodeTypes.TEXT:
genText(node, context) genText(node, context)
break break
case NodeTypes.EXPRESSION: case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, context) genExpression(node, context)
break break
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
break
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context)
break
case NodeTypes.COMMENT: case NodeTypes.COMMENT:
genComment(node, context) genComment(node, context)
break break
@ -369,24 +375,37 @@ function genElement(node: ElementNode, context: CodegenContext) {
genCallExpression(node.codegenNode!, context, false) 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) 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 { push, helper } = context
const { content, children, isStatic, isInterpolation } = node
if (isInterpolation) {
push(`${helper(TO_STRING)}(`) push(`${helper(TO_STRING)}(`)
} genNode(node.content, context)
if (children) {
genCompoundExpression(node, context)
} else {
push(isStatic ? JSON.stringify(content) : content, node)
}
if (isInterpolation) {
push(`)`) 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)
}
}
} }
function genExpressionAsPropertyKey( function genExpressionAsPropertyKey(
@ -394,28 +413,18 @@ function genExpressionAsPropertyKey(
context: CodegenContext context: CodegenContext
) { ) {
const { push } = context const { push } = context
const { content, children, isStatic } = node if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
if (children) {
push(`[`) push(`[`)
genCompoundExpression(node, context) genCompoundExpression(node, context)
push(`]`) push(`]`)
} else if (isStatic) { } else if (node.isStatic) {
// only quote keys if necessary // 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) push(text, node)
} else { } else {
push(`[${content}]`, node) push(`[${node.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)
}
} }
} }
@ -445,10 +454,14 @@ function genIfBranch(
if (condition) { if (condition) {
// v-if or v-else-if // v-if or v-else-if
const { push, indent, deindent, newline } = context const { push, indent, deindent, newline } = context
if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsQuote = !isSimpleIdentifier(condition.content) const needsQuote = !isSimpleIdentifier(condition.content)
needsQuote && push(`(`) needsQuote && push(`(`)
genExpression(condition, context) genExpression(condition, context)
needsQuote && push(`)`) needsQuote && push(`)`)
} else {
genCompoundExpression(condition, context)
}
indent() indent()
context.indentLevel++ context.indentLevel++
push(`? `) push(`? `)
@ -473,7 +486,7 @@ function genFor(node: ForNode, context: CodegenContext) {
const { push, helper, indent, deindent } = context const { push, helper, indent, deindent } = context
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
push(`${helper(RENDER_LIST)}(`, node, true) push(`${helper(RENDER_LIST)}(`, node, true)
genExpression(source, context) genNode(source, context)
push(`, (`) push(`, (`)
if (valueAlias) { if (valueAlias) {
genExpression(valueAlias, context) genExpression(valueAlias, context)

View File

@ -128,7 +128,8 @@ export const errorMessages: { [code: number]: string } = {
[ErrorCodes.X_MISSING_INTERPOLATION_END]: [ErrorCodes.X_MISSING_INTERPOLATION_END]:
'Interpolation end sign was not found.', 'Interpolation end sign was not found.',
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]: [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 // transform errors
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`, [ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`,

View File

@ -23,7 +23,8 @@ import {
RootNode, RootNode,
SourceLocation, SourceLocation,
TextNode, TextNode,
ChildNode ChildNode,
InterpolationNode
} from './ast' } from './ast'
export interface ParserOptions { export interface ParserOptions {
@ -122,7 +123,7 @@ function parseChildren(
while (!isEnd(context, mode, ancestors)) { while (!isEnd(context, mode, ancestors)) {
__DEV__ && assert(context.source.length > 0) __DEV__ && assert(context.source.length > 0)
const s = context.source const s = context.source
let node: any = null let node: ChildNode | ChildNode[] | undefined = undefined
if (startsWith(s, context.options.delimiters[0])) { if (startsWith(s, context.options.delimiters[0])) {
// '{{' // '{{'
@ -529,10 +530,9 @@ function parseAttribute(
} }
arg = { arg = {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content, content,
isStatic, isStatic,
isInterpolation: false,
loc loc
} }
} }
@ -555,10 +555,9 @@ function parseAttribute(
? 'on' ? 'on'
: 'slot'), : 'slot'),
exp: value && { exp: value && {
type: NodeTypes.EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: value.content, content: value.content,
isStatic: false, isStatic: false,
isInterpolation: false,
loc: value.loc loc: value.loc
}, },
arg, arg,
@ -633,7 +632,7 @@ function parseAttributeValue(
function parseInterpolation( function parseInterpolation(
context: ParserContext, context: ParserContext,
mode: TextModes mode: TextModes
): ExpressionNode | undefined { ): InterpolationNode | undefined {
const [open, close] = context.options.delimiters const [open, close] = context.options.delimiters
__DEV__ && assert(startsWith(context.source, open)) __DEV__ && assert(startsWith(context.source, open))
@ -643,28 +642,32 @@ function parseInterpolation(
return undefined return undefined
} }
advanceBy(context, open.length)
const start = getCursor(context) 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 rawContentLength = closeIndex - open.length
const rawContent = context.source.slice(0, rawContentLength) const rawContent = context.source.slice(0, rawContentLength)
const preTrimContent = parseTextData(context, rawContentLength, mode) const preTrimContent = parseTextData(context, rawContentLength, mode)
const content = preTrimContent.trim() const content = preTrimContent.trim()
const startOffset = preTrimContent.indexOf(content) const startOffset = preTrimContent.indexOf(content)
if (startOffset > 0) { if (startOffset > 0) {
advancePositionWithMutation(start, rawContent, startOffset) advancePositionWithMutation(innerStart, rawContent, startOffset)
} }
const endOffset = const endOffset =
rawContentLength - (preTrimContent.length - content.length - startOffset) rawContentLength - (preTrimContent.length - content.length - startOffset)
advancePositionWithMutation(end, rawContent, endOffset) advancePositionWithMutation(innerEnd, rawContent, endOffset)
advanceBy(context, close.length) advanceBy(context, close.length)
return { return {
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
content, content,
loc: getSelection(context, start, end), loc: getSelection(context, innerStart, innerEnd)
isStatic: content === '', },
isInterpolation: true loc: getSelection(context, start)
} }
} }

View File

@ -13,3 +13,4 @@ export const RENDER_LIST = `renderList`
export const TO_STRING = `toString` export const TO_STRING = `toString`
export const MERGE_PROPS = `mergeProps` export const MERGE_PROPS = `mergeProps`
export const TO_HANDLERS = `toHandlers` export const TO_HANDLERS = `toHandlers`
export const CAMELIZE = `camelize`

View File

@ -7,8 +7,9 @@ import {
DirectiveNode, DirectiveNode,
Property, Property,
ExpressionNode, ExpressionNode,
createExpression, createSimpleExpression,
JSChildNode JSChildNode,
SimpleExpressionNode
} from './ast' } from './ast'
import { isString, isArray } from '@vue/shared' import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors' import { CompilerError, defaultOnError } from './errors'
@ -63,8 +64,8 @@ export interface TransformContext extends Required<TransformOptions> {
replaceNode(node: ChildNode): void replaceNode(node: ChildNode): void
removeNode(node?: ChildNode): void removeNode(node?: ChildNode): void
onNodeRemoved: () => void onNodeRemoved: () => void
addIdentifier(exp: ExpressionNode): void addIdentifier(exp: SimpleExpressionNode): void
removeIdentifier(exp: ExpressionNode): void removeIdentifier(exp: SimpleExpressionNode): void
hoist(exp: JSChildNode): ExpressionNode hoist(exp: JSChildNode): ExpressionNode
} }
@ -138,7 +139,7 @@ function createTransformContext(
}, },
hoist(exp) { hoist(exp) {
context.hoists.push(exp) context.hoists.push(exp)
return createExpression( return createSimpleExpression(
`_hoisted_${context.hoists.length}`, `_hoisted_${context.hoists.length}`,
false, false,
exp.loc exp.loc
@ -205,11 +206,9 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
// comment nodes with `createVNode` // comment nodes with `createVNode`
context.helper(COMMENT) context.helper(COMMENT)
break break
case NodeTypes.EXPRESSION: case NodeTypes.INTERPOLATION:
// no need to traverse, but we need to inject toString helper // no need to traverse, but we need to inject toString helper
if (node.isInterpolation) {
context.helper(TO_STRING) context.helper(TO_STRING)
}
break break
// for container types, further traverse downwards // for container types, further traverse downwards

View File

@ -11,7 +11,7 @@ import {
createCallExpression, createCallExpression,
createArrayExpression, createArrayExpression,
createObjectProperty, createObjectProperty,
createExpression, createSimpleExpression,
createObjectExpression, createObjectExpression,
Property Property
} from '../ast' } from '../ast'
@ -122,8 +122,12 @@ function buildProps(
const { loc, name, value } = prop const { loc, name, value } = prop
properties.push( properties.push(
createObjectProperty( createObjectProperty(
createExpression(name, true, getInnerRange(loc, 0, name.length)), createSimpleExpression(
createExpression( name,
true,
getInnerRange(loc, 0, name.length)
),
createSimpleExpression(
value ? value.content : '', value ? value.content : '',
true, true,
value ? value.loc : loc value ? value.loc : loc
@ -236,8 +240,8 @@ function dedupeProperties(properties: Property[]): Property[] {
const deduped: Property[] = [] const deduped: Property[] = []
for (let i = 0; i < properties.length; i++) { for (let i = 0; i < properties.length; i++) {
const prop = properties[i] const prop = properties[i]
// dynamic key named are always allowed // dynamic keys are always allowed
if (!prop.key.isStatic) { if (prop.key.type === NodeTypes.COMPOUND_EXPRESSION || !prop.key.isStatic) {
deduped.push(prop) deduped.push(prop)
continue continue
} }
@ -273,16 +277,7 @@ function mergeAsArray(existing: Property, incoming: Property) {
// :class="expression" class="string" // :class="expression" class="string"
// -> class: expression + "string" // -> class: expression + "string"
function mergeClasses(existing: Property, incoming: Property) { function mergeClasses(existing: Property, incoming: Property) {
const e = existing.value as ExpressionNode // TODO
const children =
e.children ||
(e.children = [
{
...e,
children: undefined
}
])
children.push(` + " " + `, incoming.value as ExpressionNode)
} }
function createDirectiveArgs( function createDirectiveArgs(
@ -305,8 +300,8 @@ function createDirectiveArgs(
createObjectExpression( createObjectExpression(
dir.modifiers.map(modifier => dir.modifiers.map(modifier =>
createObjectProperty( createObjectProperty(
createExpression(modifier, true, loc), createSimpleExpression(modifier, true, loc),
createExpression(`true`, false, loc), createSimpleExpression(`true`, false, loc),
loc loc
) )
), ),

View File

@ -11,26 +11,39 @@
import { parseScript } from 'meriyah' import { parseScript } from 'meriyah'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import { NodeTransform, TransformContext } from '../transform' 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 { Node, Function, Identifier, Property } from 'estree'
import { advancePositionWithClone } from '../utils' import { advancePositionWithClone } from '../utils'
export const transformExpression: NodeTransform = (node, context) => { export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.EXPRESSION && !node.isStatic) { if (node.type === NodeTypes.INTERPOLATION) {
processExpression(node, context) node.content = processExpression(
node.content as SimpleExpressionNode,
context
)
} else if (node.type === NodeTypes.ELEMENT) { } else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element // handle directives on element
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const prop = node.props[i] const prop = node.props[i]
if (prop.type === NodeTypes.DIRECTIVE) { if (prop.type === NodeTypes.DIRECTIVE) {
if (prop.exp) { const exp = prop.exp as SimpleExpressionNode | undefined
processExpression(prop.exp, context) 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') { if (prop.name === 'class') {
// TODO special expression optimization for classes // TODO special expression optimization for classes
processExpression(prop.arg, context) prop.arg = processExpression(arg, context)
} else { } 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 // always be used with a leading !__BROWSER__ check so that it can be
// tree-shaken from the browser build. // tree-shaken from the browser build.
export function processExpression( export function processExpression(
node: ExpressionNode, node: SimpleExpressionNode,
context: TransformContext context: TransformContext
) { ): ExpressionNode {
if (!context.prefixIdentifiers) { 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. // fast path if expression is a simple identifier.
if (simpleIdRE.test(node.content)) { if (simpleIdRE.test(node.content)) {
if (!context.identifiers[node.content]) { if (!context.identifiers[node.content]) {
node.content = `_ctx.${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 let ast
try { try {
ast = parseScript(`(${node.content})`, { ranges: true }) as any ast = parseScript(`(${node.content})`, { ranges: true }) as any
} catch (e) { } catch (e) {
context.onError(e) context.onError(e)
return return node
} }
const ids: (Identifier & PrefixMeta)[] = [] const ids: (Identifier & PrefixMeta)[] = []
@ -145,7 +159,7 @@ export function processExpression(
// an ExpressionNode has the `.children` property, it will be used instead of // an ExpressionNode has the `.children` property, it will be used instead of
// `.content`. // `.content`.
const full = node.content const full = node.content
const children: ExpressionNode['children'] = [] const children: CompoundExpressionNode['children'] = []
ids.sort((a, b) => a.start - b.start) ids.sort((a, b) => a.start - b.start)
ids.forEach((id, i) => { ids.forEach((id, i) => {
const last = ids[i - 1] as any const last = ids[i - 1] as any
@ -155,7 +169,7 @@ export function processExpression(
} }
const source = full.slice(id.start - 1, id.end - 1) const source = full.slice(id.start - 1, id.end - 1)
children.push( children.push(
createExpression(id.name, false, { createSimpleExpression(id.name, false, {
source, source,
start: advancePositionWithClone(node.loc.start, source, id.start - 1), start: advancePositionWithClone(node.loc.start, source, id.start - 1),
end: advancePositionWithClone(node.loc.start, source, id.end - 1) end: advancePositionWithClone(node.loc.start, source, id.end - 1)
@ -167,10 +181,9 @@ export function processExpression(
}) })
if (children.length) { if (children.length) {
// mark it empty so that it's more noticeable in case another transform or return createCompoundExpression(children, node.loc)
// codegen forget to handle `.children` first. } else {
node.content = __DEV__ ? `[[REMOVED]]` : `` return node
node.children = children
} }
} }

View File

@ -1,5 +1,5 @@
import { NodeTransform } from '../transform' 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. // Parse inline CSS strings for static style attributes into an object.
// This is a NodeTransform since it works on the static `style` attribute and // 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) { if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
// replace p with an expression node // replace p with an expression node
const parsed = JSON.stringify(parseInlineCSS(p.value.content)) 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] = { node.props[i] = {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: `bind`, name: `bind`,
arg: createExpression(`style`, true, p.loc), arg: createSimpleExpression(`style`, true, p.loc),
exp, exp,
modifiers: [], modifiers: [],
loc: p.loc loc: p.loc

View File

@ -1,27 +1,36 @@
import { DirectiveTransform } from '../transform' import { DirectiveTransform } from '../transform'
import { createObjectProperty, createExpression } from '../ast' import { createObjectProperty, createSimpleExpression, NodeTypes } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { camelize } from '@vue/shared' import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeConstants'
// v-bind without arg is handled directly in ./element.ts due to it affecting // 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 // codegen for the entire props object. This transform here is only for v-bind
// *with* args. // *with* args.
export const transformBind: DirectiveTransform = ( export const transformBind: DirectiveTransform = (dir, context) => {
{ exp, arg, modifiers, loc }, const { exp, modifiers, loc } = dir
context const arg = dir.arg!
) => {
if (!exp) { if (!exp) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc)) context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
} }
// .prop is no longer necessary due to new patch behavior // .prop is no longer necessary due to new patch behavior
// .sync is replced by v-model:arg // .sync is replced by v-model:arg
if (modifiers.includes('camel')) { 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 { return {
props: createObjectProperty( props: createObjectProperty(
arg!, arg!,
exp || createExpression('', true, loc), exp || createSimpleExpression('', true, loc),
loc loc
), ),
needRuntime: false needRuntime: false

View File

@ -5,8 +5,9 @@ import {
import { import {
NodeTypes, NodeTypes,
ExpressionNode, ExpressionNode,
createExpression, createSimpleExpression,
SourceLocation SourceLocation,
SimpleExpressionNode
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange } from '../utils' import { getInnerRange } from '../utils'
@ -17,7 +18,12 @@ export const transformFor = createStructuralDirectiveTransform(
'for', 'for',
(node, dir, context) => { (node, dir, context) => {
if (dir.exp) { 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) { if (parseResult) {
context.helper(RENDER_LIST) context.helper(RENDER_LIST)
@ -66,29 +72,33 @@ const stripParensRE = /^\(|\)$/g
interface ForParseResult { interface ForParseResult {
source: ExpressionNode source: ExpressionNode
value: ExpressionNode | undefined value: SimpleExpressionNode | undefined
key: ExpressionNode | undefined key: SimpleExpressionNode | undefined
index: ExpressionNode | undefined index: SimpleExpressionNode | undefined
} }
function parseForExpression( function parseForExpression(
input: ExpressionNode, input: SimpleExpressionNode,
context: TransformContext context: TransformContext
): ForParseResult | null { ): ForParseResult | null {
const loc = input.loc const loc = input.loc
const source = input.content const exp = input.content
const inMatch = source.match(forAliasRE) const inMatch = exp.match(forAliasRE)
if (!inMatch) return null if (!inMatch) return null
const [, LHS, RHS] = inMatch const [, LHS, RHS] = inMatch
const result: ForParseResult = {
source: createAliasExpression( let source: ExpressionNode = createAliasExpression(
loc, loc,
RHS.trim(), RHS.trim(),
source.indexOf(RHS, LHS.length), exp.indexOf(RHS, LHS.length)
context, )
context.prefixIdentifiers if (!__BROWSER__ && context.prefixIdentifiers) {
), source = processExpression(source, context)
}
const result: ForParseResult = {
source,
value: undefined, value: undefined,
key: undefined, key: undefined,
index: undefined index: undefined
@ -106,11 +116,8 @@ function parseForExpression(
const keyContent = iteratorMatch[1].trim() const keyContent = iteratorMatch[1].trim()
let keyOffset: number | undefined let keyOffset: number | undefined
if (keyContent) { if (keyContent) {
keyOffset = source.indexOf( keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
keyContent, result.key = createAliasExpression(loc, keyContent, keyOffset)
trimmedOffset + valueContent.length
)
result.key = createAliasExpression(loc, keyContent, keyOffset, context)
} }
if (iteratorMatch[2]) { if (iteratorMatch[2]) {
@ -120,25 +127,19 @@ function parseForExpression(
result.index = createAliasExpression( result.index = createAliasExpression(
loc, loc,
indexContent, indexContent,
source.indexOf( exp.indexOf(
indexContent, indexContent,
result.key result.key
? keyOffset! + keyContent.length ? keyOffset! + keyContent.length
: trimmedOffset + valueContent.length : trimmedOffset + valueContent.length
), )
context
) )
} }
} }
} }
if (valueContent) { if (valueContent) {
result.value = createAliasExpression( result.value = createAliasExpression(loc, valueContent, trimmedOffset)
loc,
valueContent,
trimmedOffset,
context
)
} }
return result return result
@ -147,17 +148,11 @@ function parseForExpression(
function createAliasExpression( function createAliasExpression(
range: SourceLocation, range: SourceLocation,
content: string, content: string,
offset: number, offset: number
context: TransformContext, ): SimpleExpressionNode {
process: boolean = false return createSimpleExpression(
): ExpressionNode {
const exp = createExpression(
content, content,
false, false,
getInnerRange(range, offset, content.length) getInnerRange(range, offset, content.length)
) )
if (!__BROWSER__ && process) {
processExpression(exp, context)
}
return exp
} }

View File

@ -7,7 +7,8 @@ import {
ElementTypes, ElementTypes,
ElementNode, ElementNode,
DirectiveNode, DirectiveNode,
IfBranchNode IfBranchNode,
SimpleExpressionNode
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
@ -16,7 +17,9 @@ export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
(node, dir, context) => { (node, dir, context) => {
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) { 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') { if (dir.name === 'if') {
context.replaceNode({ context.replaceNode({

View File

@ -1,37 +1,46 @@
import { DirectiveTransform } from '../transform' import { DirectiveTransform } from '../transform'
import { createObjectProperty, createExpression, ExpressionNode } from '../ast' import {
createObjectProperty,
createSimpleExpression,
ExpressionNode,
NodeTypes,
createCompoundExpression
} from '../ast'
import { capitalize } from '@vue/shared' import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
// v-on without arg is handled directly in ./element.ts due to it affecting // 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 // codegen for the entire props object. This transform here is only for v-on
// *with* args. // *with* args.
export const transformOn: DirectiveTransform = ( export const transformOn: DirectiveTransform = (dir, context) => {
{ arg, exp, loc, modifiers }, const { exp, loc, modifiers } = dir
context const arg = dir.arg!
) => {
if (!exp && !modifiers.length) { if (!exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc)) context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
} }
const { content, isStatic, loc: argLoc } = arg!
let eventName: ExpressionNode let eventName: ExpressionNode
if (isStatic) { if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
// static arg if (arg.isStatic) {
eventName = createExpression(`on${capitalize(content)}`, true, argLoc) eventName = createSimpleExpression(
`on${capitalize(arg.content)}`,
true,
arg.loc
)
} else { } else {
// dynamic arg. turn it into a compound expression. eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc)
eventName = arg! }
;( } else {
eventName.children || // already a compound epxression.
(eventName.children = [{ ...eventName, children: undefined }]) eventName = arg
).unshift(`"on" + `) eventName.children.unshift(`"on" + (`)
eventName.children.push(`)`)
} }
// TODO .once modifier handling since it is platform agnostic // TODO .once modifier handling since it is platform agnostic
// other modifiers are handled in compiler-dom // other modifiers are handled in compiler-dom
return { return {
props: createObjectProperty( props: createObjectProperty(
eventName, eventName,
exp || createExpression(`() => {}`, false, loc), exp || createSimpleExpression(`() => {}`, false, loc),
loc loc
), ),
needRuntime: false needRuntime: false

View File

@ -4,8 +4,8 @@ import {
ElementNode, ElementNode,
TextNode, TextNode,
ErrorCodes, ErrorCodes,
ExpressionNode, ElementTypes,
ElementTypes InterpolationNode
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { import {
parserOptionsMinimal as parserOptions, parserOptionsMinimal as parserOptions,
@ -109,18 +109,25 @@ describe('DOM parser', () => {
test('HTML entities in interpolation should be translated for backward compatibility.', () => { test('HTML entities in interpolation should be translated for backward compatibility.', () => {
const ast = parse('<div>{{ a &lt; b }}</div>', parserOptions) const ast = parse('<div>{{ a &lt; b }}</div>', parserOptions)
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const interpolation = element.children[0] as ExpressionNode const interpolation = element.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({ expect(interpolation).toStrictEqual({
type: NodeTypes.EXPRESSION, type: NodeTypes.INTERPOLATION,
content: 'a < b', content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a < b`,
isStatic: false, isStatic: false,
isInterpolation: true,
loc: { loc: {
start: { offset: 8, line: 1, column: 9 }, start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 }, end: { offset: 16, line: 1, column: 17 },
source: 'a &lt; b' source: 'a &lt; b'
} }
},
loc: {
start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 },
source: '{{ a &lt; b }}'
}
}) })
}) })
}) })

View File

@ -42,6 +42,7 @@ export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
export { renderList } from './helpers/renderList' export { renderList } from './helpers/renderList'
export { toString } from './helpers/toString' export { toString } from './helpers/toString'
export { toHandlers } from './helpers/toHandlers' export { toHandlers } from './helpers/toHandlers'
export { capitalize, camelize } from '@vue/shared'
// Internal, for integration with runtime compiler // Internal, for integration with runtime compiler
export { registerRuntimeCompiler } from './component' export { registerRuntimeCompiler } from './component'