},
},
"name": "attr",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "c",
"isEmpty": false,
},
},
"name": "attr",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "&#a;",
"isEmpty": false,
},
},
"name": "attr",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "ΓΏ",
"isEmpty": false,
},
},
"name": "attr",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "&#xg;",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "",
"isEmpty": true,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "",
"isEmpty": true,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "abc",
"isEmpty": false,
Object {
"children": Array [
Object {
- "content": "a < b",
- "isInterpolation": true,
- "isStatic": false,
+ "content": Object {
+ "content": "a < b",
+ "isStatic": false,
+ "loc": Object {
+ "end": Object {
+ "column": 18,
+ "line": 1,
+ "offset": 17,
+ },
+ "source": "a < b",
+ "start": Object {
+ "column": 13,
+ "line": 1,
+ "offset": 12,
+ },
+ },
+ "type": 4,
+ },
"loc": Object {
"end": Object {
- "column": 18,
+ "column": 20,
"line": 1,
- "offset": 17,
+ "offset": 19,
},
- "source": "a < b",
+ "source": "{{a < b}}",
"start": Object {
- "column": 13,
+ "column": 11,
"line": 1,
- "offset": 12,
+ "offset": 10,
},
},
- "type": 4,
+ "type": 5,
},
],
"codegenNode": undefined,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "/",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "foo",
"isEmpty": false,
},
},
"name": "class",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar",
"isEmpty": false,
},
},
"name": "id",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "foo",
"isEmpty": false,
},
},
"name": "class",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar",
"isEmpty": false,
},
},
"name": "a\\"bc",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "",
"isEmpty": true,
},
},
"name": "a'bc",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "",
"isEmpty": true,
},
},
"name": "a<bc",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "",
"isEmpty": true,
},
},
"name": "foo",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar\\"",
"isEmpty": false,
},
},
"name": "foo",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar'",
"isEmpty": false,
},
},
"name": "foo",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar<div",
"isEmpty": false,
},
},
"name": "foo",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar=baz",
"isEmpty": false,
},
},
"name": "foo",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar\`",
"isEmpty": false,
},
},
"name": "=",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
},
},
"name": "=foo",
- "type": 5,
+ "type": 6,
"value": Object {
"content": "bar",
"isEmpty": false,
},
},
"name": "a",
- "type": 5,
+ "type": 6,
"value": undefined,
},
Object {
},
},
"name": "b",
- "type": 5,
+ "type": 6,
"value": undefined,
},
],
Object {
"children": Array [
Object {
- "content": "'</div>'",
- "isInterpolation": true,
- "isStatic": false,
+ "content": Object {
+ "content": "'</div>'",
+ "isStatic": false,
+ "loc": Object {
+ "end": Object {
+ "column": 21,
+ "line": 1,
+ "offset": 20,
+ },
+ "source": "'</div>'",
+ "start": Object {
+ "column": 13,
+ "line": 1,
+ "offset": 12,
+ },
+ },
+ "type": 4,
+ },
"loc": Object {
"end": Object {
- "column": 21,
+ "column": 23,
"line": 1,
- "offset": 20,
+ "offset": 22,
},
- "source": "'</div>'",
+ "source": "{{'</div>'}}",
"start": Object {
- "column": 13,
+ "column": 11,
"line": 1,
- "offset": 12,
+ "offset": 10,
},
},
- "type": 4,
+ "type": 5,
},
],
"codegenNode": undefined,
Object {
"children": Array [
Object {
- "content": "",
- "isInterpolation": true,
- "isStatic": true,
+ "content": Object {
+ "content": "",
+ "isStatic": false,
+ "loc": Object {
+ "end": Object {
+ "column": 3,
+ "line": 1,
+ "offset": 2,
+ },
+ "source": "",
+ "start": Object {
+ "column": 3,
+ "line": 1,
+ "offset": 2,
+ },
+ },
+ "type": 4,
+ },
"loc": Object {
"end": Object {
- "column": 3,
+ "column": 5,
"line": 1,
- "offset": 2,
+ "offset": 4,
},
- "source": "",
+ "source": "{{}}",
"start": Object {
- "column": 3,
+ "column": 1,
"line": 1,
- "offset": 2,
+ "offset": 0,
},
},
- "type": 4,
+ "type": 5,
},
],
"hoists": Array [],
Object {
"arg": Object {
"content": "class",
- "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
},
"exp": Object {
"content": "{ some: condition }",
- "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
},
"modifiers": Array [],
"name": "bind",
- "type": 6,
+ "type": 7,
},
],
"tag": "div",
Object {
"arg": Object {
"content": "style",
- "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
},
"exp": Object {
"content": "{ color: 'red' }",
- "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
},
"modifiers": Array [],
"name": "bind",
- "type": 6,
+ "type": 7,
},
],
"tag": "p",
Object {
"arg": Object {
"content": "style",
- "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
},
"exp": Object {
"content": "{ color: 'red' }",
- "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
},
"modifiers": Array [],
"name": "bind",
- "type": 6,
+ "type": 7,
},
],
"tag": "p",
Object {
"arg": Object {
"content": "class",
- "isInterpolation": false,
"isStatic": true,
"loc": Object {
"end": Object {
},
"exp": Object {
"content": "{ some: condition }",
- "isInterpolation": false,
"isStatic": false,
"loc": Object {
"end": Object {
},
"modifiers": Array [],
"name": "bind",
- "type": 6,
+ "type": 7,
},
],
"tag": "div",
NodeTypes,
RootNode,
SourceLocation,
- createExpression,
+ createSimpleExpression,
Namespaces,
ElementTypes,
CallExpression,
createObjectExpression,
createObjectProperty,
createArrayExpression,
- ElementNode
+ ElementNode,
+ createCompoundExpression,
+ createInterpolation
} from '../src'
import {
CREATE_VNODE,
test('hoists', () => {
const root = createRoot({
hoists: [
- createExpression(`hello`, false, mockLoc),
+ createSimpleExpression(`hello`, false, mockLoc),
createObjectExpression(
[
createObjectProperty(
- createExpression(`id`, true, mockLoc),
- createExpression(`foo`, true, mockLoc),
+ createSimpleExpression(`id`, true, mockLoc),
+ createSimpleExpression(`foo`, true, mockLoc),
mockLoc
)
],
test('interpolation', () => {
const { code } = generate(
createRoot({
- children: [createExpression(`hello`, false, mockLoc, true)]
+ children: [createInterpolation(`hello`, mockLoc)]
})
)
expect(code).toMatch(`return _${TO_STRING}(hello)`)
isEmpty: false,
loc: mockLoc
},
- createExpression(`hello`, false, mockLoc, true),
+ createInterpolation(`hello`, mockLoc),
{
type: NodeTypes.COMMENT,
content: 'foo',
isEmpty: false,
loc: mockLoc
},
- createExpression(`hello`, false, mockLoc, true),
+ createInterpolation(`hello`, mockLoc),
{
type: NodeTypes.COMMENT,
content: 'foo',
const { code } = generate(
createRoot({
children: [
- {
- type: NodeTypes.EXPRESSION,
- content: 'foo',
- isStatic: false,
- isInterpolation: true,
- loc: mockLoc,
- children: [`_ctx.`, createExpression(`foo`, false, mockLoc)]
- }
+ createInterpolation(
+ createCompoundExpression(
+ [`_ctx.`, createSimpleExpression(`foo`, false, mockLoc)],
+ mockLoc
+ ),
+ mockLoc
+ )
]
})
)
branches: [
{
type: NodeTypes.IF_BRANCH,
- condition: createExpression('foo', false, mockLoc),
+ condition: createSimpleExpression('foo', false, mockLoc),
loc: mockLoc,
children: [
{
},
{
type: NodeTypes.IF_BRANCH,
- condition: createExpression('a + b', false, mockLoc),
+ condition: createSimpleExpression('a + b', false, mockLoc),
loc: mockLoc,
- children: [createExpression(`bye`, false, mockLoc, true)]
+ children: [createInterpolation(`bye`, mockLoc)]
},
{
type: NodeTypes.IF_BRANCH,
branches: [
{
type: NodeTypes.IF_BRANCH,
- condition: createExpression('foo', false, mockLoc),
+ condition: createSimpleExpression('foo', false, mockLoc),
loc: mockLoc,
children: [
{
},
{
type: NodeTypes.IF_BRANCH,
- condition: createExpression('a + b', false, mockLoc),
+ condition: createSimpleExpression('a + b', false, mockLoc),
loc: mockLoc,
- children: [createExpression(`bye`, false, mockLoc, true)]
+ children: [createInterpolation(`bye`, mockLoc)]
}
]
}
{
type: NodeTypes.FOR,
loc: mockLoc,
- source: createExpression(`list`, false, mockLoc),
- valueAlias: createExpression(`v`, false, mockLoc),
- keyAlias: createExpression(`k`, false, mockLoc),
- objectIndexAlias: createExpression(`i`, false, mockLoc),
- children: [createExpression(`v`, false, mockLoc, true)]
+ source: createSimpleExpression(`list`, false, mockLoc),
+ valueAlias: createSimpleExpression(`v`, false, mockLoc),
+ keyAlias: createSimpleExpression(`k`, false, mockLoc),
+ objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
+ children: [createInterpolation(`v`, mockLoc)]
}
]
})
{
type: NodeTypes.FOR,
loc: mockLoc,
- source: createExpression(`list`, false, mockLoc),
- valueAlias: createExpression(`v`, false, mockLoc),
- keyAlias: createExpression(`k`, false, mockLoc),
- objectIndexAlias: createExpression(`i`, false, mockLoc),
- children: [createExpression(`v`, false, mockLoc, true)]
+ source: createSimpleExpression(`list`, false, mockLoc),
+ valueAlias: createSimpleExpression(`v`, false, mockLoc),
+ keyAlias: createSimpleExpression(`k`, false, mockLoc),
+ objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
+ children: [createInterpolation(`v`, mockLoc)]
}
]
}),
{
type: NodeTypes.FOR,
loc: mockLoc,
- source: createExpression(`list`, false, mockLoc),
+ source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: undefined,
- keyAlias: createExpression(`k`, false, mockLoc),
- objectIndexAlias: createExpression(`i`, false, mockLoc),
- children: [createExpression(`v`, false, mockLoc, true)]
+ keyAlias: createSimpleExpression(`k`, false, mockLoc),
+ objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
+ children: [createInterpolation(`v`, mockLoc)]
}
]
})
{
type: NodeTypes.FOR,
loc: mockLoc,
- source: createExpression(`list`, false, mockLoc),
- valueAlias: createExpression(`v`, false, mockLoc),
+ source: createSimpleExpression(`list`, false, mockLoc),
+ valueAlias: createSimpleExpression(`v`, false, mockLoc),
keyAlias: undefined,
- objectIndexAlias: createExpression(`i`, false, mockLoc),
- children: [createExpression(`v`, false, mockLoc, true)]
+ objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
+ children: [createInterpolation(`v`, mockLoc)]
}
]
})
{
type: NodeTypes.FOR,
loc: mockLoc,
- source: createExpression(`list`, false, mockLoc),
+ source: createSimpleExpression(`list`, false, mockLoc),
valueAlias: undefined,
keyAlias: undefined,
- objectIndexAlias: createExpression(`i`, false, mockLoc),
- children: [createExpression(`v`, false, mockLoc, true)]
+ objectIndexAlias: createSimpleExpression(`i`, false, mockLoc),
+ children: [createInterpolation(`v`, mockLoc)]
}
]
})
createObjectExpression(
[
createObjectProperty(
- createExpression(`id`, true, mockLoc),
- createExpression(`foo`, true, mockLoc),
+ createSimpleExpression(`id`, true, mockLoc),
+ createSimpleExpression(`foo`, true, mockLoc),
mockLoc
),
createObjectProperty(
- createExpression(`prop`, false, mockLoc),
- createExpression(`bar`, false, mockLoc),
+ createSimpleExpression(`prop`, false, mockLoc),
+ createSimpleExpression(`bar`, false, mockLoc),
mockLoc
),
// compound expression as computed key
createObjectProperty(
{
- type: NodeTypes.EXPRESSION,
- content: ``,
+ type: NodeTypes.COMPOUND_EXPRESSION,
loc: mockLoc,
- isStatic: false,
- isInterpolation: false,
children: [
`foo + `,
- createExpression(`bar`, false, mockLoc)
+ createSimpleExpression(`bar`, false, mockLoc)
]
},
- createExpression(`bar`, false, mockLoc),
+ createSimpleExpression(`bar`, false, mockLoc),
mockLoc
)
],
[
createObjectProperty(
// should quote the key!
- createExpression(`some-key`, true, mockLoc),
- createExpression(`foo`, true, mockLoc),
+ createSimpleExpression(`some-key`, true, mockLoc),
+ createSimpleExpression(`foo`, true, mockLoc),
mockLoc
)
],
CommentNode,
ElementNode,
ElementTypes,
- ExpressionNode,
Namespaces,
NodeTypes,
Position,
TextNode,
- AttributeNode
+ AttributeNode,
+ InterpolationNode
} from '../src/ast'
describe('compiler: parse', () => {
describe('Interpolation', () => {
test('simple interpolation', () => {
const ast = parse('{{message}}')
- const interpolation = ast.children[0] as ExpressionNode
+ const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
- type: NodeTypes.EXPRESSION,
- content: 'message',
- isStatic: false,
- isInterpolation: true,
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `message`,
+ isStatic: false,
+ loc: {
+ start: { offset: 2, line: 1, column: 3 },
+ end: { offset: 9, line: 1, column: 10 },
+ source: `message`
+ }
+ },
loc: {
- start: { offset: 2, line: 1, column: 3 },
- end: { offset: 9, line: 1, column: 10 },
- source: 'message'
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 11, line: 1, column: 12 },
+ source: '{{message}}'
}
})
})
test('it can have tag-like notation', () => {
const ast = parse('{{ a<b }}')
- const interpolation = ast.children[0] as ExpressionNode
+ const interpolation = ast.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
- type: NodeTypes.EXPRESSION,
- content: 'a<b',
- isStatic: false,
- isInterpolation: true,
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `a<b`,
+ isStatic: false,
+ loc: {
+ start: { offset: 3, line: 1, column: 4 },
+ end: { offset: 6, line: 1, column: 7 },
+ source: 'a<b'
+ }
+ },
loc: {
- start: { offset: 3, line: 1, column: 4 },
- end: { offset: 6, line: 1, column: 7 },
- source: 'a<b'
+ 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)', () => {
const ast = parse('{{ a<b }}{{ c>d }}')
- const interpolation1 = ast.children[0] as ExpressionNode
- const interpolation2 = ast.children[1] as ExpressionNode
+ const interpolation1 = ast.children[0] as InterpolationNode
+ const interpolation2 = ast.children[1] as InterpolationNode
expect(interpolation1).toStrictEqual({
- type: NodeTypes.EXPRESSION,
- content: 'a<b',
- isStatic: false,
- isInterpolation: true,
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `a<b`,
+ isStatic: false,
+ loc: {
+ start: { offset: 3, line: 1, column: 4 },
+ end: { offset: 6, line: 1, column: 7 },
+ source: 'a<b'
+ }
+ },
loc: {
- start: { offset: 3, line: 1, column: 4 },
- end: { offset: 6, line: 1, column: 7 },
- source: 'a<b'
+ start: { offset: 0, line: 1, column: 1 },
+ end: { offset: 9, line: 1, column: 10 },
+ source: '{{ a<b }}'
}
})
+
expect(interpolation2).toStrictEqual({
- type: NodeTypes.EXPRESSION,
- content: 'c>d',
- isStatic: false,
- isInterpolation: true,
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ isStatic: false,
+ content: 'c>d',
+ loc: {
+ start: { offset: 12, line: 1, column: 13 },
+ end: { offset: 15, line: 1, column: 16 },
+ source: 'c>d'
+ }
+ },
loc: {
- start: { offset: 12, line: 1, column: 13 },
- end: { offset: 15, line: 1, column: 16 },
- source: 'c>d'
+ start: { offset: 9, line: 1, column: 10 },
+ end: { offset: 18, line: 1, column: 19 },
+ source: '{{ c>d }}'
}
})
})
test('it can have tag-like notation (3)', () => {
const ast = parse('<div>{{ "</div>" }}</div>')
const element = ast.children[0] as ElementNode
- const interpolation = element.children[0] as ExpressionNode
+ const interpolation = element.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
- type: NodeTypes.EXPRESSION,
- content: '"</div>"',
- isStatic: false,
- isInterpolation: true,
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ isStatic: false,
+ content: '"</div>"',
+ loc: {
+ start: { offset: 8, line: 1, column: 9 },
+ end: { offset: 16, line: 1, column: 17 },
+ source: '"</div>"'
+ }
+ },
loc: {
- start: { offset: 8, line: 1, column: 9 },
- end: { offset: 16, line: 1, column: 17 },
- source: '"</div>"'
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 19, line: 1, column: 20 },
+ source: '{{ "</div>" }}'
}
})
})
arg: undefined,
modifiers: [],
exp: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: false,
- isInterpolation: false,
loc: {
start: { offset: 11, line: 1, column: 12 },
end: { offset: 12, line: 1, column: 13 },
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
- isInterpolation: false,
+
loc: {
source: 'click',
start: {
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
- isInterpolation: false,
+
loc: {
source: 'click',
start: {
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isInterpolation: false,
+
loc: {
source: 'a',
start: {
},
modifiers: [],
exp: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isInterpolation: false,
+
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isInterpolation: false,
+
loc: {
source: 'a',
start: {
},
modifiers: ['sync'],
exp: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isInterpolation: false,
+
loc: {
start: { offset: 13, line: 1, column: 14 },
end: { offset: 14, line: 1, column: 15 },
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isInterpolation: false,
+
loc: {
source: 'a',
start: {
},
modifiers: [],
exp: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isInterpolation: false,
+
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 9, line: 1, column: 10 },
type: NodeTypes.DIRECTIVE,
name: 'on',
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isInterpolation: false,
+
loc: {
source: 'a',
start: {
},
modifiers: ['enter'],
exp: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isInterpolation: false,
+
loc: {
start: { offset: 14, line: 1, column: 15 },
end: { offset: 15, line: 1, column: 16 },
offset += foo.loc.source.length
expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
+ expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
+ const barInner = (bar as InterpolationNode).content
offset += 3
- expect(bar.loc.start).toEqual({ line: 2, column: 8, offset })
- offset += bar.loc.source.length
- expect(bar.loc.end).toEqual({ line: 2, column: 11, offset })
+ expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
+ offset += barInner.loc.source.length
+ expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
offset += 3
+ expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
offset += but.loc.source.length
expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
+ expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
+ const bazInner = (baz as InterpolationNode).content
+ offset += 3
+ expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
+ offset += bazInner.loc.source.length
+ expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset })
offset += 3
- expect(baz.loc.start).toEqual({ line: 2, column: 22, offset })
- offset += baz.loc.source.length
- expect(baz.loc.end).toEqual({ line: 2, column: 25, offset })
+ expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
})
describe('namedCharacterReferences option', () => {
properties: Object.keys(obj).map(key => ({
type: NodeTypes.JS_PROPERTY,
key: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: key,
isStatic: true
},
value: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: obj[key],
isStatic: true
}
expect(node.arguments).toMatchObject([
`"div"`,
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
}
])
expect(node.arguments).toMatchObject([
`"div"`,
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`
},
[
expect(node.callee).toBe(`_${CREATE_VNODE}`)
// should directly use `obj` in props position
expect(node.arguments[1]).toMatchObject({
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
})
})
id: 'foo'
}),
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
}
]
callee: `_${MERGE_PROPS}`,
arguments: [
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
},
createStaticObjectMatcher({
id: 'foo'
}),
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
},
createStaticObjectMatcher({
callee: `_${TO_HANDLERS}`,
arguments: [
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
}
]
callee: `_${TO_HANDLERS}`,
arguments: [
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `handlers`
}
]
},
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj`
}
]
`_directive_foo`,
// exp
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `hello`,
- isStatic: false,
- isInterpolation: false
+ isStatic: false
},
// arg
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
isStatic: true
}
`_directive_bar`,
// exp
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `x`
}
]
`_directive_baz`,
// exp
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `y`,
- isStatic: false,
- isInterpolation: false
+ isStatic: false
},
// arg
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `arg`,
isStatic: false
},
{
type: NodeTypes.JS_PROPERTY,
key: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `mod`,
isStatic: true
},
value: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`,
isStatic: false
}
{
type: NodeTypes.JS_PROPERTY,
key: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `mad`,
isStatic: true
},
value: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `true`,
isStatic: false
}
{
type: NodeTypes.JS_PROPERTY,
key: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `onClick`,
isStatic: true
},
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `a`,
isStatic: false
},
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `b`,
isStatic: false
}
{
type: NodeTypes.JS_PROPERTY,
key: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
isStatic: true
},
type: NodeTypes.JS_ARRAY_EXPRESSION,
elements: [
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
isStatic: false
},
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `{ color: 'red' }`,
isStatic: false
}
import {
parse,
transform,
- ExpressionNode,
ElementNode,
DirectiveNode,
NodeTypes,
ForNode,
CompilerOptions,
- IfNode
+ IfNode,
+ InterpolationNode
} from '../../src'
import { transformIf } from '../../src/transforms/vIf'
import { transformFor } from '../../src/transforms/vFor'
describe('compiler: expression transform', () => {
test('interpolation (root)', () => {
- const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode
- expect(node.children).toBeUndefined()
- expect(node.content).toBe(`_ctx.foo`)
+ const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.foo`
+ })
})
test('interpolation (children)', () => {
const el = parseWithExpressionTransform(
`<div>{{ foo }}</div>`
) as ElementNode
- const node = el.children[0] as ExpressionNode
- expect(node.children).toBeUndefined()
- expect(node.content).toBe(`_ctx.foo`)
+ const node = el.children[0] as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.foo`
+ })
+ })
+
+ test('interpolation (complex)', () => {
+ const el = parseWithExpressionTransform(
+ `<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', () => {
const node = parseWithExpressionTransform(
`<div v-foo:arg="baz"/>`
) as ElementNode
- expect((node.props[0] as DirectiveNode).arg!.children).toBeUndefined()
+ const arg = (node.props[0] as DirectiveNode).arg!
+ expect(arg).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `arg`
+ })
const exp = (node.props[0] as DirectiveNode).exp!
- expect(exp.children).toBeUndefined()
- expect(exp.content).toBe(`_ctx.baz`)
+ expect(exp).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.baz`
+ })
})
test('dynamic directive arg', () => {
`<div v-foo:[arg]="baz"/>`
) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg!
+ expect(arg).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.arg`
+ })
const exp = (node.props[0] as DirectiveNode).exp!
- expect(arg.children).toBeUndefined()
- expect(arg.content).toBe(`_ctx.arg`)
- expect(exp.children).toBeUndefined()
- expect(exp.content).toBe(`_ctx.baz`)
+ expect(exp).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.baz`
+ })
})
test('should prefix complex expressions', () => {
const node = parseWithExpressionTransform(
`{{ foo(baz + 1, { key: kuz }) }}`
- ) as ExpressionNode
+ ) as InterpolationNode
// should parse into compound expression
- expect(node.children).toMatchObject([
- {
- content: `_ctx.foo`,
- loc: {
- source: `foo`,
- start: {
- offset: 3,
- line: 1,
- column: 4
- },
- end: {
- offset: 6,
- line: 1,
- column: 7
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ {
+ content: `_ctx.foo`,
+ loc: {
+ source: `foo`,
+ start: {
+ offset: 3,
+ line: 1,
+ column: 4
+ },
+ end: {
+ offset: 6,
+ line: 1,
+ column: 7
+ }
}
- }
- },
- `(`,
- {
- content: `_ctx.baz`,
- loc: {
- source: `baz`,
- start: {
- offset: 7,
- line: 1,
- column: 8
- },
- end: {
- offset: 10,
- line: 1,
- column: 11
+ },
+ `(`,
+ {
+ content: `_ctx.baz`,
+ loc: {
+ source: `baz`,
+ start: {
+ offset: 7,
+ line: 1,
+ column: 8
+ },
+ end: {
+ offset: 10,
+ line: 1,
+ column: 11
+ }
}
- }
- },
- ` + 1, { key: `,
- {
- content: `_ctx.kuz`,
- loc: {
- source: `kuz`,
- start: {
- offset: 23,
- line: 1,
- column: 24
- },
- end: {
- offset: 26,
- line: 1,
- column: 27
+ },
+ ` + 1, { key: `,
+ {
+ content: `_ctx.kuz`,
+ loc: {
+ source: `kuz`,
+ start: {
+ offset: 23,
+ line: 1,
+ column: 24
+ },
+ end: {
+ offset: 26,
+ line: 1,
+ column: 27
+ }
}
- }
- },
- ` })`
- ])
+ },
+ ` })`
+ ]
+ })
})
test('should prefix v-if condition', () => {
const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode
- expect(node.branches[0].condition!.children).toBeUndefined()
- expect(node.branches[0].condition!.content).toBe(`_ctx.ok`)
+ expect(node.branches[0].condition).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.ok`
+ })
})
test('should prefix v-for source', () => {
const node = parseWithExpressionTransform(
`<div v-for="i in list"/>`
) as ForNode
- expect(node.source.children).toBeUndefined()
- expect(node.source.content).toBe(`_ctx.list`)
+ expect(node.source).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.list`
+ })
+ })
+
+ test('should prefix v-for source w/ complex expression', () => {
+ const node = parseWithExpressionTransform(
+ `<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', () => {
`<div v-for="i in list">{{ i }}{{ j }}</div>`
) as ForNode
const div = node.children[0] as ElementNode
-
- const i = div.children[0] as ExpressionNode
- expect(i.type).toBe(NodeTypes.EXPRESSION)
- expect(i.content).toBe(`i`)
- expect(i.children).toBeUndefined()
-
- const j = div.children[1] as ExpressionNode
- expect(j.type).toBe(NodeTypes.EXPRESSION)
- expect(j.children).toBeUndefined()
- expect(j.content).toBe(`_ctx.j`)
+ expect((div.children[0] as InterpolationNode).content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `i`
+ })
+ expect((div.children[1] as InterpolationNode).content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.j`
+ })
})
test('should not prefix v-for aliases (multiple)', () => {
`<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`
) as ForNode
const div = node.children[0] as ElementNode
-
- const exp = div.children[0] as ExpressionNode
- expect(exp.type).toBe(NodeTypes.EXPRESSION)
- // parsed for better source-map support
- expect(exp.children).toMatchObject([
- { content: `i` },
- ` + `,
- { content: `j` },
- ` + `,
- { content: `k` }
- ])
-
- const l = div.children[1] as ExpressionNode
- expect(l.type).toBe(NodeTypes.EXPRESSION)
- expect(l.children).toBeUndefined()
- expect(l.content).toBe(`_ctx.l`)
+ expect((div.children[0] as InterpolationNode).content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ { content: `i` },
+ ` + `,
+ { content: `j` },
+ ` + `,
+ { content: `k` }
+ ]
+ })
+ expect((div.children[1] as InterpolationNode).content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.l`
+ })
})
test('should prefix id outside of v-for', () => {
const node = parseWithExpressionTransform(
`<div><div v-for="i in list" />{{ i }}</div>`
) as ElementNode
- const exp = node.children[1] as ExpressionNode
- expect(exp.type).toBe(NodeTypes.EXPRESSION)
- expect(exp.children).toBeUndefined()
- expect(exp.content).toBe(`_ctx.i`)
+ expect((node.children[1] as InterpolationNode).content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.i`
+ })
})
test('nested v-for', () => {
const outerDiv = node.children[0] as ElementNode
const innerFor = outerDiv.children[0] as ForNode
const innerExp = (innerFor.children[0] as ElementNode)
- .children[0] as ExpressionNode
- expect(innerExp.type).toBe(NodeTypes.EXPRESSION)
- expect(innerExp.children).toMatchObject([
- { content: 'i' },
- ` + `,
- { content: `_ctx.j` }
- ])
+ .children[0] as InterpolationNode
+ expect(innerExp.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }]
+ })
// when an inner v-for shadows a variable of an outer v-for and exit,
// it should not cause the outer v-for's alias to be removed from known ids
- const outerExp = outerDiv.children[1] as ExpressionNode
- expect(outerExp.type).toBe(NodeTypes.EXPRESSION)
- expect(outerExp.content).toBe(`i`)
- expect(outerExp.children).toBeUndefined()
+ const outerExp = outerDiv.children[1] as InterpolationNode
+ expect(outerExp.content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `i`
+ })
})
test('should not prefix whitelisted globals', () => {
const node = parseWithExpressionTransform(
`{{ Math.max(1, 2) }}`
- ) as ExpressionNode
- expect(node.type).toBe(NodeTypes.EXPRESSION)
- expect(node.children).toMatchObject([
- { content: `Math` },
- `.`,
- { content: `max` },
- `(1, 2)`
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`]
+ })
})
test('should not prefix id of a function declaration', () => {
const node = parseWithExpressionTransform(
`{{ function foo() { return bar } }}`
- ) as ExpressionNode
- expect(node.type).toBe(NodeTypes.EXPRESSION)
- expect(node.children).toMatchObject([
- `function `,
- { content: `foo` },
- `() { return `,
- { content: `_ctx.bar` },
- ` }`
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ `function `,
+ { content: `foo` },
+ `() { return `,
+ { content: `_ctx.bar` },
+ ` }`
+ ]
+ })
})
test('should not prefix params of a function expression', () => {
const node = parseWithExpressionTransform(
`{{ foo => foo + bar }}`
- ) as ExpressionNode
- expect(node.type).toBe(NodeTypes.EXPRESSION)
- expect(node.children).toMatchObject([
- { content: `foo` },
- ` => `,
- { content: `foo` },
- ` + `,
- { content: `_ctx.bar` }
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ { content: `foo` },
+ ` => `,
+ { content: `foo` },
+ ` + `,
+ { content: `_ctx.bar` }
+ ]
+ })
})
test('should not prefix an object property key', () => {
const node = parseWithExpressionTransform(
`{{ { foo: bar } }}`
- ) as ExpressionNode
- expect(node.type).toBe(NodeTypes.EXPRESSION)
- expect(node.children).toMatchObject([
- `{ foo: `,
- { content: `_ctx.bar` },
- ` }`
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [`{ foo: `, { content: `_ctx.bar` }, ` }`]
+ })
})
test('should prefix a computed object property key', () => {
const node = parseWithExpressionTransform(
`{{ { [foo]: bar } }}`
- ) as ExpressionNode
- expect(node.type).toBe(NodeTypes.EXPRESSION)
- expect(node.children).toMatchObject([
- `{ [`,
- { content: `_ctx.foo` },
- `]: `,
- { content: `_ctx.bar` },
- ` }`
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ `{ [`,
+ { content: `_ctx.foo` },
+ `]: `,
+ { content: `_ctx.bar` },
+ ` }`
+ ]
+ })
})
test('should prefix object property shorthand value', () => {
- const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode
- expect(node.children).toMatchObject([
- `{ foo: `,
- { content: `_ctx.foo` },
- ` }`
- ])
+ const node = parseWithExpressionTransform(
+ `{{ { foo } }}`
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [`{ foo: `, { content: `_ctx.foo` }, ` }`]
+ })
})
test('should not prefix id in a member expression', () => {
const node = parseWithExpressionTransform(
`{{ foo.bar.baz }}`
- ) as ExpressionNode
- expect(node.children).toMatchObject([
- { content: `_ctx.foo` },
- `.`,
- { content: `bar` },
- `.`,
- { content: `baz` }
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ { content: `_ctx.foo` },
+ `.`,
+ { content: `bar` },
+ `.`,
+ { content: `baz` }
+ ]
+ })
})
test('should prefix computed id in a member expression', () => {
const node = parseWithExpressionTransform(
`{{ foo[bar][baz] }}`
- ) as ExpressionNode
- expect(node.children).toMatchObject([
- { content: `_ctx.foo` },
- `[`,
- { content: `_ctx.bar` },
- `][`,
- { content: '_ctx.baz' },
- `]`
- ])
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ { content: `_ctx.foo` },
+ `[`,
+ { content: `_ctx.bar` },
+ `][`,
+ { content: '_ctx.baz' },
+ `]`
+ ]
+ })
})
test('should handle parse error', () => {
)
expect(root.hoists).toMatchObject([
{
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`,
isStatic: false
}
type: NodeTypes.DIRECTIVE,
name: `bind`,
arg: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
isStatic: true
},
exp: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
isStatic: false
}
properties: [
{
key: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`,
isStatic: true
},
value: {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`,
isStatic: false
}
} from '../../src'
import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement'
+import { CAMELIZE } from '../../src/runtimeConstants'
+import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVBind(
template: string,
): ElementNode {
const ast = parse(template)
transform(ast, {
- nodeTransforms: [transformElement],
+ nodeTransforms: [
+ ...(options.prefixIdentifiers ? [transformExpression] : []),
+ transformElement
+ ],
directiveTransforms: {
bind: transformBind
},
}
})
})
+
+ 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
+ }
+ })
+ })
})
import { parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformFor } from '../../src/transforms/vFor'
-import { ForNode, NodeTypes } from '../../src/ast'
+import { ForNode, NodeTypes, SimpleExpressionNode } from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions } from '../../src'
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('index')
- expect(forNode.source.content).toBe('5')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('5')
})
test('value', () => {
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('object de-structured value', () => {
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('{ id, value }')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('array de-structured value', () => {
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('[ id, value ]')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('value and key', () => {
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('value, key and index', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('skipped key', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('skipped value and key', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias).toBeUndefined()
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('unbracketed value', () => {
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('unbracketed value and key', () => {
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('unbracketed value, key and index', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('unbracketed skipped key', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('unbracketed skipped value and key', () => {
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias).toBeUndefined()
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
})
test('missing expression', () => {
)
const itemsOffset = source.indexOf('items')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
)
const itemsOffset = source.indexOf('items')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
)
const itemsOffset = source.indexOf('items')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
)
const itemsOffset = source.indexOf('items')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
)
const itemsOffset = source.indexOf('items')
- expect(forNode.source.content).toBe('items')
+ expect((forNode.source as SimpleExpressionNode).content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(itemsOffset)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
NodeTypes,
ElementNode,
TextNode,
- CommentNode
+ CommentNode,
+ SimpleExpressionNode
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions } from '../../src'
const node = parseWithIfTransform(`<div v-if="ok"/>`)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
- expect(node.branches[0].condition!.content).toBe(`ok`)
+ expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
+ `ok`
+ )
expect(node.branches[0].children.length).toBe(1)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`)
)
expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1)
- expect(node.branches[0].condition!.content).toBe(`ok`)
+ expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
+ `ok`
+ )
expect(node.branches[0].children.length).toBe(3)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`)
expect(node.branches.length).toBe(2)
const b1 = node.branches[0]
- expect(b1.condition!.content).toBe(`ok`)
+ expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`)
expect(b1.children.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`)
expect(node.branches.length).toBe(2)
const b1 = node.branches[0]
- expect(b1.condition!.content).toBe(`ok`)
+ expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`)
expect(b1.children.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`)
const b2 = node.branches[1]
- expect(b2.condition!.content).toBe(`orNot`)
+ expect((b2.condition as SimpleExpressionNode).content).toBe(`orNot`)
expect(b2.children.length).toBe(1)
expect(b2.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b2.children[0] as ElementNode).tag).toBe(`p`)
expect(node.branches.length).toBe(3)
const b1 = node.branches[0]
- expect(b1.condition!.content).toBe(`ok`)
+ expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`)
expect(b1.children.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`)
const b2 = node.branches[1]
- expect(b2.condition!.content).toBe(`orNot`)
+ expect((b2.condition as SimpleExpressionNode).content).toBe(`orNot`)
expect(b2.children.length).toBe(1)
expect(b2.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b2.children[0] as ElementNode).tag).toBe(`p`)
expect(node.branches.length).toBe(3)
const b1 = node.branches[0]
- expect(b1.condition!.content).toBe(`ok`)
+ expect((b1.condition as SimpleExpressionNode).content).toBe(`ok`)
expect(b1.children.length).toBe(1)
expect(b1.children[0].type).toBe(NodeTypes.ELEMENT)
expect((b1.children[0] as ElementNode).tag).toBe(`div`)
const b2 = node.branches[1]
- expect(b2.condition!.content).toBe(`orNot`)
+ expect((b2.condition as SimpleExpressionNode).content).toBe(`orNot`)
expect(b2.children.length).toBe(2)
expect(b2.children[0].type).toBe(NodeTypes.COMMENT)
expect((b2.children[0] as CommentNode).content).toBe(`foo`)
ElementNode,
ObjectExpression,
CompilerOptions,
- ErrorCodes
+ ErrorCodes,
+ NodeTypes
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
- isStatic: false,
- children: [`"on" + `, { content: `event` }]
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [`"on" + (`, { content: `event` }, `)`]
},
value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`,
isStatic: false
}
const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
- isStatic: false,
- children: [`"on" + `, { content: `_ctx.event` }]
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [`"on" + (`, { content: `_ctx.event` }, `)`]
+ },
+ value: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `_ctx.handler`,
+ isStatic: false
+ }
+ })
+ })
+
+ test('dynamic arg with complex exp prefixing', () => {
+ const node = parseWithVOn(`<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`,
isStatic: false
}
+import { isString } from '@vue/shared'
+
// Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces like SVG and MathML are declared by platform specific
// compilers.
ELEMENT,
TEXT,
COMMENT,
- EXPRESSION,
+ SIMPLE_EXPRESSION,
+ INTERPOLATION,
ATTRIBUTE,
DIRECTIVE,
// containers
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
+export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
+
export type ChildNode =
| ElementNode
- | ExpressionNode
+ | InterpolationNode
| TextNode
| CommentNode
| IfNode
modifiers: string[]
}
-export interface ExpressionNode extends Node {
- type: NodeTypes.EXPRESSION
+export interface SimpleExpressionNode extends Node {
+ type: NodeTypes.SIMPLE_EXPRESSION
content: string
isStatic: boolean
- isInterpolation: boolean
- children?: (ExpressionNode | string)[]
+}
+
+export interface InterpolationNode extends Node {
+ type: NodeTypes.INTERPOLATION
+ content: ExpressionNode
+}
+
+// always dynamic
+export interface CompoundExpressionNode extends Node {
+ type: NodeTypes.COMPOUND_EXPRESSION
+ children: (SimpleExpressionNode | string)[]
}
export interface IfNode extends Node {
export interface ForNode extends Node {
type: NodeTypes.FOR
source: ExpressionNode
- valueAlias: ExpressionNode | undefined
- keyAlias: ExpressionNode | undefined
- objectIndexAlias: ExpressionNode | undefined
+ valueAlias: SimpleExpressionNode | undefined
+ keyAlias: SimpleExpressionNode | undefined
+ objectIndexAlias: SimpleExpressionNode | undefined
children: ChildNode[]
}
export function createObjectProperty(
key: ExpressionNode,
- value: ExpressionNode,
+ value: JSChildNode,
loc: SourceLocation
): Property {
return {
}
}
-export function createExpression(
+export function createSimpleExpression(
content: string,
isStatic: boolean,
- loc: SourceLocation,
- isInterpolation = false
-): ExpressionNode {
+ loc: SourceLocation
+): SimpleExpressionNode {
return {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
loc,
content,
- isStatic,
- isInterpolation
+ isStatic
+ }
+}
+
+export function createInterpolation(
+ content: string | CompoundExpressionNode,
+ loc: SourceLocation
+): InterpolationNode {
+ return {
+ type: NodeTypes.INTERPOLATION,
+ loc,
+ content: isString(content)
+ ? createSimpleExpression(content, false, loc)
+ : content
+ }
+}
+
+export function createCompoundExpression(
+ children: CompoundExpressionNode['children'],
+ loc: SourceLocation
+): CompoundExpressionNode {
+ return {
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ loc,
+ children
}
}
ObjectExpression,
IfBranchNode,
SourceLocation,
- Position
+ Position,
+ InterpolationNode,
+ CompoundExpressionNode,
+ SimpleExpressionNode
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
if (!__BROWSER__ && context.map) {
if (node) {
let name
- if (
- node.type === NodeTypes.EXPRESSION &&
- !node.children &&
- !node.isStatic
- ) {
+ if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
const content = node.content.replace(/^_ctx\./, '')
if (content !== node.content && isSimpleIdentifier(content)) {
name = content
// This will generate a single vnode call if:
// - The target position explicitly allows a single node (root, if, for)
-// - The list has length === 1, AND The only child is a text or expression.
+// - The list has length === 1, AND The only child is a text, expression or comment.
function genChildren(
children: ChildNode[],
context: CodegenContext,
children.length === 1 &&
(allowSingle ||
child.type === NodeTypes.TEXT ||
- child.type == NodeTypes.EXPRESSION)
+ child.type === NodeTypes.INTERPOLATION ||
+ child.type === NodeTypes.COMMENT)
) {
genNode(child, context)
} else {
case NodeTypes.TEXT:
genText(node, context)
break
- case NodeTypes.EXPRESSION:
+ case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, context)
break
+ case NodeTypes.INTERPOLATION:
+ genInterpolation(node, context)
+ break
+ case NodeTypes.COMPOUND_EXPRESSION:
+ genCompoundExpression(node, context)
+ break
case NodeTypes.COMMENT:
genComment(node, context)
break
genCallExpression(node.codegenNode!, context, false)
}
-function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
+function genText(
+ node: TextNode | SimpleExpressionNode,
+ context: CodegenContext
+) {
context.push(JSON.stringify(node.content), node)
}
-function genExpression(node: ExpressionNode, context: CodegenContext) {
+function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
+ const { content, isStatic } = node
+ context.push(isStatic ? JSON.stringify(content) : content, node)
+}
+
+function genInterpolation(node: InterpolationNode, context: CodegenContext) {
const { push, helper } = context
- const { content, children, isStatic, isInterpolation } = node
- if (isInterpolation) {
- push(`${helper(TO_STRING)}(`)
- }
- if (children) {
- genCompoundExpression(node, context)
- } else {
- push(isStatic ? JSON.stringify(content) : content, node)
- }
- if (isInterpolation) {
- push(`)`)
+ push(`${helper(TO_STRING)}(`)
+ genNode(node.content, context)
+ push(`)`)
+}
+
+function genCompoundExpression(
+ node: CompoundExpressionNode,
+ context: CodegenContext
+) {
+ for (let i = 0; i < node.children!.length; i++) {
+ const child = node.children![i]
+ if (isString(child)) {
+ context.push(child)
+ } else {
+ genExpression(child, context)
+ }
}
}
context: CodegenContext
) {
const { push } = context
- const { content, children, isStatic } = node
- if (children) {
+ if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
push(`[`)
genCompoundExpression(node, context)
push(`]`)
- } else if (isStatic) {
+ } else if (node.isStatic) {
// only quote keys if necessary
- const text = isSimpleIdentifier(content) ? content : JSON.stringify(content)
+ const text = isSimpleIdentifier(node.content)
+ ? node.content
+ : JSON.stringify(node.content)
push(text, node)
} else {
- push(`[${content}]`, node)
- }
-}
-
-function genCompoundExpression(node: ExpressionNode, context: CodegenContext) {
- for (let i = 0; i < node.children!.length; i++) {
- const child = node.children![i]
- if (isString(child)) {
- context.push(child)
- } else {
- genExpression(child, context)
- }
+ push(`[${node.content}]`, node)
}
}
if (condition) {
// v-if or v-else-if
const { push, indent, deindent, newline } = context
- const needsQuote = !isSimpleIdentifier(condition.content)
- needsQuote && push(`(`)
- genExpression(condition, context)
- needsQuote && push(`)`)
+ if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
+ const needsQuote = !isSimpleIdentifier(condition.content)
+ needsQuote && push(`(`)
+ genExpression(condition, context)
+ needsQuote && push(`)`)
+ } else {
+ genCompoundExpression(condition, context)
+ }
indent()
context.indentLevel++
push(`? `)
const { push, helper, indent, deindent } = context
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
push(`${helper(RENDER_LIST)}(`, node, true)
- genExpression(source, context)
+ genNode(source, context)
push(`, (`)
if (valueAlias) {
genExpression(valueAlias, context)
[ErrorCodes.X_MISSING_INTERPOLATION_END]:
'Interpolation end sign was not found.',
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:
- 'End bracket for dynamic directive argument was not found.',
+ 'End bracket for dynamic directive argument was not found. ' +
+ 'Note that dynamic directive argument connot contain spaces.',
// transform errors
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`,
RootNode,
SourceLocation,
TextNode,
- ChildNode
+ ChildNode,
+ InterpolationNode
} from './ast'
export interface ParserOptions {
while (!isEnd(context, mode, ancestors)) {
__DEV__ && assert(context.source.length > 0)
const s = context.source
- let node: any = null
+ let node: ChildNode | ChildNode[] | undefined = undefined
if (startsWith(s, context.options.delimiters[0])) {
// '{{'
}
arg = {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content,
isStatic,
- isInterpolation: false,
loc
}
}
? 'on'
: 'slot'),
exp: value && {
- type: NodeTypes.EXPRESSION,
+ type: NodeTypes.SIMPLE_EXPRESSION,
content: value.content,
isStatic: false,
- isInterpolation: false,
loc: value.loc
},
arg,
function parseInterpolation(
context: ParserContext,
mode: TextModes
-): ExpressionNode | undefined {
+): InterpolationNode | undefined {
const [open, close] = context.options.delimiters
__DEV__ && assert(startsWith(context.source, open))
return undefined
}
- advanceBy(context, open.length)
const start = getCursor(context)
- const end = getCursor(context)
+ advanceBy(context, open.length)
+ const innerStart = getCursor(context)
+ const innerEnd = getCursor(context)
const rawContentLength = closeIndex - open.length
const rawContent = context.source.slice(0, rawContentLength)
const preTrimContent = parseTextData(context, rawContentLength, mode)
const content = preTrimContent.trim()
const startOffset = preTrimContent.indexOf(content)
if (startOffset > 0) {
- advancePositionWithMutation(start, rawContent, startOffset)
+ advancePositionWithMutation(innerStart, rawContent, startOffset)
}
const endOffset =
rawContentLength - (preTrimContent.length - content.length - startOffset)
- advancePositionWithMutation(end, rawContent, endOffset)
+ advancePositionWithMutation(innerEnd, rawContent, endOffset)
advanceBy(context, close.length)
return {
- type: NodeTypes.EXPRESSION,
- content,
- loc: getSelection(context, start, end),
- isStatic: content === '',
- isInterpolation: true
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ isStatic: false,
+ content,
+ loc: getSelection(context, innerStart, innerEnd)
+ },
+ loc: getSelection(context, start)
}
}
export const TO_STRING = `toString`
export const MERGE_PROPS = `mergeProps`
export const TO_HANDLERS = `toHandlers`
+export const CAMELIZE = `camelize`
DirectiveNode,
Property,
ExpressionNode,
- createExpression,
- JSChildNode
+ createSimpleExpression,
+ JSChildNode,
+ SimpleExpressionNode
} from './ast'
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
replaceNode(node: ChildNode): void
removeNode(node?: ChildNode): void
onNodeRemoved: () => void
- addIdentifier(exp: ExpressionNode): void
- removeIdentifier(exp: ExpressionNode): void
+ addIdentifier(exp: SimpleExpressionNode): void
+ removeIdentifier(exp: SimpleExpressionNode): void
hoist(exp: JSChildNode): ExpressionNode
}
},
hoist(exp) {
context.hoists.push(exp)
- return createExpression(
+ return createSimpleExpression(
`_hoisted_${context.hoists.length}`,
false,
exp.loc
// comment nodes with `createVNode`
context.helper(COMMENT)
break
- case NodeTypes.EXPRESSION:
+ case NodeTypes.INTERPOLATION:
// no need to traverse, but we need to inject toString helper
- if (node.isInterpolation) {
- context.helper(TO_STRING)
- }
+ context.helper(TO_STRING)
break
// for container types, further traverse downwards
createCallExpression,
createArrayExpression,
createObjectProperty,
- createExpression,
+ createSimpleExpression,
createObjectExpression,
Property
} from '../ast'
const { loc, name, value } = prop
properties.push(
createObjectProperty(
- createExpression(name, true, getInnerRange(loc, 0, name.length)),
- createExpression(
+ createSimpleExpression(
+ name,
+ true,
+ getInnerRange(loc, 0, name.length)
+ ),
+ createSimpleExpression(
value ? value.content : '',
true,
value ? value.loc : loc
const deduped: Property[] = []
for (let i = 0; i < properties.length; i++) {
const prop = properties[i]
- // dynamic key named are always allowed
- if (!prop.key.isStatic) {
+ // dynamic keys are always allowed
+ if (prop.key.type === NodeTypes.COMPOUND_EXPRESSION || !prop.key.isStatic) {
deduped.push(prop)
continue
}
// :class="expression" class="string"
// -> class: expression + "string"
function mergeClasses(existing: Property, incoming: Property) {
- const e = existing.value as ExpressionNode
- const children =
- e.children ||
- (e.children = [
- {
- ...e,
- children: undefined
- }
- ])
- children.push(` + " " + `, incoming.value as ExpressionNode)
+ // TODO
}
function createDirectiveArgs(
createObjectExpression(
dir.modifiers.map(modifier =>
createObjectProperty(
- createExpression(modifier, true, loc),
- createExpression(`true`, false, loc),
+ createSimpleExpression(modifier, true, loc),
+ createSimpleExpression(`true`, false, loc),
loc
)
),
import { parseScript } from 'meriyah'
import { walk } from 'estree-walker'
import { NodeTransform, TransformContext } from '../transform'
-import { NodeTypes, createExpression, ExpressionNode } from '../ast'
+import {
+ NodeTypes,
+ createSimpleExpression,
+ ExpressionNode,
+ SimpleExpressionNode,
+ CompoundExpressionNode,
+ createCompoundExpression
+} from '../ast'
import { Node, Function, Identifier, Property } from 'estree'
import { advancePositionWithClone } from '../utils'
+
export const transformExpression: NodeTransform = (node, context) => {
- if (node.type === NodeTypes.EXPRESSION && !node.isStatic) {
- processExpression(node, context)
+ if (node.type === NodeTypes.INTERPOLATION) {
+ node.content = processExpression(
+ node.content as SimpleExpressionNode,
+ context
+ )
} else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element
for (let i = 0; i < node.props.length; i++) {
const prop = node.props[i]
if (prop.type === NodeTypes.DIRECTIVE) {
- if (prop.exp) {
- processExpression(prop.exp, context)
+ const exp = prop.exp as SimpleExpressionNode | undefined
+ const arg = prop.arg as SimpleExpressionNode | undefined
+ if (exp) {
+ prop.exp = processExpression(exp, context)
}
- if (prop.arg && !prop.arg.isStatic) {
+ if (arg && !arg.isStatic) {
if (prop.name === 'class') {
// TODO special expression optimization for classes
- processExpression(prop.arg, context)
+ prop.arg = processExpression(arg, context)
} else {
- processExpression(prop.arg, context)
+ prop.arg = processExpression(arg, context)
}
}
}
// always be used with a leading !__BROWSER__ check so that it can be
// tree-shaken from the browser build.
export function processExpression(
- node: ExpressionNode,
+ node: SimpleExpressionNode,
context: TransformContext
-) {
+): ExpressionNode {
if (!context.prefixIdentifiers) {
- return
+ return node
}
- // lazy require dependencies so that they don't end up in rollup's dep graph
- // and thus can be tree-shaken in browser builds.
- const parseScript =
- _parseScript || (_parseScript = require('meriyah').parseScript)
- const walk = _walk || (_walk = require('estree-walker').walk)
// fast path if expression is a simple identifier.
if (simpleIdRE.test(node.content)) {
if (!context.identifiers[node.content]) {
node.content = `_ctx.${node.content}`
}
- return
+ return node
}
+ // lazy require dependencies so that they don't end up in rollup's dep graph
+ // and thus can be tree-shaken in browser builds.
+ const parseScript =
+ _parseScript || (_parseScript = require('meriyah').parseScript)
+ const walk = _walk || (_walk = require('estree-walker').walk)
+
let ast
try {
ast = parseScript(`(${node.content})`, { ranges: true }) as any
} catch (e) {
context.onError(e)
- return
+ return node
}
const ids: (Identifier & PrefixMeta)[] = []
// an ExpressionNode has the `.children` property, it will be used instead of
// `.content`.
const full = node.content
- const children: ExpressionNode['children'] = []
+ const children: CompoundExpressionNode['children'] = []
ids.sort((a, b) => a.start - b.start)
ids.forEach((id, i) => {
const last = ids[i - 1] as any
}
const source = full.slice(id.start - 1, id.end - 1)
children.push(
- createExpression(id.name, false, {
+ createSimpleExpression(id.name, false, {
source,
start: advancePositionWithClone(node.loc.start, source, id.start - 1),
end: advancePositionWithClone(node.loc.start, source, id.end - 1)
})
if (children.length) {
- // mark it empty so that it's more noticeable in case another transform or
- // codegen forget to handle `.children` first.
- node.content = __DEV__ ? `[[REMOVED]]` : ``
- node.children = children
+ return createCompoundExpression(children, node.loc)
+ } else {
+ return node
}
}
import { NodeTransform } from '../transform'
-import { NodeTypes, createExpression } from '../ast'
+import { NodeTypes, createSimpleExpression } from '../ast'
// Parse inline CSS strings for static style attributes into an object.
// This is a NodeTransform since it works on the static `style` attribute and
if (p.type === NodeTypes.ATTRIBUTE && p.name === 'style' && p.value) {
// replace p with an expression node
const parsed = JSON.stringify(parseInlineCSS(p.value.content))
- const exp = context.hoist(createExpression(parsed, false, p.loc))
+ const exp = context.hoist(createSimpleExpression(parsed, false, p.loc))
node.props[i] = {
type: NodeTypes.DIRECTIVE,
name: `bind`,
- arg: createExpression(`style`, true, p.loc),
+ arg: createSimpleExpression(`style`, true, p.loc),
exp,
modifiers: [],
loc: p.loc
import { DirectiveTransform } from '../transform'
-import { createObjectProperty, createExpression } from '../ast'
+import { createObjectProperty, createSimpleExpression, NodeTypes } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { camelize } from '@vue/shared'
+import { CAMELIZE } from '../runtimeConstants'
// v-bind without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-bind
// *with* args.
-export const transformBind: DirectiveTransform = (
- { exp, arg, modifiers, loc },
- context
-) => {
+export const transformBind: DirectiveTransform = (dir, context) => {
+ const { exp, modifiers, loc } = dir
+ const arg = dir.arg!
if (!exp) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
}
// .prop is no longer necessary due to new patch behavior
// .sync is replced by v-model:arg
if (modifiers.includes('camel')) {
- arg!.content = camelize(arg!.content)
+ if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
+ if (arg.isStatic) {
+ arg.content = camelize(arg.content)
+ } else {
+ arg.content = `${context.helper(CAMELIZE)}(${arg.content})`
+ }
+ } else {
+ arg.children.unshift(`${context.helper(CAMELIZE)}(`)
+ arg.children.push(`)`)
+ }
}
return {
props: createObjectProperty(
arg!,
- exp || createExpression('', true, loc),
+ exp || createSimpleExpression('', true, loc),
loc
),
needRuntime: false
import {
NodeTypes,
ExpressionNode,
- createExpression,
- SourceLocation
+ createSimpleExpression,
+ SourceLocation,
+ SimpleExpressionNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange } from '../utils'
'for',
(node, dir, context) => {
if (dir.exp) {
- const parseResult = parseForExpression(dir.exp, context)
+ const parseResult = parseForExpression(
+ // can only be simple expression because vFor transform is applied
+ // before expression transform.
+ dir.exp as SimpleExpressionNode,
+ context
+ )
if (parseResult) {
context.helper(RENDER_LIST)
interface ForParseResult {
source: ExpressionNode
- value: ExpressionNode | undefined
- key: ExpressionNode | undefined
- index: ExpressionNode | undefined
+ value: SimpleExpressionNode | undefined
+ key: SimpleExpressionNode | undefined
+ index: SimpleExpressionNode | undefined
}
function parseForExpression(
- input: ExpressionNode,
+ input: SimpleExpressionNode,
context: TransformContext
): ForParseResult | null {
const loc = input.loc
- const source = input.content
- const inMatch = source.match(forAliasRE)
+ const exp = input.content
+ const inMatch = exp.match(forAliasRE)
if (!inMatch) return null
const [, LHS, RHS] = inMatch
+
+ let source: ExpressionNode = createAliasExpression(
+ loc,
+ RHS.trim(),
+ exp.indexOf(RHS, LHS.length)
+ )
+ if (!__BROWSER__ && context.prefixIdentifiers) {
+ source = processExpression(source, context)
+ }
+
const result: ForParseResult = {
- source: createAliasExpression(
- loc,
- RHS.trim(),
- source.indexOf(RHS, LHS.length),
- context,
- context.prefixIdentifiers
- ),
+ source,
value: undefined,
key: undefined,
index: undefined
const keyContent = iteratorMatch[1].trim()
let keyOffset: number | undefined
if (keyContent) {
- keyOffset = source.indexOf(
- keyContent,
- trimmedOffset + valueContent.length
- )
- result.key = createAliasExpression(loc, keyContent, keyOffset, context)
+ keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
+ result.key = createAliasExpression(loc, keyContent, keyOffset)
}
if (iteratorMatch[2]) {
result.index = createAliasExpression(
loc,
indexContent,
- source.indexOf(
+ exp.indexOf(
indexContent,
result.key
? keyOffset! + keyContent.length
: trimmedOffset + valueContent.length
- ),
- context
+ )
)
}
}
}
if (valueContent) {
- result.value = createAliasExpression(
- loc,
- valueContent,
- trimmedOffset,
- context
- )
+ result.value = createAliasExpression(loc, valueContent, trimmedOffset)
}
return result
function createAliasExpression(
range: SourceLocation,
content: string,
- offset: number,
- context: TransformContext,
- process: boolean = false
-): ExpressionNode {
- const exp = createExpression(
+ offset: number
+): SimpleExpressionNode {
+ return createSimpleExpression(
content,
false,
getInnerRange(range, offset, content.length)
)
- if (!__BROWSER__ && process) {
- processExpression(exp, context)
- }
- return exp
}
ElementTypes,
ElementNode,
DirectiveNode,
- IfBranchNode
+ IfBranchNode,
+ SimpleExpressionNode
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
/^(if|else|else-if)$/,
(node, dir, context) => {
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
- processExpression(dir.exp, context)
+ // dir.exp can only be simple expression because vIf transform is applied
+ // before expression transform.
+ processExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
context.replaceNode({
import { DirectiveTransform } from '../transform'
-import { createObjectProperty, createExpression, ExpressionNode } from '../ast'
+import {
+ createObjectProperty,
+ createSimpleExpression,
+ ExpressionNode,
+ NodeTypes,
+ createCompoundExpression
+} from '../ast'
import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
// v-on without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on
// *with* args.
-export const transformOn: DirectiveTransform = (
- { arg, exp, loc, modifiers },
- context
-) => {
+export const transformOn: DirectiveTransform = (dir, context) => {
+ const { exp, loc, modifiers } = dir
+ const arg = dir.arg!
if (!exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
- const { content, isStatic, loc: argLoc } = arg!
let eventName: ExpressionNode
- if (isStatic) {
- // static arg
- eventName = createExpression(`on${capitalize(content)}`, true, argLoc)
+ if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
+ if (arg.isStatic) {
+ eventName = createSimpleExpression(
+ `on${capitalize(arg.content)}`,
+ true,
+ arg.loc
+ )
+ } else {
+ eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc)
+ }
} else {
- // dynamic arg. turn it into a compound expression.
- eventName = arg!
- ;(
- eventName.children ||
- (eventName.children = [{ ...eventName, children: undefined }])
- ).unshift(`"on" + `)
+ // already a compound epxression.
+ eventName = arg
+ eventName.children.unshift(`"on" + (`)
+ eventName.children.push(`)`)
}
// TODO .once modifier handling since it is platform agnostic
// other modifiers are handled in compiler-dom
return {
props: createObjectProperty(
eventName,
- exp || createExpression(`() => {}`, false, loc),
+ exp || createSimpleExpression(`() => {}`, false, loc),
loc
),
needRuntime: false
ElementNode,
TextNode,
ErrorCodes,
- ExpressionNode,
- ElementTypes
+ ElementTypes,
+ InterpolationNode
} from '@vue/compiler-core'
import {
parserOptionsMinimal as parserOptions,
test('HTML entities in interpolation should be translated for backward compatibility.', () => {
const ast = parse('<div>{{ a < b }}</div>', parserOptions)
const element = ast.children[0] as ElementNode
- const interpolation = element.children[0] as ExpressionNode
+ const interpolation = element.children[0] as InterpolationNode
expect(interpolation).toStrictEqual({
- type: NodeTypes.EXPRESSION,
- content: 'a < b',
- isStatic: false,
- isInterpolation: true,
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `a < b`,
+ isStatic: false,
+ loc: {
+ start: { offset: 8, line: 1, column: 9 },
+ end: { offset: 16, line: 1, column: 17 },
+ source: 'a < b'
+ }
+ },
loc: {
- start: { offset: 8, line: 1, column: 9 },
- end: { offset: 16, line: 1, column: 17 },
- source: 'a < b'
+ start: { offset: 5, line: 1, column: 6 },
+ end: { offset: 19, line: 1, column: 20 },
+ source: '{{ a < b }}'
}
})
})
export { renderList } from './helpers/renderList'
export { toString } from './helpers/toString'
export { toHandlers } from './helpers/toHandlers'
+export { capitalize, camelize } from '@vue/shared'
// Internal, for integration with runtime compiler
export { registerRuntimeCompiler } from './component'