test: finish tests for transformExpression
This commit is contained in:
parent
4a82e7cdbc
commit
f5b3f580f1
@ -3,7 +3,9 @@ import {
|
|||||||
transform,
|
transform,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
DirectiveNode
|
DirectiveNode,
|
||||||
|
NodeTypes,
|
||||||
|
ForNode
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { transformFor } from '../..//src/transforms/vFor'
|
import { transformFor } from '../..//src/transforms/vFor'
|
||||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
@ -20,30 +22,63 @@ function parseWithExpressionTransform(template: string) {
|
|||||||
describe('compiler: expression transform', () => {
|
describe('compiler: expression transform', () => {
|
||||||
test('interpolation (root)', () => {
|
test('interpolation (root)', () => {
|
||||||
const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode
|
const node = parseWithExpressionTransform(`{{ foo }}`) as ExpressionNode
|
||||||
expect(node.content).toBe(`_ctx.foo`)
|
expect(node.children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{
|
||||||
|
content: `foo`,
|
||||||
|
loc: node.loc
|
||||||
|
}
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('interpolation (children)', () => {
|
test('interpolation (children)', () => {
|
||||||
const node = parseWithExpressionTransform(
|
const node = parseWithExpressionTransform(
|
||||||
`<div>{{ foo }}</div>`
|
`<div>{{ foo }}</div>`
|
||||||
) as ElementNode
|
) as ElementNode
|
||||||
expect((node.children[0] as ExpressionNode).content).toBe(`_ctx.foo`)
|
expect((node.children[0] as ExpressionNode).children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{
|
||||||
|
content: `foo`,
|
||||||
|
loc: node.children[0].loc
|
||||||
|
}
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('directive value', () => {
|
test('directive value', () => {
|
||||||
const node = parseWithExpressionTransform(
|
const node = parseWithExpressionTransform(
|
||||||
`<div v-foo:arg="baz"/>`
|
`<div v-foo:arg="baz"/>`
|
||||||
) as ElementNode
|
) as ElementNode
|
||||||
expect((node.props[0] as DirectiveNode).arg!.content).toBe(`arg`)
|
expect((node.props[0] as DirectiveNode).arg!.children).toBeUndefined()
|
||||||
expect((node.props[0] as DirectiveNode).exp!.content).toBe(`_ctx.baz`)
|
const exp = (node.props[0] as DirectiveNode).exp!
|
||||||
|
expect(exp.children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{
|
||||||
|
content: `baz`,
|
||||||
|
loc: exp.loc
|
||||||
|
}
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('dynamic directive arg', () => {
|
test('dynamic directive arg', () => {
|
||||||
const node = parseWithExpressionTransform(
|
const node = parseWithExpressionTransform(
|
||||||
`<div v-foo:[arg]="baz"/>`
|
`<div v-foo:[arg]="baz"/>`
|
||||||
) as ElementNode
|
) as ElementNode
|
||||||
expect((node.props[0] as DirectiveNode).arg!.content).toBe(`_ctx.arg`)
|
const arg = (node.props[0] as DirectiveNode).arg!
|
||||||
expect((node.props[0] as DirectiveNode).exp!.content).toBe(`_ctx.baz`)
|
const exp = (node.props[0] as DirectiveNode).exp!
|
||||||
|
expect(arg.children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{
|
||||||
|
content: `arg`,
|
||||||
|
loc: arg.loc
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expect(exp.children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{
|
||||||
|
content: `baz`,
|
||||||
|
loc: exp.loc
|
||||||
|
}
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should prefix complex expressions', () => {
|
test('should prefix complex expressions', () => {
|
||||||
@ -52,42 +87,214 @@ describe('compiler: expression transform', () => {
|
|||||||
) as ExpressionNode
|
) as ExpressionNode
|
||||||
// should parse into compound expression
|
// should parse into compound expression
|
||||||
expect(node.children).toMatchObject([
|
expect(node.children).toMatchObject([
|
||||||
{ content: `_ctx.foo` },
|
`_ctx.`,
|
||||||
`(`,
|
{
|
||||||
{ content: `_ctx.baz` },
|
content: `foo`,
|
||||||
` + 1, { key: `,
|
loc: {
|
||||||
{ content: `_ctx.kuz` },
|
source: `foo`,
|
||||||
|
start: {
|
||||||
|
offset: 3,
|
||||||
|
line: 1,
|
||||||
|
column: 4
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: 6,
|
||||||
|
line: 1,
|
||||||
|
column: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
`(_ctx.`,
|
||||||
|
{
|
||||||
|
content: `baz`,
|
||||||
|
loc: {
|
||||||
|
source: `baz`,
|
||||||
|
start: {
|
||||||
|
offset: 7,
|
||||||
|
line: 1,
|
||||||
|
column: 8
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: 10,
|
||||||
|
line: 1,
|
||||||
|
column: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
` + 1, { key: _ctx.`,
|
||||||
|
{
|
||||||
|
content: `kuz`,
|
||||||
|
loc: {
|
||||||
|
source: `kuz`,
|
||||||
|
start: {
|
||||||
|
offset: 23,
|
||||||
|
line: 1,
|
||||||
|
column: 24
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
offset: 26,
|
||||||
|
line: 1,
|
||||||
|
column: 27
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
` })`
|
` })`
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO FIXME
|
test('should not prefix v-for alias', () => {
|
||||||
test('should not prefix v-for aliases', () => {
|
const node = parseWithExpressionTransform(
|
||||||
// const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode
|
`<div v-for="i in list">{{ i }}{{ j }}</div>`
|
||||||
// expect(node.children).toMatchObject([
|
) as ForNode
|
||||||
// `{ foo: `,
|
const div = node.children[0] as ElementNode
|
||||||
// { content: `_ctx.foo` },
|
|
||||||
// ` }`
|
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).toMatchObject([`_ctx.`, { content: `j` }])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should prefix id outside of v-for', () => {})
|
test('should not prefix v-for aliases (multiple)', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`
|
||||||
|
) as ForNode
|
||||||
|
const div = node.children[0] as ElementNode
|
||||||
|
|
||||||
test('nested v-for', () => {})
|
const exp = div.children[0] as ExpressionNode
|
||||||
|
expect(exp.type).toBe(NodeTypes.EXPRESSION)
|
||||||
|
expect(exp.content).toBe(`i + j + k`)
|
||||||
|
expect(exp.children).toBeUndefined()
|
||||||
|
|
||||||
test('should not prefix whitelisted globals', () => {})
|
const l = div.children[1] as ExpressionNode
|
||||||
|
expect(l.type).toBe(NodeTypes.EXPRESSION)
|
||||||
|
expect(l.children).toMatchObject([`_ctx.`, { content: `l` }])
|
||||||
|
})
|
||||||
|
|
||||||
test('should not prefix id of a function declaration', () => {})
|
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.content).toBe(`i`)
|
||||||
|
expect(exp.children).toMatchObject([`_ctx.`, { content: `i` }])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('nested v-for', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`<div v-for="i in list">
|
||||||
|
<div v-for="i in list">{{ i + j }}</div>{{ i }}
|
||||||
|
</div>`
|
||||||
|
) as ForNode
|
||||||
|
const outerDiv = node.children[0] as ElementNode
|
||||||
|
const innerFor = outerDiv.children[0] as ForNode
|
||||||
|
const innerExp = (innerFor.children[0] as ElementNode)
|
||||||
|
.children[0] as ExpressionNode
|
||||||
|
expect(innerExp.type).toBe(NodeTypes.EXPRESSION)
|
||||||
|
expect(innerExp.children).toMatchObject([`i + _ctx.`, { content: `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()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not prefix whitelisted globals', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ Math.max(1, 2) }}`
|
||||||
|
) as ExpressionNode
|
||||||
|
expect(node.type).toBe(NodeTypes.EXPRESSION)
|
||||||
|
expect(node.content).toBe(`Math.max(1, 2)`)
|
||||||
|
expect(node.children).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
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 foo() { return _ctx.`,
|
||||||
|
{ content: `bar` },
|
||||||
|
` }`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test('should not prefix params of a function expression', () => {
|
test('should not prefix params of a function expression', () => {
|
||||||
// also test object + array destructure
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ foo => foo + bar }}`
|
||||||
|
) as ExpressionNode
|
||||||
|
expect(node.type).toBe(NodeTypes.EXPRESSION)
|
||||||
|
expect(node.children).toMatchObject([
|
||||||
|
`foo => foo + _ctx.`,
|
||||||
|
{ content: `bar` }
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not prefix an object property key', () => {})
|
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: _ctx.`,
|
||||||
|
{ content: `bar` },
|
||||||
|
` }`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test('should prefix a computed object property key', () => {})
|
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([
|
||||||
|
`{ [_ctx.`,
|
||||||
|
{ content: `foo` },
|
||||||
|
`]: _ctx.`,
|
||||||
|
{ content: `bar` },
|
||||||
|
` }`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test('should prefix object property shorthand value', () => {})
|
test('should prefix object property shorthand value', () => {
|
||||||
|
const node = parseWithExpressionTransform(`{{ { foo } }}`) as ExpressionNode
|
||||||
|
expect(node.children).toMatchObject([
|
||||||
|
`{ foo: _ctx.`,
|
||||||
|
{ content: `foo` },
|
||||||
|
` }`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test('should not prefix id in a member expression', () => {})
|
test('should not prefix id in a member expression', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ foo.bar.baz }}`
|
||||||
|
) as ExpressionNode
|
||||||
|
expect(node.children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{ content: `foo` },
|
||||||
|
`.bar.baz`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should prefix computed id in a member expression', () => {
|
||||||
|
const node = parseWithExpressionTransform(
|
||||||
|
`{{ foo[bar][baz] }}`
|
||||||
|
) as ExpressionNode
|
||||||
|
expect(node.children).toMatchObject([
|
||||||
|
`_ctx.`,
|
||||||
|
{ content: `foo` },
|
||||||
|
`[_ctx.`,
|
||||||
|
{ content: `bar` },
|
||||||
|
`][_ctx.`,
|
||||||
|
{ content: 'baz' },
|
||||||
|
`]`
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -36,10 +36,19 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
|
|
||||||
const simpleIdRE = /^[a-zA-Z$_][\w$]*$/
|
const simpleIdRE = /^[a-zA-Z$_][\w$]*$/
|
||||||
|
|
||||||
|
const isFunction = (node: Node): node is Function =>
|
||||||
|
/Function(Expression|Declaration)$/.test(node.type)
|
||||||
|
|
||||||
// cache node requires
|
// cache node requires
|
||||||
let _parseScript: typeof parseScript
|
let _parseScript: typeof parseScript
|
||||||
let _walk: typeof walk
|
let _walk: typeof walk
|
||||||
|
|
||||||
|
interface PrefixMeta {
|
||||||
|
prefix: string
|
||||||
|
start: number
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
// Important: since this function uses Node.js only dependencies, it should
|
// Important: since this function uses Node.js only dependencies, it should
|
||||||
// always be used with a leading !__BROWSER__ check so that it can be
|
// always be used with a leading !__BROWSER__ check so that it can be
|
||||||
// tree-shaken from the browser build.
|
// tree-shaken from the browser build.
|
||||||
@ -56,7 +65,7 @@ export function processExpression(
|
|||||||
// fast path if expression is a simple identifier.
|
// fast path if expression is a simple identifier.
|
||||||
if (simpleIdRE.test(node.content)) {
|
if (simpleIdRE.test(node.content)) {
|
||||||
if (!context.identifiers[node.content]) {
|
if (!context.identifiers[node.content]) {
|
||||||
node.content = `_ctx.${node.content}`
|
node.children = [`_ctx.`, createExpression(node.content, false, node.loc)]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -68,21 +77,35 @@ export function processExpression(
|
|||||||
context.onError(e)
|
context.onError(e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const ids: Node[] = []
|
|
||||||
|
const ids: (Identifier & PrefixMeta)[] = []
|
||||||
const knownIds = Object.create(context.identifiers)
|
const knownIds = Object.create(context.identifiers)
|
||||||
|
|
||||||
|
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
|
||||||
walk(ast, {
|
walk(ast, {
|
||||||
enter(node, parent) {
|
enter(node: Node & PrefixMeta, parent) {
|
||||||
if (node.type === 'Identifier') {
|
if (node.type === 'Identifier') {
|
||||||
if (
|
if (
|
||||||
ids.indexOf(node) === -1 &&
|
ids.indexOf(node) === -1 &&
|
||||||
!knownIds[node.name] &&
|
!knownIds[node.name] &&
|
||||||
shouldPrefix(node, parent)
|
shouldPrefix(node, parent)
|
||||||
) {
|
) {
|
||||||
node.name = `_ctx.${node.name}`
|
if (
|
||||||
|
parent.type === 'Property' &&
|
||||||
|
parent.value === node &&
|
||||||
|
parent.key === node
|
||||||
|
) {
|
||||||
|
// property shorthand like { foo }, we need to add the key since we
|
||||||
|
// rewrite the value
|
||||||
|
node.prefix = `${node.name}: _ctx.`
|
||||||
|
} else {
|
||||||
|
node.prefix = `_ctx.`
|
||||||
|
}
|
||||||
ids.push(node)
|
ids.push(node)
|
||||||
}
|
}
|
||||||
} else if (isFunction(node)) {
|
} else if (isFunction(node)) {
|
||||||
|
// walk function expressions and add its arguments to known identifiers
|
||||||
|
// so that we don't prefix them
|
||||||
node.params.forEach(p =>
|
node.params.forEach(p =>
|
||||||
walk(p, {
|
walk(p, {
|
||||||
enter(child) {
|
enter(child) {
|
||||||
@ -107,24 +130,26 @@ export function processExpression(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// We break up the coumpound expression into an array of strings and sub
|
||||||
|
// expressions (for identifiers that have been prefixed). In codegen, if
|
||||||
|
// an ExpressionNode has the `.children` property, it will be used instead of
|
||||||
|
// `.content`.
|
||||||
const full = node.content
|
const full = node.content
|
||||||
const children: ExpressionNode['children'] = []
|
const children: ExpressionNode['children'] = []
|
||||||
ids.sort((a: any, b: any) => a.start - b.start)
|
ids.sort((a, b) => a.start - b.start)
|
||||||
ids.forEach((id: any, i) => {
|
ids.forEach((id, i) => {
|
||||||
const last = ids[i - 1] as any
|
const last = ids[i - 1] as any
|
||||||
const text = full.slice(last ? last.end - 1 : 0, id.start - 1)
|
const leadingText = full.slice(last ? last.end - 1 : 0, id.start - 1)
|
||||||
if (text.length) {
|
children.push(leadingText + id.prefix)
|
||||||
children.push(text)
|
const source = full.slice(id.start - 1, id.end - 1)
|
||||||
}
|
|
||||||
const source = full.slice(id.start, id.end)
|
|
||||||
children.push(
|
children.push(
|
||||||
createExpression(id.name, false, {
|
createExpression(id.name, false, {
|
||||||
source,
|
source,
|
||||||
start: advancePositionWithClone(node.loc.start, source, id.start),
|
start: advancePositionWithClone(node.loc.start, source, id.start + 2),
|
||||||
end: advancePositionWithClone(node.loc.start, source, id.end)
|
end: advancePositionWithClone(node.loc.start, source, id.end + 2)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
if (i === ids.length - 1 && id.end < full.length - 1) {
|
if (i === ids.length - 1 && id.end - 1 < full.length) {
|
||||||
children.push(full.slice(id.end - 1))
|
children.push(full.slice(id.end - 1))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -145,20 +170,23 @@ const globals = new Set(
|
|||||||
.split(',')
|
.split(',')
|
||||||
)
|
)
|
||||||
|
|
||||||
const isFunction = (node: Node): node is Function =>
|
|
||||||
/Function(Expression|Declaration)$/.test(node.type)
|
|
||||||
|
|
||||||
function shouldPrefix(identifier: Identifier, parent: Node) {
|
function shouldPrefix(identifier: Identifier, parent: Node) {
|
||||||
if (
|
if (
|
||||||
|
!(
|
||||||
|
isFunction(parent) &&
|
||||||
// not id of a FunctionDeclaration
|
// not id of a FunctionDeclaration
|
||||||
!(parent.type === 'FunctionDeclaration' && parent.id === identifier) &&
|
((parent as any).id === identifier ||
|
||||||
// not a params of a function
|
// not a params of a function
|
||||||
!(isFunction(parent) && parent.params.indexOf(identifier) > -1) &&
|
parent.params.indexOf(identifier) > -1)
|
||||||
|
) &&
|
||||||
// not a key of Property
|
// not a key of Property
|
||||||
!(
|
!(
|
||||||
parent.type === 'Property' &&
|
parent.type === 'Property' &&
|
||||||
parent.key === identifier &&
|
parent.key === identifier &&
|
||||||
!parent.computed
|
// computed keys should be prefixed
|
||||||
|
!parent.computed &&
|
||||||
|
// shorthand keys should be prefixed
|
||||||
|
!(parent.value === identifier)
|
||||||
) &&
|
) &&
|
||||||
// not a property of a MemberExpression
|
// not a property of a MemberExpression
|
||||||
!(
|
!(
|
||||||
|
Loading…
Reference in New Issue
Block a user