fix: fix source map by fixing advancePositionWithMutation

This commit is contained in:
Evan You 2019-09-25 19:17:45 -04:00
parent ff2313e43a
commit 6c8f226a79
20 changed files with 533 additions and 255 deletions

View File

@ -20,7 +20,7 @@ exports[`compiler: codegen comment 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return createVNode(Comment, 0, \\"foo\\") return _createVNode(_Comment, 0, \\"foo\\")
} }
}" }"
`; `;
@ -29,7 +29,7 @@ exports[`compiler: codegen compound expression 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return toString(_ctx.foo) return _toString(_ctx.foo)
} }
}" }"
`; `;
@ -38,16 +38,30 @@ exports[`compiler: codegen forNode 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return renderList(list, (v, k, i) => toString(v)) return _renderList(list, (v, k, i) => {
return _toString(v)
})
} }
}" }"
`; `;
exports[`compiler: codegen forNode w/ prefixIdentifiers: true 1`] = `
"
return function render() {
const _ctx = this
return renderList(list, (v, k, i) => {
return toString(v)
})
}"
`;
exports[`compiler: codegen forNode w/ skipped key alias 1`] = ` exports[`compiler: codegen forNode w/ skipped key alias 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return renderList(list, (v, __key, i) => toString(v)) return _renderList(list, (v, __key, i) => {
return _toString(v)
})
} }
}" }"
`; `;
@ -56,7 +70,9 @@ exports[`compiler: codegen forNode w/ skipped value alias 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return renderList(list, (__value, k, i) => toString(v)) return _renderList(list, (__value, k, i) => {
return _toString(v)
})
} }
}" }"
`; `;
@ -65,7 +81,9 @@ exports[`compiler: codegen forNode w/ skipped value and key aliases 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return renderList(list, (__value, __key, i) => toString(v)) return _renderList(list, (__value, __key, i) => {
return _toString(v)
})
} }
}" }"
`; `;
@ -74,12 +92,21 @@ exports[`compiler: codegen function mode preamble 1`] = `
"const _Vue = Vue "const _Vue = Vue
return function render() { return function render() {
with (this) { with (this) {
const { helperOne, helperTwo } = _Vue const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue
return null return null
} }
}" }"
`; `;
exports[`compiler: codegen function mode preamble w/ prefixIdentifiers: true 1`] = `
"const { helperOne, helperTwo } = Vue
return function render() {
const _ctx = this
return null
}"
`;
exports[`compiler: codegen hoists 1`] = ` exports[`compiler: codegen hoists 1`] = `
"const _hoisted_1 = hello "const _hoisted_1 = hello
const _hoisted_2 = { id: \\"foo\\" } const _hoisted_2 = { id: \\"foo\\" }
@ -98,8 +125,8 @@ return function render() {
return foo return foo
? \\"foo\\" ? \\"foo\\"
: (a + b) : (a + b)
? toString(bye) ? _toString(bye)
: createVNode(Comment, 0, \\"foo\\") : _createVNode(_Comment, 0, \\"foo\\")
} }
}" }"
`; `;
@ -111,7 +138,7 @@ return function render() {
return foo return foo
? \\"foo\\" ? \\"foo\\"
: (a + b) : (a + b)
? toString(bye) ? _toString(bye)
: null : null
} }
}" }"
@ -121,7 +148,7 @@ exports[`compiler: codegen interpolation 1`] = `
" "
return function render() { return function render() {
with (this) { with (this) {
return toString(hello) return _toString(hello)
} }
}" }"
`; `;
@ -171,9 +198,21 @@ return function render() {
with (this) { with (this) {
return [ return [
\\"foo\\", \\"foo\\",
toString(hello), _toString(hello),
createVNode(Comment, 0, \\"foo\\") _createVNode(_Comment, 0, \\"foo\\")
] ]
} }
}" }"
`; `;
exports[`compiler: codegen text + comment + interpolation w/ prefixIdentifiers: true 1`] = `
"
return function render() {
const _ctx = this
return [
\\"foo\\",
toString(hello),
createVNode(Comment, 0, \\"foo\\")
]
}"
`;

View File

@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`function mode 1`] = `
"const _Vue = Vue
return function render() {
with (this) {
const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue
return _createVNode(\\"div\\", {
id: \\"foo\\",
class: bar
}, [
_toString(world),
ok
? _createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\",
_renderList(list, (i, j) => {
return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(i + j))])
})
])
}
}"
`;

View File

@ -7481,15 +7481,15 @@ Object {
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 34, "column": 33,
"line": 1, "line": 1,
"offset": 33, "offset": 32,
}, },
"source": "\\"{ some: condition }\\"", "source": "\\"{ some: condition }\\"",
"start": Object { "start": Object {
"column": 13, "column": 14,
"line": 1, "line": 1,
"offset": 12, "offset": 13,
}, },
}, },
"type": 4, "type": 4,
@ -7561,15 +7561,15 @@ Object {
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 35, "column": 34,
"line": 2, "line": 2,
"offset": 71, "offset": 70,
}, },
"source": "\\"{ color: 'red' }\\"", "source": "\\"{ color: 'red' }\\"",
"start": Object { "start": Object {
"column": 17, "column": 18,
"line": 2, "line": 2,
"offset": 53, "offset": 54,
}, },
}, },
"type": 4, "type": 4,
@ -7629,13 +7629,13 @@ Object {
"isSelfClosing": true, "isSelfClosing": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 38, "column": 39,
"line": 2, "line": 2,
"offset": 73, "offset": 73,
}, },
"source": "<p v-bind:style=\\"{ color: 'red' }\\"/>", "source": "<p v-bind:style=\\"{ color: 'red' }\\"/>",
"start": Object { "start": Object {
"column": 2, "column": 3,
"line": 2, "line": 2,
"offset": 37, "offset": 37,
}, },
@ -7649,13 +7649,13 @@ Object {
"isStatic": true, "isStatic": true,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 17, "column": 18,
"line": 2, "line": 2,
"offset": 52, "offset": 52,
}, },
"source": "style", "source": "style",
"start": Object { "start": Object {
"column": 12, "column": 13,
"line": 2, "line": 2,
"offset": 47, "offset": 47,
}, },
@ -7670,26 +7670,26 @@ Object {
"end": Object { "end": Object {
"column": 36, "column": 36,
"line": 2, "line": 2,
"offset": 71, "offset": 70,
}, },
"source": "\\"{ color: 'red' }\\"", "source": "\\"{ color: 'red' }\\"",
"start": Object { "start": Object {
"column": 18, "column": 20,
"line": 2, "line": 2,
"offset": 53, "offset": 54,
}, },
}, },
"type": 4, "type": 4,
}, },
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 36, "column": 37,
"line": 2, "line": 2,
"offset": 71, "offset": 71,
}, },
"source": "v-bind:style=\\"{ color: 'red' }\\"", "source": "v-bind:style=\\"{ color: 'red' }\\"",
"start": Object { "start": Object {
"column": 5, "column": 6,
"line": 2, "line": 2,
"offset": 40, "offset": 40,
}, },
@ -7707,13 +7707,13 @@ Object {
"content": " a comment with <html> inside it ", "content": " a comment with <html> inside it ",
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 42, "column": 43,
"line": 3, "line": 3,
"offset": 116, "offset": 116,
}, },
"source": "<!-- a comment with <html> inside it -->", "source": "<!-- a comment with <html> inside it -->",
"start": Object { "start": Object {
"column": 2, "column": 3,
"line": 3, "line": 3,
"offset": 76, "offset": 76,
}, },
@ -7767,15 +7767,15 @@ Object {
"isStatic": false, "isStatic": false,
"loc": Object { "loc": Object {
"end": Object { "end": Object {
"column": 34, "column": 33,
"line": 1, "line": 1,
"offset": 33, "offset": 32,
}, },
"source": "\\"{ some: condition }\\"", "source": "\\"{ some: condition }\\"",
"start": Object { "start": Object {
"column": 13, "column": 14,
"line": 1, "line": 1,
"offset": 12, "offset": 13,
}, },
}, },
"type": 4, "type": 4,

View File

@ -12,7 +12,12 @@ import {
createArrayExpression, createArrayExpression,
ElementNode ElementNode
} from '../src' } from '../src'
import { CREATE_VNODE, COMMENT, TO_STRING } from '../src/runtimeConstants' import {
CREATE_VNODE,
COMMENT,
TO_STRING,
RENDER_LIST
} from '../src/runtimeConstants'
const mockLoc: SourceLocation = { const mockLoc: SourceLocation = {
source: ``, source: ``,
@ -56,7 +61,22 @@ describe('compiler: codegen', () => {
}) })
const { code } = generate(root, { mode: 'function' }) const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(`const _Vue = Vue`) expect(code).toMatch(`const _Vue = Vue`)
expect(code).toMatch(`const { helperOne, helperTwo } = _Vue`) expect(code).toMatch(
`const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue`
)
expect(code).toMatchSnapshot()
})
test('function mode preamble w/ prefixIdentifiers: true', () => {
const root = createRoot({
imports: [`helperOne`, `helperTwo`]
})
const { code } = generate(root, {
mode: 'function',
prefixIdentifiers: true
})
expect(code).not.toMatch(`const _Vue = Vue`)
expect(code).toMatch(`const { helperOne, helperTwo } = Vue`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -121,7 +141,7 @@ describe('compiler: codegen', () => {
children: [createExpression(`hello`, false, mockLoc, true)] children: [createExpression(`hello`, false, mockLoc, true)]
}) })
) )
expect(code).toMatch(`return toString(hello)`) expect(code).toMatch(`return _${TO_STRING}(hello)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -137,7 +157,7 @@ describe('compiler: codegen', () => {
] ]
}) })
) )
expect(code).toMatch(`return ${CREATE_VNODE}(${COMMENT}, 0, "foo")`) expect(code).toMatch(`return _${CREATE_VNODE}(_${COMMENT}, 0, "foo")`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -163,7 +183,38 @@ describe('compiler: codegen', () => {
expect(code).toMatch(` expect(code).toMatch(`
return [ return [
"foo", "foo",
toString(hello), _${TO_STRING}(hello),
_${CREATE_VNODE}(_${COMMENT}, 0, "foo")
]`)
expect(code).toMatchSnapshot()
})
test('text + comment + interpolation w/ prefixIdentifiers: true', () => {
const { code } = generate(
createRoot({
children: [
{
type: NodeTypes.TEXT,
content: 'foo',
isEmpty: false,
loc: mockLoc
},
createExpression(`hello`, false, mockLoc, true),
{
type: NodeTypes.COMMENT,
content: 'foo',
loc: mockLoc
}
]
}),
{
prefixIdentifiers: true
}
)
expect(code).toMatch(`
return [
"foo",
${TO_STRING}(hello),
${CREATE_VNODE}(${COMMENT}, 0, "foo") ${CREATE_VNODE}(${COMMENT}, 0, "foo")
]`) ]`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -184,7 +235,7 @@ describe('compiler: codegen', () => {
] ]
}) })
) )
expect(code).toMatch(`return toString(_ctx.foo)`) expect(code).toMatch(`return _${TO_STRING}(_ctx.foo)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -195,13 +246,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.IF, type: NodeTypes.IF,
loc: mockLoc, loc: mockLoc,
isRoot: true,
branches: [ branches: [
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('foo', false, mockLoc), condition: createExpression('foo', false, mockLoc),
loc: mockLoc, loc: mockLoc,
isRoot: true,
children: [ children: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
@ -215,14 +264,12 @@ describe('compiler: codegen', () => {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('a + b', false, mockLoc), condition: createExpression('a + b', false, mockLoc),
loc: mockLoc, loc: mockLoc,
isRoot: true,
children: [createExpression(`bye`, false, mockLoc, true)] children: [createExpression(`bye`, false, mockLoc, true)]
}, },
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: undefined, condition: undefined,
loc: mockLoc, loc: mockLoc,
isRoot: true,
children: [ children: [
{ {
type: NodeTypes.COMMENT, type: NodeTypes.COMMENT,
@ -240,8 +287,8 @@ describe('compiler: codegen', () => {
return foo return foo
? "foo" ? "foo"
: (a + b) : (a + b)
? ${TO_STRING}(bye) ? _${TO_STRING}(bye)
: ${CREATE_VNODE}(${COMMENT}, 0, "foo")`) : _${CREATE_VNODE}(_${COMMENT}, 0, "foo")`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -252,13 +299,11 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.IF, type: NodeTypes.IF,
loc: mockLoc, loc: mockLoc,
isRoot: true,
branches: [ branches: [
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('foo', false, mockLoc), condition: createExpression('foo', false, mockLoc),
loc: mockLoc, loc: mockLoc,
isRoot: true,
children: [ children: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
@ -272,7 +317,6 @@ describe('compiler: codegen', () => {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('a + b', false, mockLoc), condition: createExpression('a + b', false, mockLoc),
loc: mockLoc, loc: mockLoc,
isRoot: true,
children: [createExpression(`bye`, false, mockLoc, true)] children: [createExpression(`bye`, false, mockLoc, true)]
} }
] ]
@ -284,7 +328,7 @@ describe('compiler: codegen', () => {
return foo return foo
? "foo" ? "foo"
: (a + b) : (a + b)
? ${TO_STRING}(bye) ? _${TO_STRING}(bye)
: null`) : null`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -305,7 +349,38 @@ describe('compiler: codegen', () => {
] ]
}) })
) )
expect(code).toMatch(`renderList(list, (v, k, i) => toString(v))`) expect(code).toMatch(
`return _${RENDER_LIST}(list, (v, k, i) => {
return _${TO_STRING}(v)
})`
)
expect(code).toMatchSnapshot()
})
test('forNode w/ prefixIdentifiers: true', () => {
const { code } = generate(
createRoot({
children: [
{
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)]
}
]
}),
{
prefixIdentifiers: true
}
)
expect(code).toMatch(
`return ${RENDER_LIST}(list, (v, k, i) => {
return ${TO_STRING}(v)
})`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -325,7 +400,11 @@ describe('compiler: codegen', () => {
] ]
}) })
) )
expect(code).toMatch(`renderList(list, (__value, k, i) => toString(v))`) expect(code).toMatch(
`return _${RENDER_LIST}(list, (__value, k, i) => {
return _${TO_STRING}(v)
})`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -345,7 +424,11 @@ describe('compiler: codegen', () => {
] ]
}) })
) )
expect(code).toMatch(`renderList(list, (v, __key, i) => toString(v))`) expect(code).toMatch(
`return _${RENDER_LIST}(list, (v, __key, i) => {
return _${TO_STRING}(v)
})`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -365,7 +448,11 @@ describe('compiler: codegen', () => {
] ]
}) })
) )
expect(code).toMatch(`renderList(list, (__value, __key, i) => toString(v))`) expect(code).toMatch(
`return _${RENDER_LIST}(list, (__value, __key, i) => {
return _${TO_STRING}(v)
})`
)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })

View File

@ -2,35 +2,138 @@ import { compile } from '../src'
import { SourceMapConsumer, RawSourceMap } from 'source-map' import { SourceMapConsumer, RawSourceMap } from 'source-map'
// Integration tests for parser + transform + codegen // Integration tests for parser + transform + codegen
test('basic source map support', async () => { test('function mode', async () => {
const source = `hello {{ world }}` const source = `
<div id="foo" :class="bar">
{{ world }}
<div v-if="ok">yes</div>
<template v-else>no</template>
<div v-for="(i, j) in list"><span>{{ i + j }}</span></div>
</div>
`.trim()
const { code, map } = compile(source, { const { code, map } = compile(source, {
sourceMap: true, sourceMap: true,
filename: `foo.vue` filename: `foo.vue`
}) })
expect(code).toMatch(
`const _Vue = Vue
return function render() {
with (this) {
const { toString } = _Vue
return [
"hello ",
toString(world)
]
}
}`
)
expect(code).toMatchSnapshot()
expect(map!.sources).toEqual([`foo.vue`]) expect(map!.sources).toEqual([`foo.vue`])
expect(map!.sourcesContent).toEqual([source]) expect(map!.sourcesContent).toEqual([source])
const consumer = await new SourceMapConsumer(map as RawSourceMap) const consumer = await new SourceMapConsumer(map as RawSourceMap)
const pos = consumer.originalPositionFor({
line: 7, // id=
column: 16 expect(
}) consumer.originalPositionFor({
expect(pos).toMatchObject({ line: 6,
line: 1,
column: 6 column: 6
}) })
).toMatchObject({
line: 1,
column: 5
})
// "foo"
expect(
consumer.originalPositionFor({
line: 6,
column: 10
})
).toMatchObject({
line: 1,
column: 8
})
// :class=
expect(
consumer.originalPositionFor({
line: 7,
column: 6
})
).toMatchObject({
line: 1,
column: 15
})
// bar
expect(
consumer.originalPositionFor({
line: 7,
column: 13
})
).toMatchObject({
line: 1,
column: 22
})
// {{ world }}
expect(
consumer.originalPositionFor({
line: 9,
column: 16
})
).toMatchObject({
line: 2,
column: 2
})
// ok
expect(
consumer.originalPositionFor({
line: 10,
column: 6
})
).toMatchObject({
line: 3,
column: 13
})
// i
expect(
consumer.originalPositionFor({
line: 13,
column: 25
})
).toMatchObject({
line: 5,
column: 15
})
// j
expect(
consumer.originalPositionFor({
line: 13,
column: 28
})
).toMatchObject({
line: 5,
column: 18
})
// list
expect(
consumer.originalPositionFor({
line: 13,
column: 18
})
).toMatchObject({
line: 5,
column: 24
})
// i + j
expect(
consumer.originalPositionFor({
line: 14,
column: 81
})
).toMatchObject({
line: 5,
column: 36
})
}) })
test.todo('function mode w/ prefixIdentifiers: true')
test.todo('module mode')
test.todo('module mode w/ prefixIdentifiers: true')

View File

@ -894,8 +894,8 @@ describe('compiler: parse', () => {
isStatic: false, isStatic: false,
isInterpolation: false, isInterpolation: false,
loc: { loc: {
start: { offset: 10, line: 1, column: 11 }, start: { offset: 11, line: 1, column: 12 },
end: { offset: 13, line: 1, column: 14 }, end: { offset: 12, line: 1, column: 13 },
source: '"a"' source: '"a"'
} }
}, },
@ -1303,25 +1303,27 @@ describe('compiler: parse', () => {
test('parse with correct location info', () => { test('parse with correct location info', () => {
const [foo, bar, but, baz] = parse( const [foo, bar, but, baz] = parse(
'foo \n is {{ bar }} but {{ baz }}' `
foo
is {{ bar }} but {{ baz }}`.trim()
).children ).children
let offset = 0 let offset = 0
expect(foo.loc.start).toEqual({ line: 1, column: 1, offset }) expect(foo.loc.start).toEqual({ line: 1, column: 1, offset })
offset += foo.loc.source.length offset += foo.loc.source.length
expect(foo.loc.end).toEqual({ line: 2, column: 4, offset }) expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
expect(bar.loc.start).toEqual({ line: 2, column: 4, offset }) expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
offset += bar.loc.source.length offset += bar.loc.source.length
expect(bar.loc.end).toEqual({ line: 2, column: 13, offset }) expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
expect(but.loc.start).toEqual({ line: 2, column: 13, 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: 18, offset }) expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
expect(baz.loc.start).toEqual({ line: 2, column: 18, offset }) expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
offset += baz.loc.source.length offset += baz.loc.source.length
expect(baz.loc.end).toEqual({ line: 2, column: 27, offset }) expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
}) })
describe('namedCharacterReferences option', () => { describe('namedCharacterReferences option', () => {

View File

@ -3,8 +3,7 @@ import {
CompilerOptions, CompilerOptions,
parse, parse,
transform, transform,
ErrorCodes, ErrorCodes
compile
} from '../../src' } from '../../src'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { import {
@ -74,7 +73,7 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform( const { root, node } = parseWithElementTransform(
`<div id="foo" class="bar" />` `<div id="foo" class="bar" />`
) )
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
// should hoist the static object // should hoist the static object
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
createStaticObjectMatcher({ createStaticObjectMatcher({
@ -95,7 +94,7 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform( const { root, node } = parseWithElementTransform(
`<div id="foo"><span/></div>` `<div id="foo"><span/></div>`
) )
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
createStaticObjectMatcher({ createStaticObjectMatcher({
id: 'foo' id: 'foo'
@ -112,7 +111,7 @@ describe('compiler: element transform', () => {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: 'span', tag: 'span',
codegenNode: { codegenNode: {
callee: CREATE_VNODE, callee: `_${CREATE_VNODE}`,
arguments: [`"span"`] arguments: [`"span"`]
} }
} }
@ -122,7 +121,7 @@ describe('compiler: element transform', () => {
test('0 placeholder for children with no props', () => { test('0 placeholder for children with no props', () => {
const { node } = parseWithElementTransform(`<div><span/></div>`) const { node } = parseWithElementTransform(`<div><span/></div>`)
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
`"div"`, `"div"`,
`0`, `0`,
@ -131,7 +130,7 @@ describe('compiler: element transform', () => {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: 'span', tag: 'span',
codegenNode: { codegenNode: {
callee: CREATE_VNODE, callee: `_${CREATE_VNODE}`,
arguments: [`"span"`] arguments: [`"span"`]
} }
} }
@ -143,7 +142,7 @@ describe('compiler: element transform', () => {
const { root, node } = parseWithElementTransform(`<div v-bind="obj" />`) const { root, node } = parseWithElementTransform(`<div v-bind="obj" />`)
// single v-bind doesn't need mergeProps // single v-bind doesn't need mergeProps
expect(root.imports).not.toContain(MERGE_PROPS) expect(root.imports).not.toContain(MERGE_PROPS)
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.EXPRESSION,
@ -156,10 +155,10 @@ describe('compiler: element transform', () => {
`<div id="foo" v-bind="obj" />` `<div id="foo" v-bind="obj" />`
) )
expect(root.imports).toContain(MERGE_PROPS) expect(root.imports).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createStaticObjectMatcher({
id: 'foo' id: 'foo'
@ -177,10 +176,10 @@ describe('compiler: element transform', () => {
`<div v-bind="obj" id="foo" />` `<div v-bind="obj" id="foo" />`
) )
expect(root.imports).toContain(MERGE_PROPS) expect(root.imports).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.EXPRESSION,
@ -198,10 +197,10 @@ describe('compiler: element transform', () => {
`<div id="foo" v-bind="obj" class="bar" />` `<div id="foo" v-bind="obj" class="bar" />`
) )
expect(root.imports).toContain(MERGE_PROPS) expect(root.imports).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createStaticObjectMatcher({
id: 'foo' id: 'foo'
@ -222,17 +221,17 @@ describe('compiler: element transform', () => {
`<div id="foo" v-on="obj" class="bar" />` `<div id="foo" v-on="obj" class="bar" />`
) )
expect(root.imports).toContain(MERGE_PROPS) expect(root.imports).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createStaticObjectMatcher({
id: 'foo' id: 'foo'
}), }),
{ {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: TO_HANDLERS, callee: `_${TO_HANDLERS}`,
arguments: [ arguments: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.EXPRESSION,
@ -252,17 +251,17 @@ describe('compiler: element transform', () => {
`<div id="foo" v-on="handlers" v-bind="obj" />` `<div id="foo" v-on="handlers" v-bind="obj" />`
) )
expect(root.imports).toContain(MERGE_PROPS) expect(root.imports).toContain(MERGE_PROPS)
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createStaticObjectMatcher({
id: 'foo' id: 'foo'
}), }),
{ {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: TO_HANDLERS, callee: `_${TO_HANDLERS}`,
arguments: [ arguments: [
{ {
type: NodeTypes.EXPRESSION, type: NodeTypes.EXPRESSION,
@ -301,7 +300,7 @@ describe('compiler: element transform', () => {
} }
} }
}) })
expect(node.callee).toBe(CREATE_VNODE) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments[1]).toMatchObject({ expect(node.arguments[1]).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [ properties: [
@ -333,11 +332,11 @@ describe('compiler: element transform', () => {
expect(root.imports).toContain(RESOLVE_DIRECTIVE) expect(root.imports).toContain(RESOLVE_DIRECTIVE)
expect(root.statements[0]).toMatch(`${RESOLVE_DIRECTIVE}("foo")`) expect(root.statements[0]).toMatch(`${RESOLVE_DIRECTIVE}("foo")`)
expect(node.callee).toBe(APPLY_DIRECTIVES) expect(node.callee).toBe(`_${APPLY_DIRECTIVES}`)
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
{ {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_VNODE, callee: `_${CREATE_VNODE}`,
arguments: [ arguments: [
`"div"`, `"div"`,
{ {
@ -388,7 +387,7 @@ describe('compiler: element transform', () => {
expect(root.statements[1]).toMatch(`${RESOLVE_DIRECTIVE}("bar")`) expect(root.statements[1]).toMatch(`${RESOLVE_DIRECTIVE}("bar")`)
expect(root.statements[2]).toMatch(`${RESOLVE_DIRECTIVE}("baz")`) expect(root.statements[2]).toMatch(`${RESOLVE_DIRECTIVE}("baz")`)
expect(node.callee).toBe(APPLY_DIRECTIVES) expect(node.callee).toBe(`_${APPLY_DIRECTIVES}`)
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
{ {
type: NodeTypes.JS_CALL_EXPRESSION type: NodeTypes.JS_CALL_EXPRESSION
@ -467,13 +466,7 @@ describe('compiler: element transform', () => {
]) ])
}) })
test('props dedupe', () => { test.todo(`props dedupe`)
const { code } = compile(
`<div class="a" :class="b" @click.foo="a" @click.bar="b" style="color: red" />
<div id="foo"/>`
)
console.log(code)
})
test.todo('slot outlets') test.todo('slot outlets')
}) })

View File

@ -49,11 +49,11 @@ describe('compiler: transform v-bind', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 16 column: 17
}, },
end: { end: {
line: 1, line: 1,
column: 20 column: 19
} }
} }
}, },

View File

@ -212,150 +212,155 @@ describe('compiler: transform v-for', () => {
const source = '<span v-for="item in items" />' const source = '<span v-for="item in items" />'
const forNode = parseWithForTransform(source) const forNode = parseWithForTransform(source)
const itemOffset = source.indexOf('item')
expect(forNode.valueAlias!.content).toBe('item') expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe( expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset)
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1) expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1)
expect(forNode.valueAlias!.loc.end.line).toBe(1) expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe( expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4 itemOffset + 1 + `item`.length
) )
const itemsOffset = source.indexOf('items')
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) 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(source.indexOf('items')) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length
)
}) })
test('bracketed value', () => { test('bracketed value', () => {
const source = '<span v-for="( item ) in items" />' const source = '<span v-for="( item ) in items" />'
const forNode = parseWithForTransform(source) const forNode = parseWithForTransform(source)
const itemOffset = source.indexOf('item')
expect(forNode.valueAlias!.content).toBe('item') expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe( expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset)
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1) expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1)
expect(forNode.valueAlias!.loc.end.line).toBe(1) expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe( expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4 itemOffset + 1 + `item`.length
) )
const itemsOffset = source.indexOf('items')
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) 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(source.indexOf('items')) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length
)
}) })
test('de-structured value', () => { test('de-structured value', () => {
const source = '<span v-for="( { id, key })in items" />' const source = '<span v-for="( { id, key })in items" />'
const forNode = parseWithForTransform(source) const forNode = parseWithForTransform(source)
const valueIndex = source.indexOf('{ id, key }')
expect(forNode.valueAlias!.content).toBe('{ id, key }') expect(forNode.valueAlias!.content).toBe('{ id, key }')
expect(forNode.valueAlias!.loc.start.offset).toBe( expect(forNode.valueAlias!.loc.start.offset).toBe(valueIndex)
source.indexOf('{ id, key }') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1) expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe( expect(forNode.valueAlias!.loc.start.column).toBe(valueIndex + 1)
source.indexOf('{ id, key }')
)
expect(forNode.valueAlias!.loc.end.line).toBe(1) expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe( expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('{ id, key }') + '{ id, key }'.length valueIndex + 1 + '{ id, key }'.length
) )
const itemsOffset = source.indexOf('items')
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) 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(source.indexOf('items')) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length
)
}) })
test('bracketed value, key, index', () => { test('bracketed value, key, index', () => {
const source = '<span v-for="( item, key, index ) in items" />' const source = '<span v-for="( item, key, index ) in items" />'
const forNode = parseWithForTransform(source) const forNode = parseWithForTransform(source)
const itemOffset = source.indexOf('item')
expect(forNode.valueAlias!.content).toBe('item') expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe( expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset)
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1) expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1)
expect(forNode.valueAlias!.loc.end.line).toBe(1) expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe( expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4 itemOffset + 1 + `item`.length
) )
const keyOffset = source.indexOf('key')
expect(forNode.keyAlias!.content).toBe('key') expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.keyAlias!.loc.start.offset).toBe(source.indexOf('key') - 1) expect(forNode.keyAlias!.loc.start.offset).toBe(keyOffset)
expect(forNode.keyAlias!.loc.start.line).toBe(1) expect(forNode.keyAlias!.loc.start.line).toBe(1)
expect(forNode.keyAlias!.loc.start.column).toBe(source.indexOf('key')) expect(forNode.keyAlias!.loc.start.column).toBe(keyOffset + 1)
expect(forNode.keyAlias!.loc.end.line).toBe(1) expect(forNode.keyAlias!.loc.end.line).toBe(1)
expect(forNode.keyAlias!.loc.end.column).toBe(source.indexOf('key') + 3) expect(forNode.keyAlias!.loc.end.column).toBe(
keyOffset + 1 + `key`.length
)
const indexOffset = source.indexOf('index')
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.objectIndexAlias!.loc.start.offset).toBe( expect(forNode.objectIndexAlias!.loc.start.offset).toBe(indexOffset)
source.indexOf('index') - 1
)
expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.start.column).toBe( expect(forNode.objectIndexAlias!.loc.start.column).toBe(indexOffset + 1)
source.indexOf('index')
)
expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.end.column).toBe( expect(forNode.objectIndexAlias!.loc.end.column).toBe(
source.indexOf('index') + 5 indexOffset + 1 + `index`.length
) )
const itemsOffset = source.indexOf('items')
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) 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(source.indexOf('items')) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length
)
}) })
test('skipped key', () => { test('skipped key', () => {
const source = '<span v-for="( item,, index ) in items" />' const source = '<span v-for="( item,, index ) in items" />'
const forNode = parseWithForTransform(source) const forNode = parseWithForTransform(source)
const itemOffset = source.indexOf('item')
expect(forNode.valueAlias!.content).toBe('item') expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe( expect(forNode.valueAlias!.loc.start.offset).toBe(itemOffset)
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1) expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) expect(forNode.valueAlias!.loc.start.column).toBe(itemOffset + 1)
expect(forNode.valueAlias!.loc.end.line).toBe(1) expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe( expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4 itemOffset + 1 + `item`.length
) )
const indexOffset = source.indexOf('index')
expect(forNode.objectIndexAlias!.content).toBe('index') expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.objectIndexAlias!.loc.start.offset).toBe( expect(forNode.objectIndexAlias!.loc.start.offset).toBe(indexOffset)
source.indexOf('index') - 1
)
expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.start.column).toBe( expect(forNode.objectIndexAlias!.loc.start.column).toBe(indexOffset + 1)
source.indexOf('index')
)
expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.end.column).toBe( expect(forNode.objectIndexAlias!.loc.end.column).toBe(
source.indexOf('index') + 5 indexOffset + 1 + `index`.length
) )
const itemsOffset = source.indexOf('items')
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) 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(source.indexOf('items')) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length
)
}) })
}) })
}) })

View File

@ -29,9 +29,7 @@ describe('compiler: transform v-if', () => {
test('basic v-if', () => { test('basic 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.isRoot).toBe(true)
expect(node.branches.length).toBe(1) expect(node.branches.length).toBe(1)
expect(node.branches[0].isRoot).toBe(true)
expect(node.branches[0].condition!.content).toBe(`ok`) expect(node.branches[0].condition!.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)

View File

@ -50,11 +50,11 @@ describe('compiler: transform v-bind', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 17 column: 18
}, },
end: { end: {
line: 1, line: 1,
column: 26 column: 25
} }
} }
}, },

View File

@ -28,7 +28,7 @@ describe('advancePositionWithClone', () => {
const pos = p(1, 1, 0) const pos = p(1, 1, 0)
const newPos = advancePositionWithClone(pos, 'foo\nbar\nbaz', 10) const newPos = advancePositionWithClone(pos, 'foo\nbar\nbaz', 10)
expect(newPos.column).toBe(2) expect(newPos.column).toBe(3)
expect(newPos.line).toBe(3) expect(newPos.line).toBe(3)
expect(newPos.offset).toBe(10) expect(newPos.offset).toBe(10)
}) })
@ -62,7 +62,7 @@ describe('getInnerRange', () => {
expect(loc2.start.column).toBe(1) expect(loc2.start.column).toBe(1)
expect(loc2.start.line).toBe(2) expect(loc2.start.line).toBe(2)
expect(loc2.start.offset).toBe(4) expect(loc2.start.offset).toBe(4)
expect(loc2.end.column).toBe(3) expect(loc2.end.column).toBe(4)
expect(loc2.end.line).toBe(2) expect(loc2.end.line).toBe(2)
expect(loc2.end.offset).toBe(7) expect(loc2.end.offset).toBe(7)
}) })

View File

@ -118,14 +118,12 @@ export interface ExpressionNode extends Node {
export interface IfNode extends Node { export interface IfNode extends Node {
type: NodeTypes.IF type: NodeTypes.IF
branches: IfBranchNode[] branches: IfBranchNode[]
isRoot: boolean
} }
export interface IfBranchNode extends Node { export interface IfBranchNode extends Node {
type: NodeTypes.IF_BRANCH type: NodeTypes.IF_BRANCH
condition: ExpressionNode | undefined // else condition: ExpressionNode | undefined // else
children: ChildNode[] children: ChildNode[]
isRoot: boolean
} }
export interface ForNode extends Node { export interface ForNode extends Node {

View File

@ -65,6 +65,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
offset: number offset: number
indentLevel: number indentLevel: number
map?: SourceMapGenerator map?: SourceMapGenerator
helper(name: string): string
push(code: string, node?: CodegenNode): void push(code: string, node?: CodegenNode): void
indent(): void indent(): void
deindent(withoutNewLine?: boolean): void deindent(withoutNewLine?: boolean): void
@ -98,11 +99,14 @@ function createCodegenContext(
? undefined ? undefined
: new (require('source-map')).SourceMapGenerator(), : new (require('source-map')).SourceMapGenerator(),
helper(name) {
return prefixIdentifiers ? name : `_${name}`
},
push(code, node?: CodegenNode) { push(code, node?: CodegenNode) {
context.code += code context.code += code
if (context.map) { if (context.map) {
if (node) { if (node) {
context.map.addMapping({ const mapping = {
source: context.filename, source: context.filename,
original: { original: {
line: node.loc.start.line, line: node.loc.start.line,
@ -112,9 +116,10 @@ function createCodegenContext(
line: context.line, line: context.line,
column: context.column - 1 column: context.column - 1
} }
})
} }
advancePositionWithMutation(context, code, code.length) context.map.addMapping(mapping)
}
advancePositionWithMutation(context, code)
} }
}, },
indent() { indent() {
@ -144,7 +149,7 @@ export function generate(
): CodegenResult { ): CodegenResult {
const context = createCodegenContext(ast, options) const context = createCodegenContext(ast, options)
const { mode, push, prefixIdentifiers, indent, deindent, newline } = context const { mode, push, prefixIdentifiers, indent, deindent, newline } = context
const imports = ast.imports.join(', ') const hasImports = ast.imports.length
// preambles // preambles
if (mode === 'function') { if (mode === 'function') {
@ -152,9 +157,9 @@ export function generate(
// In prefix mode, we place the const declaration at top so it's done // In prefix mode, we place the const declaration at top so it's done
// only once; But if we not prefixing, we place the decalration inside the // only once; But if we not prefixing, we place the decalration inside the
// with block so it doesn't incur the `in` check cost for every helper access. // with block so it doesn't incur the `in` check cost for every helper access.
if (imports) { if (hasImports) {
if (prefixIdentifiers) { if (prefixIdentifiers) {
push(`const { ${imports} } = Vue\n`) push(`const { ${ast.imports.join(', ')} } = Vue\n`)
} else { } else {
// save Vue in a separate variable to avoid collision // save Vue in a separate variable to avoid collision
push(`const _Vue = Vue`) push(`const _Vue = Vue`)
@ -164,8 +169,8 @@ export function generate(
push(`return `) push(`return `)
} else { } else {
// generate import statements for helpers // generate import statements for helpers
if (imports) { if (hasImports) {
push(`import { ${imports} } from 'vue'\n`) push(`import { ${ast.imports.join(', ')} } from 'vue'\n`)
} }
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
push(`export default `) push(`export default `)
@ -179,8 +184,9 @@ export function generate(
push(`with (this) {`) push(`with (this) {`)
indent() indent()
// function mode const declarations should be inside with block // function mode const declarations should be inside with block
if (mode === 'function' && imports) { // also they should be renamed to avoid collision with user properties
push(`const { ${imports} } = _Vue`) if (mode === 'function' && hasImports) {
push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`)
newline() newline()
} }
} else { } else {
@ -199,7 +205,7 @@ export function generate(
// generate the VNode tree expression // generate the VNode tree expression
push(`return `) push(`return `)
genChildren(ast.children, context, true /* asRoot */) genChildren(ast.children, context, true)
if (!prefixIdentifiers) { if (!prefixIdentifiers) {
deindent() deindent()
push(`}`) push(`}`)
@ -223,13 +229,12 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
} }
// This will generate a single vnode call if: // This will generate a single vnode call if:
// - The list has length === 1, AND: // - The target position explicitly allows a single node (root, if, for)
// - This is a root node, OR: // - The list has length === 1, AND The only child is a text or expression.
// - The only child is a text or expression.
function genChildren( function genChildren(
children: ChildNode[], children: ChildNode[],
context: CodegenContext, context: CodegenContext,
asRoot: boolean = false allowSingle: boolean = false
) { ) {
if (!children.length) { if (!children.length) {
return context.push(`null`) return context.push(`null`)
@ -237,7 +242,7 @@ function genChildren(
const child = children[0] const child = children[0]
if ( if (
children.length === 1 && children.length === 1 &&
(asRoot || (allowSingle ||
child.type === NodeTypes.TEXT || child.type === NodeTypes.TEXT ||
child.type == NodeTypes.EXPRESSION) child.type == NodeTypes.EXPRESSION)
) { ) {
@ -336,10 +341,10 @@ function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
} }
function genExpression(node: ExpressionNode, context: CodegenContext) { function genExpression(node: ExpressionNode, context: CodegenContext) {
const { push } = context const { push, helper } = context
const { content, children, isStatic, isInterpolation } = node const { content, children, isStatic, isInterpolation } = node
if (isInterpolation) { if (isInterpolation) {
push(`${TO_STRING}(`) push(`${helper(TO_STRING)}(`)
} }
if (children) { if (children) {
genCompoundExpression(node, context) genCompoundExpression(node, context)
@ -383,8 +388,11 @@ function genCompoundExpression(node: ExpressionNode, context: CodegenContext) {
function genComment(node: CommentNode, context: CodegenContext) { function genComment(node: CommentNode, context: CodegenContext) {
if (__DEV__) { if (__DEV__) {
context.push( const { push, helper } = context
`${CREATE_VNODE}(${COMMENT}, 0, ${JSON.stringify(node.content)})`, push(
`${helper(CREATE_VNODE)}(${helper(COMMENT)}, 0, ${JSON.stringify(
node.content
)})`,
node node
) )
} }
@ -396,7 +404,7 @@ function genIf(node: IfNode, context: CodegenContext) {
} }
function genIfBranch( function genIfBranch(
{ condition, children, isRoot }: IfBranchNode, { condition, children }: IfBranchNode,
branches: IfBranchNode[], branches: IfBranchNode[],
nextIndex: number, nextIndex: number,
context: CodegenContext context: CodegenContext
@ -411,7 +419,7 @@ function genIfBranch(
indent() indent()
context.indentLevel++ context.indentLevel++
push(`? `) push(`? `)
genChildren(children, context, isRoot) genChildren(children, context, true)
context.indentLevel-- context.indentLevel--
newline() newline()
push(`: `) push(`: `)
@ -424,14 +432,14 @@ function genIfBranch(
} else { } else {
// v-else // v-else
__DEV__ && assert(nextIndex === branches.length) __DEV__ && assert(nextIndex === branches.length)
genChildren(children, context, isRoot) genChildren(children, context, true)
} }
} }
function genFor(node: ForNode, context: CodegenContext) { function genFor(node: ForNode, context: CodegenContext) {
const { push } = context const { push, helper, indent, deindent } = context
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
push(`${RENDER_LIST}(`, node) push(`${helper(RENDER_LIST)}(`, node)
genExpression(source, context) genExpression(source, context)
push(`, (`) push(`, (`)
if (valueAlias) { if (valueAlias) {
@ -455,9 +463,12 @@ function genFor(node: ForNode, context: CodegenContext) {
push(`, `) push(`, `)
genExpression(objectIndexAlias, context) genExpression(objectIndexAlias, context)
} }
push(`) => `) push(`) => {`)
genChildren(children, context) indent()
push(`)`) push(`return `)
genChildren(children, context, true)
deindent()
push(`})`)
} }
// JavaScript // JavaScript

View File

@ -479,7 +479,14 @@ function parseAttribute(
advanceBy(context, name.length) advanceBy(context, name.length)
// Value // Value
let value: { content: string; loc: SourceLocation } | undefined = undefined let value:
| {
content: string
isQuoted: boolean
loc: SourceLocation
}
| undefined = undefined
if (/^[\t\r\n\f ]*=/.test(context.source)) { if (/^[\t\r\n\f ]*=/.test(context.source)) {
advanceSpaces(context) advanceSpaces(context)
advanceBy(context, 1) advanceBy(context, 1)
@ -530,6 +537,13 @@ function parseAttribute(
} }
} }
if (value && value.isQuoted) {
const valueLoc = value.loc
valueLoc.start.offset++
valueLoc.start.column++
valueLoc.end = advancePositionWithClone(valueLoc.start, value.content)
}
return { return {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
name: name:
@ -567,13 +581,20 @@ function parseAttribute(
function parseAttributeValue( function parseAttributeValue(
context: ParserContext context: ParserContext
): { content: string; loc: SourceLocation } | undefined { ):
| {
content: string
isQuoted: boolean
loc: SourceLocation
}
| undefined {
const start = getCursor(context) const start = getCursor(context)
let content: string let content: string
if (/^["']/.test(context.source)) {
// Quoted value.
const quote = context.source[0] const quote = context.source[0]
const isQuoted = quote === `"` || quote === `'`
if (isQuoted) {
// Quoted value.
advanceBy(context, 1) advanceBy(context, 1)
const endIndex = context.source.indexOf(quote) const endIndex = context.source.indexOf(quote)
@ -605,7 +626,7 @@ function parseAttributeValue(
content = parseTextData(context, match[0].length, TextModes.ATTRIBUTE_VALUE) content = parseTextData(context, match[0].length, TextModes.ATTRIBUTE_VALUE)
} }
return { content, loc: getSelection(context, start) } return { content, isQuoted, loc: getSelection(context, start) }
} }
function parseInterpolation( function parseInterpolation(

View File

@ -59,6 +59,7 @@ export interface TransformContext extends Required<TransformOptions> {
parent: ParentNode parent: ParentNode
childIndex: number childIndex: number
currentNode: ChildNode | null currentNode: ChildNode | null
helper(name: string): string
replaceNode(node: ChildNode): void replaceNode(node: ChildNode): void
removeNode(node?: ChildNode): void removeNode(node?: ChildNode): void
onNodeRemoved: () => void onNodeRemoved: () => void
@ -89,6 +90,10 @@ function createTransformContext(
parent: root, parent: root,
childIndex: 0, childIndex: 0,
currentNode: null, currentNode: null,
helper(name) {
context.imports.add(name)
return prefixIdentifiers ? name : `_${name}`
},
replaceNode(node) { replaceNode(node) {
/* istanbul ignore if */ /* istanbul ignore if */
if (__DEV__ && !context.currentNode) { if (__DEV__ && !context.currentNode) {
@ -195,15 +200,15 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
switch (node.type) { switch (node.type) {
case NodeTypes.COMMENT: case NodeTypes.COMMENT:
context.imports.add(CREATE_VNODE) context.helper(CREATE_VNODE)
// inject import for the Comment symbol, which is needed for creating // inject import for the Comment symbol, which is needed for creating
// comment nodes with `createVNode` // comment nodes with `createVNode`
context.imports.add(COMMENT) context.helper(COMMENT)
break break
case NodeTypes.EXPRESSION: case NodeTypes.EXPRESSION:
// 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) { if (node.isInterpolation) {
context.imports.add(TO_STRING) context.helper(TO_STRING)
} }
break break

View File

@ -42,12 +42,11 @@ export const transformElement: NodeTransform = (node, context) => {
let componentIdentifier: string | undefined let componentIdentifier: string | undefined
if (isComponent) { if (isComponent) {
context.imports.add(RESOLVE_COMPONENT)
componentIdentifier = `_component_${toValidId(node.tag)}` componentIdentifier = `_component_${toValidId(node.tag)}`
context.statements.push( context.statements.push(
`const ${componentIdentifier} = ${RESOLVE_COMPONENT}(${JSON.stringify( `const ${componentIdentifier} = ${context.helper(
node.tag RESOLVE_COMPONENT
)})` )}(${JSON.stringify(node.tag)})`
) )
} }
@ -70,13 +69,15 @@ export const transformElement: NodeTransform = (node, context) => {
} }
const { loc } = node const { loc } = node
context.imports.add(CREATE_VNODE) const vnode = createCallExpression(
const vnode = createCallExpression(CREATE_VNODE, args, loc) context.helper(CREATE_VNODE),
args,
loc
)
if (runtimeDirectives && runtimeDirectives.length) { if (runtimeDirectives && runtimeDirectives.length) {
context.imports.add(APPLY_DIRECTIVES)
node.codegenNode = createCallExpression( node.codegenNode = createCallExpression(
APPLY_DIRECTIVES, context.helper(APPLY_DIRECTIVES),
[ [
vnode, vnode,
createArrayExpression( createArrayExpression(
@ -147,11 +148,10 @@ function buildProps(
mergeArgs.push(exp) mergeArgs.push(exp)
} else { } else {
// v-on="obj" -> toHandlers(obj) // v-on="obj" -> toHandlers(obj)
context.imports.add(TO_HANDLERS)
mergeArgs.push({ mergeArgs.push({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
loc, loc,
callee: TO_HANDLERS, callee: context.helper(TO_HANDLERS),
arguments: [exp] arguments: [exp]
}) })
} }
@ -197,8 +197,11 @@ function buildProps(
) )
} }
if (mergeArgs.length > 1) { if (mergeArgs.length > 1) {
context.imports.add(MERGE_PROPS) propsExpression = createCallExpression(
propsExpression = createCallExpression(MERGE_PROPS, mergeArgs, elementLoc) context.helper(MERGE_PROPS),
mergeArgs,
elementLoc
)
} else { } else {
// single v-bind with nothing else - no need for a mergeProps call // single v-bind with nothing else - no need for a mergeProps call
propsExpression = mergeArgs[0] propsExpression = mergeArgs[0]
@ -285,12 +288,12 @@ function createDirectiveArgs(
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext context: TransformContext
): ArrayExpression { ): ArrayExpression {
// inject import for `resolveDirective`
context.imports.add(RESOLVE_DIRECTIVE)
// inject statement for resolving directive // inject statement for resolving directive
const dirIdentifier = `_directive_${toValidId(dir.name)}` const dirIdentifier = `_directive_${toValidId(dir.name)}`
context.statements.push( context.statements.push(
`const ${dirIdentifier} = ${RESOLVE_DIRECTIVE}(${JSON.stringify(dir.name)})` `const ${dirIdentifier} = ${context.helper(
RESOLVE_DIRECTIVE
)}(${JSON.stringify(dir.name)})`
) )
const dirArgs: ArrayExpression['elements'] = [dirIdentifier] const dirArgs: ArrayExpression['elements'] = [dirIdentifier]
const { loc } = dir const { loc } = dir

View File

@ -24,7 +24,7 @@ export const transformFor = createStructuralDirectiveTransform(
const parseResult = parseForExpression(dir.exp, context) const parseResult = parseForExpression(dir.exp, context)
if (parseResult) { if (parseResult) {
context.imports.add(RENDER_LIST) context.helper(RENDER_LIST)
const { source, value, key, index } = parseResult const { source, value, key, index } = parseResult
context.replaceNode({ context.replaceNode({

View File

@ -19,14 +19,10 @@ export const transformIf = createStructuralDirectiveTransform(
processExpression(dir.exp, context) processExpression(dir.exp, context)
} }
if (dir.name === 'if') { if (dir.name === 'if') {
// check if this v-if is root - so that in codegen we can avoid generating
// arrays for each branch
const isRoot = context.parent === context.root
context.replaceNode({ context.replaceNode({
type: NodeTypes.IF, type: NodeTypes.IF,
loc: node.loc, loc: node.loc,
branches: [createIfBranch(node, dir, isRoot)], branches: [createIfBranch(node, dir)]
isRoot
}) })
} else { } else {
// locate the adjacent v-if // locate the adjacent v-if
@ -43,7 +39,7 @@ export const transformIf = createStructuralDirectiveTransform(
if (sibling && sibling.type === NodeTypes.IF) { if (sibling && sibling.type === NodeTypes.IF) {
// move the node to the if node's branches // move the node to the if node's branches
context.removeNode() context.removeNode()
const branch = createIfBranch(node, dir, sibling.isRoot) const branch = createIfBranch(node, dir)
if (__DEV__ && comments.length) { if (__DEV__ && comments.length) {
branch.children = [...comments, ...branch.children] branch.children = [...comments, ...branch.children]
} }
@ -67,16 +63,11 @@ export const transformIf = createStructuralDirectiveTransform(
} }
) )
function createIfBranch( function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
node: ElementNode,
dir: DirectiveNode,
isRoot: boolean
): IfBranchNode {
return { return {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
loc: node.loc, loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp, condition: dir.name === 'else' ? undefined : dir.exp,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node], children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
isRoot
} }
} }

View File

@ -31,7 +31,7 @@ export function getInnerRange(
export function advancePositionWithClone( export function advancePositionWithClone(
pos: Position, pos: Position,
source: string, source: string,
numberOfCharacters: number numberOfCharacters: number = source.length
): Position { ): Position {
return advancePositionWithMutation({ ...pos }, source, numberOfCharacters) return advancePositionWithMutation({ ...pos }, source, numberOfCharacters)
} }
@ -41,7 +41,7 @@ export function advancePositionWithClone(
export function advancePositionWithMutation( export function advancePositionWithMutation(
pos: Position, pos: Position,
source: string, source: string,
numberOfCharacters: number numberOfCharacters: number = source.length
): Position { ): Position {
let linesCount = 0 let linesCount = 0
let lastNewLinePos = -1 let lastNewLinePos = -1
@ -57,7 +57,7 @@ export function advancePositionWithMutation(
pos.column = pos.column =
lastNewLinePos === -1 lastNewLinePos === -1
? pos.column + numberOfCharacters ? pos.column + numberOfCharacters
: Math.max(1, numberOfCharacters - lastNewLinePos - 1) : Math.max(1, numberOfCharacters - lastNewLinePos)
return pos return pos
} }