diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 1bafff968..08c0b3e92 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -5,8 +5,9 @@ exports[`compiler: codegen callExpression + objectExpression + arrayExpression 1 with (this) { return createVNode(\\"div\\", { id: \\"foo\\", - [prop]: bar - }, [ + [prop]: bar, + [foo + bar]: bar + }, [createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })], [ foo, createVNode(\\"p\\") ]) @@ -22,6 +23,14 @@ exports[`compiler: codegen comment 1`] = ` }" `; +exports[`compiler: codegen compound expression 1`] = ` +"return function render() { + with (this) { + return toString(_ctx.foo) + } +}" +`; + exports[`compiler: codegen forNode 1`] = ` "return function render() { with (this) { @@ -30,6 +39,30 @@ exports[`compiler: codegen forNode 1`] = ` }" `; +exports[`compiler: codegen forNode w/ skipped key alias 1`] = ` +"return function render() { + with (this) { + return renderList(list, (v, __key, i) => toString(v)) + } +}" +`; + +exports[`compiler: codegen forNode w/ skipped value alias 1`] = ` +"return function render() { + with (this) { + return renderList(list, (__value, k, i) => toString(v)) + } +}" +`; + +exports[`compiler: codegen forNode w/ skipped value and key aliases 1`] = ` +"return function render() { + with (this) { + return renderList(list, (__value, __key, i) => toString(v)) + } +}" +`; + exports[`compiler: codegen function mode preamble 1`] = ` "const { helperOne, helperTwo } = Vue @@ -52,6 +85,18 @@ exports[`compiler: codegen ifNode 1`] = ` }" `; +exports[`compiler: codegen ifNode with no v-else 1`] = ` +"return function render() { + with (this) { + return (foo) + ? \\"foo\\" + : (bar) + ? toString(bye) + : null + } +}" +`; + exports[`compiler: codegen interpolation 1`] = ` "return function render() { with (this) { diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index daf84568a..38ef6b067 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -7,9 +7,11 @@ import { createExpression, Namespaces, ElementTypes, + CallExpression, createObjectExpression, createObjectProperty, - createArrayExpression + createArrayExpression, + ElementNode } from '../src' import { SourceMapConsumer, RawSourceMap } from 'source-map' import { CREATE_VNODE, COMMENT, TO_STRING } from '../src/runtimeConstants' @@ -145,6 +147,25 @@ describe('compiler: codegen', () => { expect(code).toMatchSnapshot() }) + test('compound expression', () => { + const { code } = generate( + createRoot({ + children: [ + { + type: NodeTypes.EXPRESSION, + content: 'foo', + isStatic: false, + isInterpolation: true, + loc: mockLoc, + children: [`_ctx.`, createExpression(`foo`, false, mockLoc)] + } + ] + }) + ) + expect(code).toMatch(`return toString(_ctx.foo)`) + expect(code).toMatchSnapshot() + }) + test('ifNode', () => { const { code } = generate( createRoot({ @@ -202,6 +223,50 @@ describe('compiler: codegen', () => { expect(code).toMatchSnapshot() }) + test('ifNode with no v-else', () => { + const { code } = generate( + createRoot({ + children: [ + { + type: NodeTypes.IF, + loc: mockLoc, + isRoot: true, + branches: [ + { + type: NodeTypes.IF_BRANCH, + condition: createExpression('foo', false, mockLoc), + loc: mockLoc, + isRoot: true, + children: [ + { + type: NodeTypes.TEXT, + content: 'foo', + isEmpty: false, + loc: mockLoc + } + ] + }, + { + type: NodeTypes.IF_BRANCH, + condition: createExpression('bar', false, mockLoc), + loc: mockLoc, + isRoot: true, + children: [createExpression(`bye`, false, mockLoc, true)] + } + ] + } + ] + }) + ) + expect(code).toMatch(` + return (foo) + ? "foo" + : (bar) + ? ${TO_STRING}(bye) + : null`) + expect(code).toMatchSnapshot() + }) + test('forNode', () => { const { code } = generate( createRoot({ @@ -222,63 +287,166 @@ describe('compiler: codegen', () => { expect(code).toMatchSnapshot() }) - test('callExpression + objectExpression + arrayExpression', () => { + test('forNode w/ skipped value alias', () => { const { code } = generate( createRoot({ children: [ { - type: NodeTypes.ELEMENT, + type: NodeTypes.FOR, loc: mockLoc, - ns: Namespaces.HTML, - tag: 'div', - tagType: ElementTypes.ELEMENT, - isSelfClosing: false, - props: [], - children: [], - codegenNode: { - type: NodeTypes.JS_CALL_EXPRESSION, - loc: mockLoc, - callee: CREATE_VNODE, - arguments: [ - `"div"`, + source: createExpression(`list`, false, mockLoc), + valueAlias: undefined, + keyAlias: createExpression(`k`, false, mockLoc), + objectIndexAlias: createExpression(`i`, false, mockLoc), + children: [createExpression(`v`, false, mockLoc, true)] + } + ] + }) + ) + expect(code).toMatch(`renderList(list, (__value, k, i) => toString(v))`) + expect(code).toMatchSnapshot() + }) + + test('forNode w/ skipped key alias', () => { + const { code } = generate( + createRoot({ + children: [ + { + type: NodeTypes.FOR, + loc: mockLoc, + source: createExpression(`list`, false, mockLoc), + valueAlias: createExpression(`v`, false, mockLoc), + keyAlias: undefined, + objectIndexAlias: createExpression(`i`, false, mockLoc), + children: [createExpression(`v`, false, mockLoc, true)] + } + ] + }) + ) + expect(code).toMatch(`renderList(list, (v, __key, i) => toString(v))`) + expect(code).toMatchSnapshot() + }) + + test('forNode w/ skipped value and key aliases', () => { + const { code } = generate( + createRoot({ + children: [ + { + type: NodeTypes.FOR, + loc: mockLoc, + source: createExpression(`list`, false, mockLoc), + valueAlias: undefined, + keyAlias: undefined, + objectIndexAlias: createExpression(`i`, false, mockLoc), + children: [createExpression(`v`, false, mockLoc, true)] + } + ] + }) + ) + expect(code).toMatch(`renderList(list, (__value, __key, i) => toString(v))`) + expect(code).toMatchSnapshot() + }) + + test('callExpression + objectExpression + arrayExpression', () => { + function createElementWithCodegen( + args: CallExpression['arguments'] + ): ElementNode { + return { + type: NodeTypes.ELEMENT, + loc: mockLoc, + ns: Namespaces.HTML, + tag: 'div', + tagType: ElementTypes.ELEMENT, + isSelfClosing: false, + props: [], + children: [], + codegenNode: { + type: NodeTypes.JS_CALL_EXPRESSION, + loc: mockLoc, + callee: CREATE_VNODE, + arguments: args + } + } + } + + const { code } = generate( + createRoot({ + children: [ + createElementWithCodegen([ + // string + `"div"`, + // ObjectExpression + createObjectExpression( + [ + createObjectProperty( + createExpression(`id`, true, mockLoc), + createExpression(`foo`, true, mockLoc), + mockLoc + ), + createObjectProperty( + createExpression(`prop`, false, mockLoc), + createExpression(`bar`, false, mockLoc), + mockLoc + ), + // compound expression as computed key + createObjectProperty( + { + type: NodeTypes.EXPRESSION, + content: ``, + loc: mockLoc, + isStatic: false, + isInterpolation: false, + children: [ + `foo + `, + createExpression(`bar`, false, mockLoc) + ] + }, + createExpression(`bar`, false, mockLoc), + mockLoc + ) + ], + mockLoc + ), + // ChildNode[] + [ + createElementWithCodegen([ + `"p"`, createObjectExpression( [ createObjectProperty( - createExpression(`id`, true, mockLoc), + // should quote the key! + createExpression(`some-key`, true, mockLoc), createExpression(`foo`, true, mockLoc), mockLoc - ), - createObjectProperty( - createExpression(`prop`, false, mockLoc), - createExpression(`bar`, false, mockLoc), - mockLoc ) ], mockLoc - ), - createArrayExpression( - [ - 'foo', - { - type: NodeTypes.JS_CALL_EXPRESSION, - loc: mockLoc, - callee: CREATE_VNODE, - arguments: [`"p"`] - } - ], - mockLoc ) - ] - } - } + ]) + ], + // ArrayExpression + createArrayExpression( + [ + 'foo', + { + type: NodeTypes.JS_CALL_EXPRESSION, + loc: mockLoc, + callee: CREATE_VNODE, + arguments: [`"p"`] + } + ], + mockLoc + ) + ]) ] }) ) expect(code).toMatch(` return ${CREATE_VNODE}("div", { id: "foo", - [prop]: bar - }, [ + [prop]: bar, + [foo + bar]: bar + }, [${CREATE_VNODE}("p", { "some-key": "foo" })], [ foo, ${CREATE_VNODE}("p") ])`) diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 95a55227d..af39a3b8d 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -80,6 +80,46 @@ describe('compiler: element transform', () => { ]) }) + test('props + children', () => { + const { node } = parseWithElementTransform(`
`) + expect(node.callee).toBe(CREATE_VNODE) + expect(node.arguments).toMatchObject([ + `"div"`, + createStaticObjectMatcher({ + id: 'foo' + }), + [ + { + type: NodeTypes.ELEMENT, + tag: 'span', + codegenNode: { + callee: CREATE_VNODE, + arguments: [`"span"`] + } + } + ] + ]) + }) + + test('0 placeholder for children with no props', () => { + const { node } = parseWithElementTransform(`
`) + expect(node.callee).toBe(CREATE_VNODE) + expect(node.arguments).toMatchObject([ + `"div"`, + `0`, + [ + { + type: NodeTypes.ELEMENT, + tag: 'span', + codegenNode: { + callee: CREATE_VNODE, + arguments: [`"span"`] + } + } + ] + ]) + }) + test('v-bind="obj"', () => { const { root, node } = parseWithElementTransform(`
`) // single v-bind doesn't need mergeProps diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index ae4cba926..85089bd22 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -5,16 +5,21 @@ import { ElementNode, DirectiveNode, NodeTypes, - ForNode + ForNode, + CompilerOptions } from '../../src' import { transformFor } from '../..//src/transforms/vFor' import { transformExpression } from '../../src/transforms/transformExpression' -function parseWithExpressionTransform(template: string) { +function parseWithExpressionTransform( + template: string, + options: CompilerOptions = {} +) { const ast = parse(template) transform(ast, { prefixIdentifiers: true, - nodeTransforms: [transformFor, transformExpression] + nodeTransforms: [transformFor, transformExpression], + ...options }) return ast.children[0] } @@ -297,4 +302,10 @@ describe('compiler: expression transform', () => { `]` ]) }) + + test('should handle parse error', () => { + const onError = jest.fn() + parseWithExpressionTransform(`{{ a( }}`, { onError }) + expect(onError.mock.calls[0][0].message).toMatch(`Expected ')'`) + }) }) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 0e0c085e2..559e3942c 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -277,6 +277,7 @@ function genNode(node: CodegenNode, context: CodegenContext) { genArrayExpression(node, context) break default: + /* istanbul ignore next */ __DEV__ && assert(false, `unhandled codegen node type: ${(node as any).type}`) } @@ -399,7 +400,7 @@ function genFor(node: ForNode, context: CodegenContext) { } if (keyAlias) { if (!valueAlias) { - push(`_`) + push(`__value`) } push(`, `) genExpression(keyAlias, context) @@ -407,9 +408,9 @@ function genFor(node: ForNode, context: CodegenContext) { if (objectIndexAlias) { if (!keyAlias) { if (!valueAlias) { - push(`_, __`) + push(`__value, __key`) } else { - push(`__`) + push(`, __key`) } } push(`, `) @@ -447,12 +448,9 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) { // value genExpression(value, context) if (i < properties.length - 1) { - if (multilines) { - push(`,`) - newline() - } else { - push(`, `) - } + // will only reach this if it's multilines + push(`,`) + newline() } } multilines && deindent() diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 0df48ed51..84677fd87 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -199,6 +199,7 @@ function pushNode( node: ChildNode ): void { // ignore comments in production + /* istanbul ignore next */ if (!__DEV__ && node.type === NodeTypes.COMMENT) { return } diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index b0a1278c6..4dac7986a 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -85,6 +85,7 @@ function createTransformContext( childIndex: 0, currentNode: null, replaceNode(node) { + /* istanbul ignore if */ if (__DEV__ && !context.currentNode) { throw new Error(`node being replaced is already removed.`) } @@ -97,6 +98,7 @@ function createTransformContext( : context.currentNode ? context.childIndex : -1 + /* istanbul ignore if */ if (__DEV__ && removalIndex < 0) { throw new Error(`node being removed is not a child of current parent`) } diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index 2a93612bd..59704a2d6 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -60,6 +60,7 @@ export function advancePositionWithMutation( } export function assert(condition: boolean, msg?: string) { + /* istanbul ignore if */ if (!condition) { throw new Error(msg || `unexpected compiler condition`) }