From 914087edeaab5501bd14d62b899dcdf5f81dd49f Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 22 Sep 2019 22:19:42 -0400 Subject: [PATCH] feat(compiler): basic v-bind & v-on transforms --- .../compiler-core/__tests__/parse.spec.ts | 30 +- .../compiler-core/__tests__/transform.spec.ts | 4 +- .../__tests__/transforms/element.spec.ts | 3 + .../__tests__/transforms/vFor.spec.ts | 860 +++++++++--------- .../__tests__/transforms/vIf.spec.ts | 474 +++++----- packages/compiler-core/src/codegen.ts | 4 +- packages/compiler-core/src/errors.ts | 12 +- packages/compiler-core/src/index.ts | 5 +- packages/compiler-core/src/parse.ts | 8 +- .../compiler-core/src/transforms/element.ts | 5 +- .../compiler-core/src/transforms/vBind.ts | 25 +- packages/compiler-core/src/transforms/vFor.ts | 7 +- packages/compiler-core/src/transforms/vIf.ts | 2 +- packages/compiler-core/src/transforms/vOn.ts | 25 +- 14 files changed, 749 insertions(+), 715 deletions(-) create mode 100644 packages/compiler-core/__tests__/transforms/element.spec.ts diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index bb4116ba..2bf25e67 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -195,7 +195,9 @@ describe('compiler: parse', () => { [ { code: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE, - loc: { offset: 4, line: 1, column: 5 } + loc: { + start: { offset: 4, line: 1, column: 5 } + } } ] ]) @@ -249,7 +251,9 @@ describe('compiler: parse', () => { [ { code: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE, - loc: { offset: 45, line: 1, column: 46 } + loc: { + start: { offset: 45, line: 1, column: 46 } + } } ] ]) @@ -274,7 +278,9 @@ describe('compiler: parse', () => { [ { code: ErrorCodes.CONTROL_CHARACTER_REFERENCE, - loc: { offset: 0, line: 1, column: 1 } + loc: { + start: { offset: 0, line: 1, column: 1 } + } } ] ]) @@ -1254,9 +1260,11 @@ describe('compiler: parse', () => { { code: ErrorCodes.X_MISSING_END_TAG, loc: { - offset: 13, - line: 3, - column: 1 + start: { + offset: 13, + line: 3, + column: 1 + } } } ], @@ -1264,9 +1272,11 @@ describe('compiler: parse', () => { { code: ErrorCodes.X_INVALID_END_TAG, loc: { - offset: 20, - line: 4, - column: 1 + start: { + offset: 20, + line: 4, + column: 1 + } } } ] @@ -2386,7 +2396,7 @@ describe('compiler: parse', () => { expect( spy.mock.calls.map(([err]) => ({ type: err.code, - loc: err.loc + loc: err.loc.start })) ).toMatchObject(errors) expect(ast).toMatchSnapshot() diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts index c8aa3a97..3d262f2e 100644 --- a/packages/compiler-core/__tests__/transform.spec.ts +++ b/packages/compiler-core/__tests__/transform.spec.ts @@ -162,10 +162,10 @@ describe('compiler: transform', () => { test('onError option', () => { const ast = parse(`
`) - const loc = ast.children[0].loc.start + const loc = ast.children[0].loc const plugin: NodeTransform = (node, context) => { context.onError( - createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc.start) + createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc) ) } const spy = jest.fn() diff --git a/packages/compiler-core/__tests__/transforms/element.spec.ts b/packages/compiler-core/__tests__/transforms/element.spec.ts new file mode 100644 index 00000000..41076560 --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/element.spec.ts @@ -0,0 +1,3 @@ +describe('compiler: element transform', () => { + test.todo('should work') +}) diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 64ed06e7..5c5bc1cd 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -4,10 +4,293 @@ import { transformFor } from '../../src/transforms/vFor' import { ForNode, NodeTypes } from '../../src/ast' import { ErrorCodes } from '../../src/errors' -describe('v-for', () => { - describe('transform', () => { - test('number expression', () => { - const node = parse('') +describe('compiler: transform v-for', () => { + test('number expression', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('index') + expect(forNode.source.content).toBe('5') + }) + + test('value', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.source.content).toBe('items') + }) + + test('object de-structured value', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('{ id, value }') + expect(forNode.source.content).toBe('items') + }) + + test('array de-structured value', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('[ id, value ]') + expect(forNode.source.content).toBe('items') + }) + + test('value and key', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.keyAlias).not.toBeUndefined() + expect(forNode.keyAlias!.content).toBe('key') + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.source.content).toBe('items') + }) + + test('value, key and index', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).not.toBeUndefined() + expect(forNode.keyAlias!.content).toBe('key') + expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.objectIndexAlias!.content).toBe('index') + expect(forNode.valueAlias!.content).toBe('value') + expect(forNode.source.content).toBe('items') + }) + + test('skipped key', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.objectIndexAlias!.content).toBe('index') + expect(forNode.valueAlias!.content).toBe('value') + expect(forNode.source.content).toBe('items') + }) + + test('skipped value and key', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.objectIndexAlias!.content).toBe('index') + expect(forNode.valueAlias).toBeUndefined() + expect(forNode.source.content).toBe('items') + }) + + test('unbracketed value', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.source.content).toBe('items') + }) + + test('unbracketed value and key', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).not.toBeUndefined() + expect(forNode.keyAlias!.content).toBe('key') + expect(forNode.objectIndexAlias).toBeUndefined() + expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.source.content).toBe('items') + }) + + test('unbracketed value, key and index', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).not.toBeUndefined() + expect(forNode.keyAlias!.content).toBe('key') + expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.objectIndexAlias!.content).toBe('index') + expect(forNode.valueAlias!.content).toBe('value') + expect(forNode.source.content).toBe('items') + }) + + test('unbracketed skipped key', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.objectIndexAlias!.content).toBe('index') + expect(forNode.valueAlias!.content).toBe('value') + expect(forNode.source.content).toBe('items') + }) + + test('unbracketed skipped value and key', () => { + const node = parse('') + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + expect(forNode.keyAlias).toBeUndefined() + expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.objectIndexAlias!.content).toBe('index') + expect(forNode.valueAlias).toBeUndefined() + expect(forNode.source.content).toBe('items') + }) + + test('missing expression', () => { + const node = parse('') + const onError = jest.fn() + transform(node, { nodeTransforms: [transformFor], onError }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_FOR_NO_EXPRESSION + }) + ) + }) + + test('empty expression', () => { + const node = parse('') + const onError = jest.fn() + transform(node, { nodeTransforms: [transformFor], onError }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION + }) + ) + }) + + test('invalid expression', () => { + const node = parse('') + const onError = jest.fn() + transform(node, { nodeTransforms: [transformFor], onError }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION + }) + ) + }) + + test('missing source', () => { + const node = parse('') + const onError = jest.fn() + transform(node, { nodeTransforms: [transformFor], onError }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION + }) + ) + }) + + test('missing value', () => { + const node = parse('') + const onError = jest.fn() + transform(node, { nodeTransforms: [transformFor], onError }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION + }) + ) + }) + + describe('source location', () => { + test('value & source', () => { + const source = '' + const node = parse(source) transform(node, { nodeTransforms: [transformFor] }) @@ -16,30 +299,29 @@ describe('v-for', () => { const forNode = node.children[0] as ForNode expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('index') - expect(forNode.source.content).toBe('5') - }) - test('value', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.valueAlias!.loc.start.offset).toBe( + source.indexOf('item') - 1 + ) + expect(forNode.valueAlias!.loc.start.line).toBe(1) + expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) + expect(forNode.valueAlias!.loc.end.line).toBe(1) + expect(forNode.valueAlias!.loc.end.column).toBe( + source.indexOf('item') + 4 + ) + expect(forNode.source.content).toBe('items') + expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) + expect(forNode.source.loc.start.line).toBe(1) + expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) + expect(forNode.source.loc.end.line).toBe(1) + expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) }) - test('object de-structured value', () => { - const node = parse('') + test('bracketed value', () => { + const source = '' + const node = parse(source) transform(node, { nodeTransforms: [transformFor] }) @@ -48,46 +330,29 @@ describe('v-for', () => { const forNode = node.children[0] as ForNode expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('{ id, value }') - expect(forNode.source.content).toBe('items') - }) - test('array de-structured value', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('[ id, value ]') - expect(forNode.source.content).toBe('items') - }) - - test('value and key', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') - expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.valueAlias!.loc.start.offset).toBe( + source.indexOf('item') - 1 + ) + expect(forNode.valueAlias!.loc.start.line).toBe(1) + expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) + expect(forNode.valueAlias!.loc.end.line).toBe(1) + expect(forNode.valueAlias!.loc.end.column).toBe( + source.indexOf('item') + 4 + ) + expect(forNode.source.content).toBe('items') + expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) + expect(forNode.source.loc.start.line).toBe(1) + expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) + expect(forNode.source.loc.end.line).toBe(1) + expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) }) - test('value, key and index', () => { - const node = parse('') + test('de-structured value', () => { + const source = '' + const node = parse(source) transform(node, { nodeTransforms: [transformFor] }) @@ -96,16 +361,82 @@ describe('v-for', () => { const forNode = node.children[0] as ForNode expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).not.toBeUndefined() + + expect(forNode.valueAlias!.content).toBe('{ id, key }') + expect(forNode.valueAlias!.loc.start.offset).toBe( + source.indexOf('{ id, key }') - 1 + ) + expect(forNode.valueAlias!.loc.start.line).toBe(1) + expect(forNode.valueAlias!.loc.start.column).toBe( + source.indexOf('{ id, key }') + ) + expect(forNode.valueAlias!.loc.end.line).toBe(1) + expect(forNode.valueAlias!.loc.end.column).toBe( + source.indexOf('{ id, key }') + '{ id, key }'.length + ) + + expect(forNode.source.content).toBe('items') + expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) + expect(forNode.source.loc.start.line).toBe(1) + expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) + expect(forNode.source.loc.end.line).toBe(1) + expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) + }) + + test('bracketed value, key, index', () => { + const source = '' + const node = parse(source) + + transform(node, { nodeTransforms: [transformFor] }) + + expect(node.children.length).toBe(1) + + const forNode = node.children[0] as ForNode + + expect(forNode.type).toBe(NodeTypes.FOR) + + expect(forNode.valueAlias!.content).toBe('item') + expect(forNode.valueAlias!.loc.start.offset).toBe( + source.indexOf('item') - 1 + ) + expect(forNode.valueAlias!.loc.start.line).toBe(1) + expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) + expect(forNode.valueAlias!.loc.end.line).toBe(1) + expect(forNode.valueAlias!.loc.end.column).toBe( + source.indexOf('item') + 4 + ) + expect(forNode.keyAlias!.content).toBe('key') - expect(forNode.objectIndexAlias).not.toBeUndefined() + expect(forNode.keyAlias!.loc.start.offset).toBe(source.indexOf('key') - 1) + expect(forNode.keyAlias!.loc.start.line).toBe(1) + expect(forNode.keyAlias!.loc.start.column).toBe(source.indexOf('key')) + expect(forNode.keyAlias!.loc.end.line).toBe(1) + expect(forNode.keyAlias!.loc.end.column).toBe(source.indexOf('key') + 3) + expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') + expect(forNode.objectIndexAlias!.loc.start.offset).toBe( + source.indexOf('index') - 1 + ) + expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) + expect(forNode.objectIndexAlias!.loc.start.column).toBe( + source.indexOf('index') + ) + expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) + expect(forNode.objectIndexAlias!.loc.end.column).toBe( + source.indexOf('index') + 5 + ) + expect(forNode.source.content).toBe('items') + expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) + expect(forNode.source.loc.start.line).toBe(1) + expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) + expect(forNode.source.loc.end.line).toBe(1) + expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) }) test('skipped key', () => { - const node = parse('') + const source = '' + const node = parse(source) transform(node, { nodeTransforms: [transformFor] }) @@ -114,390 +445,37 @@ describe('v-for', () => { const forNode = node.children[0] as ForNode expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') - expect(forNode.source.content).toBe('items') - }) - test('skipped value and key', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias).toBeUndefined() - expect(forNode.source.content).toBe('items') - }) - - test('unbracketed value', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.source.content).toBe('items') - }) + expect(forNode.valueAlias!.loc.start.offset).toBe( + source.indexOf('item') - 1 + ) + expect(forNode.valueAlias!.loc.start.line).toBe(1) + expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item')) + expect(forNode.valueAlias!.loc.end.line).toBe(1) + expect(forNode.valueAlias!.loc.end.column).toBe( + source.indexOf('item') + 4 + ) - test('unbracketed value and key', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') - expect(forNode.objectIndexAlias).toBeUndefined() - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.source.content).toBe('items') - }) - - test('unbracketed value, key and index', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).not.toBeUndefined() - expect(forNode.keyAlias!.content).toBe('key') - expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') + expect(forNode.objectIndexAlias!.loc.start.offset).toBe( + source.indexOf('index') - 1 + ) + expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) + expect(forNode.objectIndexAlias!.loc.start.column).toBe( + source.indexOf('index') + ) + expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) + expect(forNode.objectIndexAlias!.loc.end.column).toBe( + source.indexOf('index') + 5 + ) + expect(forNode.source.content).toBe('items') - }) - - test('unbracketed skipped key', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias!.content).toBe('value') - expect(forNode.source.content).toBe('items') - }) - - test('unbracketed skipped value and key', () => { - const node = parse('') - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - expect(forNode.keyAlias).toBeUndefined() - expect(forNode.objectIndexAlias).not.toBeUndefined() - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.valueAlias).toBeUndefined() - expect(forNode.source.content).toBe('items') - }) - - test('missing expression', () => { - const node = parse('') - const onError = jest.fn() - transform(node, { nodeTransforms: [transformFor], onError }) - - expect(onError).toHaveBeenCalledTimes(1) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ - code: ErrorCodes.X_FOR_NO_EXPRESSION - }) - ) - }) - - test('empty expression', () => { - const node = parse('') - const onError = jest.fn() - transform(node, { nodeTransforms: [transformFor], onError }) - - expect(onError).toHaveBeenCalledTimes(1) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ - code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION - }) - ) - }) - - test('invalid expression', () => { - const node = parse('') - const onError = jest.fn() - transform(node, { nodeTransforms: [transformFor], onError }) - - expect(onError).toHaveBeenCalledTimes(1) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ - code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION - }) - ) - }) - - test('missing source', () => { - const node = parse('') - const onError = jest.fn() - transform(node, { nodeTransforms: [transformFor], onError }) - - expect(onError).toHaveBeenCalledTimes(1) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ - code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION - }) - ) - }) - - test('missing value', () => { - const node = parse('') - const onError = jest.fn() - transform(node, { nodeTransforms: [transformFor], onError }) - - expect(onError).toHaveBeenCalledTimes(1) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ - code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION - }) - ) - }) - - describe('source location', () => { - test('value & source', () => { - const source = '' - const node = parse(source) - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe( - source.indexOf('item') - 1 - ) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe( - source.indexOf('item') - ) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - source.indexOf('item') + 4 - ) - - expect(forNode.source.content).toBe('items') - expect(forNode.source.loc.start.offset).toBe( - source.indexOf('items') - 1 - ) - expect(forNode.source.loc.start.line).toBe(1) - expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) - expect(forNode.source.loc.end.line).toBe(1) - expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) - }) - - test('bracketed value', () => { - const source = '' - const node = parse(source) - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe( - source.indexOf('item') - 1 - ) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe( - source.indexOf('item') - ) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - source.indexOf('item') + 4 - ) - - expect(forNode.source.content).toBe('items') - expect(forNode.source.loc.start.offset).toBe( - source.indexOf('items') - 1 - ) - expect(forNode.source.loc.start.line).toBe(1) - expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) - expect(forNode.source.loc.end.line).toBe(1) - expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) - }) - - test('de-structured value', () => { - const source = '' - const node = parse(source) - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - - expect(forNode.valueAlias!.content).toBe('{ id, key }') - expect(forNode.valueAlias!.loc.start.offset).toBe( - source.indexOf('{ id, key }') - 1 - ) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe( - source.indexOf('{ id, key }') - ) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - source.indexOf('{ id, key }') + '{ id, key }'.length - ) - - expect(forNode.source.content).toBe('items') - expect(forNode.source.loc.start.offset).toBe( - source.indexOf('items') - 1 - ) - expect(forNode.source.loc.start.line).toBe(1) - expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) - expect(forNode.source.loc.end.line).toBe(1) - expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) - }) - - test('bracketed value, key, index', () => { - const source = '' - const node = parse(source) - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe( - source.indexOf('item') - 1 - ) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe( - source.indexOf('item') - ) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - source.indexOf('item') + 4 - ) - - expect(forNode.keyAlias!.content).toBe('key') - expect(forNode.keyAlias!.loc.start.offset).toBe( - source.indexOf('key') - 1 - ) - expect(forNode.keyAlias!.loc.start.line).toBe(1) - expect(forNode.keyAlias!.loc.start.column).toBe(source.indexOf('key')) - expect(forNode.keyAlias!.loc.end.line).toBe(1) - expect(forNode.keyAlias!.loc.end.column).toBe(source.indexOf('key') + 3) - - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.objectIndexAlias!.loc.start.offset).toBe( - source.indexOf('index') - 1 - ) - expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.start.column).toBe( - source.indexOf('index') - ) - expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.end.column).toBe( - source.indexOf('index') + 5 - ) - - expect(forNode.source.content).toBe('items') - expect(forNode.source.loc.start.offset).toBe( - source.indexOf('items') - 1 - ) - expect(forNode.source.loc.start.line).toBe(1) - expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) - expect(forNode.source.loc.end.line).toBe(1) - expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) - }) - - test('skipped key', () => { - const source = '' - const node = parse(source) - - transform(node, { nodeTransforms: [transformFor] }) - - expect(node.children.length).toBe(1) - - const forNode = node.children[0] as ForNode - - expect(forNode.type).toBe(NodeTypes.FOR) - - expect(forNode.valueAlias!.content).toBe('item') - expect(forNode.valueAlias!.loc.start.offset).toBe( - source.indexOf('item') - 1 - ) - expect(forNode.valueAlias!.loc.start.line).toBe(1) - expect(forNode.valueAlias!.loc.start.column).toBe( - source.indexOf('item') - ) - expect(forNode.valueAlias!.loc.end.line).toBe(1) - expect(forNode.valueAlias!.loc.end.column).toBe( - source.indexOf('item') + 4 - ) - - expect(forNode.objectIndexAlias!.content).toBe('index') - expect(forNode.objectIndexAlias!.loc.start.offset).toBe( - source.indexOf('index') - 1 - ) - expect(forNode.objectIndexAlias!.loc.start.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.start.column).toBe( - source.indexOf('index') - ) - expect(forNode.objectIndexAlias!.loc.end.line).toBe(1) - expect(forNode.objectIndexAlias!.loc.end.column).toBe( - source.indexOf('index') + 5 - ) - - expect(forNode.source.content).toBe('items') - expect(forNode.source.loc.start.offset).toBe( - source.indexOf('items') - 1 - ) - expect(forNode.source.loc.start.line).toBe(1) - expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) - expect(forNode.source.loc.end.line).toBe(1) - expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) - }) + expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1) + expect(forNode.source.loc.start.line).toBe(1) + expect(forNode.source.loc.start.column).toBe(source.indexOf('items')) + expect(forNode.source.loc.end.line).toBe(1) + expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5) }) }) }) diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index 86809b9b..6339f4de 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -10,248 +10,242 @@ import { } from '../../src/ast' import { ErrorCodes } from '../../src/errors' -describe('compiler: v-if', () => { - describe('transform', () => { - test('basic v-if', () => { - const ast = parse(`
`) - transform(ast, { - nodeTransforms: [transformIf] - }) - const node = ast.children[0] as IfNode - expect(node.type).toBe(NodeTypes.IF) - expect(node.branches.length).toBe(1) - expect(node.branches[0].condition!.content).toBe(`ok`) - expect(node.branches[0].children.length).toBe(1) - expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) - expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) - }) - - test('template v-if', () => { - const ast = parse(``) - transform(ast, { - nodeTransforms: [transformIf] - }) - const node = ast.children[0] as IfNode - expect(node.type).toBe(NodeTypes.IF) - expect(node.branches.length).toBe(1) - expect(node.branches[0].condition!.content).toBe(`ok`) - expect(node.branches[0].children.length).toBe(3) - expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) - expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) - expect(node.branches[0].children[1].type).toBe(NodeTypes.TEXT) - expect((node.branches[0].children[1] as TextNode).content).toBe(`hello`) - expect(node.branches[0].children[2].type).toBe(NodeTypes.ELEMENT) - expect((node.branches[0].children[2] as ElementNode).tag).toBe(`p`) - }) - - test('v-if + v-else', () => { - const ast = parse(`

`) - transform(ast, { - nodeTransforms: [transformIf] - }) - // should fold branches - expect(ast.children.length).toBe(1) - - const node = ast.children[0] as IfNode - expect(node.type).toBe(NodeTypes.IF) - expect(node.branches.length).toBe(2) - - const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) - expect(b1.children.length).toBe(1) - expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b1.children[0] as ElementNode).tag).toBe(`div`) - - const b2 = node.branches[1] - expect(b2.condition).toBeUndefined() - expect(b2.children.length).toBe(1) - expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b2.children[0] as ElementNode).tag).toBe(`p`) - }) - - test('v-if + v-else-if', () => { - const ast = parse(`

`) - transform(ast, { - nodeTransforms: [transformIf] - }) - // should fold branches - expect(ast.children.length).toBe(1) - - const node = ast.children[0] as IfNode - expect(node.type).toBe(NodeTypes.IF) - expect(node.branches.length).toBe(2) - - const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) - expect(b1.children.length).toBe(1) - expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b1.children[0] as ElementNode).tag).toBe(`div`) - - const b2 = node.branches[1] - expect(b2.condition!.content).toBe(`orNot`) - expect(b2.children.length).toBe(1) - expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b2.children[0] as ElementNode).tag).toBe(`p`) - }) - - test('v-if + v-else-if + v-else', () => { - const ast = parse( - `

` - ) - transform(ast, { - nodeTransforms: [transformIf] - }) - // should fold branches - expect(ast.children.length).toBe(1) - - const node = ast.children[0] as IfNode - expect(node.type).toBe(NodeTypes.IF) - expect(node.branches.length).toBe(3) - - const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) - expect(b1.children.length).toBe(1) - expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b1.children[0] as ElementNode).tag).toBe(`div`) - - const b2 = node.branches[1] - expect(b2.condition!.content).toBe(`orNot`) - expect(b2.children.length).toBe(1) - expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b2.children[0] as ElementNode).tag).toBe(`p`) - - const b3 = node.branches[2] - expect(b3.condition).toBeUndefined() - expect(b3.children.length).toBe(1) - expect(b3.children[0].type).toBe(NodeTypes.TEXT) - expect((b3.children[0] as TextNode).content).toBe(`fine`) - }) - - test('comment between branches', () => { - const ast = parse(` -

- -

- - - `) - transform(ast, { - nodeTransforms: [transformIf] - }) - // should fold branches - expect(ast.children.length).toBe(1) - - const node = ast.children[0] as IfNode - expect(node.type).toBe(NodeTypes.IF) - expect(node.branches.length).toBe(3) - - const b1 = node.branches[0] - expect(b1.condition!.content).toBe(`ok`) - expect(b1.children.length).toBe(1) - expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) - expect((b1.children[0] as ElementNode).tag).toBe(`div`) - - const b2 = node.branches[1] - expect(b2.condition!.content).toBe(`orNot`) - expect(b2.children.length).toBe(2) - expect(b2.children[0].type).toBe(NodeTypes.COMMENT) - expect((b2.children[0] as CommentNode).content).toBe(`foo`) - expect(b2.children[1].type).toBe(NodeTypes.ELEMENT) - expect((b2.children[1] as ElementNode).tag).toBe(`p`) - - const b3 = node.branches[2] - expect(b3.condition).toBeUndefined() - expect(b3.children.length).toBe(2) - expect(b3.children[0].type).toBe(NodeTypes.COMMENT) - expect((b3.children[0] as CommentNode).content).toBe(`bar`) - expect(b3.children[1].type).toBe(NodeTypes.TEXT) - expect((b3.children[1] as TextNode).content).toBe(`fine`) - }) - - test('error on v-else missing adjacent v-if', () => { - const ast = parse(`

`) - const spy = jest.fn() - transform(ast, { - nodeTransforms: [transformIf], - onError: spy - }) - expect(spy.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, - loc: ast.children[0].loc.start - } - ]) - - const ast2 = parse(`
`) - const spy2 = jest.fn() - transform(ast2, { - nodeTransforms: [transformIf], - onError: spy2 - }) - expect(spy2.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, - loc: ast2.children[1].loc.start - } - ]) - - const ast3 = parse(`
foo
`) - const spy3 = jest.fn() - transform(ast3, { - nodeTransforms: [transformIf], - onError: spy3 - }) - expect(spy3.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, - loc: ast3.children[2].loc.start - } - ]) - }) - - test('error on v-else-if missing adjacent v-if', () => { - const ast = parse(`
`) - const spy = jest.fn() - transform(ast, { - nodeTransforms: [transformIf], - onError: spy - }) - expect(spy.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - loc: ast.children[0].loc.start - } - ]) - - const ast2 = parse(`
`) - const spy2 = jest.fn() - transform(ast2, { - nodeTransforms: [transformIf], - onError: spy2 - }) - expect(spy2.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - loc: ast2.children[1].loc.start - } - ]) - - const ast3 = parse(`
foo
`) - const spy3 = jest.fn() - transform(ast3, { - nodeTransforms: [transformIf], - onError: spy3 - }) - expect(spy3.mock.calls[0]).toMatchObject([ - { - code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - loc: ast3.children[2].loc.start - } - ]) +describe('compiler: transform v-if', () => { + test('basic v-if', () => { + const ast = parse(`
`) + transform(ast, { + nodeTransforms: [transformIf] }) + const node = ast.children[0] as IfNode + expect(node.type).toBe(NodeTypes.IF) + expect(node.branches.length).toBe(1) + expect(node.branches[0].condition!.content).toBe(`ok`) + expect(node.branches[0].children.length).toBe(1) + expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) }) - describe('codegen', () => { - // TODO + test('template v-if', () => { + const ast = parse(``) + transform(ast, { + nodeTransforms: [transformIf] + }) + const node = ast.children[0] as IfNode + expect(node.type).toBe(NodeTypes.IF) + expect(node.branches.length).toBe(1) + expect(node.branches[0].condition!.content).toBe(`ok`) + expect(node.branches[0].children.length).toBe(3) + expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.branches[0].children[0] as ElementNode).tag).toBe(`div`) + expect(node.branches[0].children[1].type).toBe(NodeTypes.TEXT) + expect((node.branches[0].children[1] as TextNode).content).toBe(`hello`) + expect(node.branches[0].children[2].type).toBe(NodeTypes.ELEMENT) + expect((node.branches[0].children[2] as ElementNode).tag).toBe(`p`) + }) + + test('v-if + v-else', () => { + const ast = parse(`

`) + transform(ast, { + nodeTransforms: [transformIf] + }) + // should fold branches + expect(ast.children.length).toBe(1) + + const node = ast.children[0] as IfNode + expect(node.type).toBe(NodeTypes.IF) + expect(node.branches.length).toBe(2) + + const b1 = node.branches[0] + expect(b1.condition!.content).toBe(`ok`) + expect(b1.children.length).toBe(1) + expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b1.children[0] as ElementNode).tag).toBe(`div`) + + const b2 = node.branches[1] + expect(b2.condition).toBeUndefined() + expect(b2.children.length).toBe(1) + expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b2.children[0] as ElementNode).tag).toBe(`p`) + }) + + test('v-if + v-else-if', () => { + const ast = parse(`

`) + transform(ast, { + nodeTransforms: [transformIf] + }) + // should fold branches + expect(ast.children.length).toBe(1) + + const node = ast.children[0] as IfNode + expect(node.type).toBe(NodeTypes.IF) + expect(node.branches.length).toBe(2) + + const b1 = node.branches[0] + expect(b1.condition!.content).toBe(`ok`) + expect(b1.children.length).toBe(1) + expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b1.children[0] as ElementNode).tag).toBe(`div`) + + const b2 = node.branches[1] + expect(b2.condition!.content).toBe(`orNot`) + expect(b2.children.length).toBe(1) + expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b2.children[0] as ElementNode).tag).toBe(`p`) + }) + + test('v-if + v-else-if + v-else', () => { + const ast = parse( + `

` + ) + transform(ast, { + nodeTransforms: [transformIf] + }) + // should fold branches + expect(ast.children.length).toBe(1) + + const node = ast.children[0] as IfNode + expect(node.type).toBe(NodeTypes.IF) + expect(node.branches.length).toBe(3) + + const b1 = node.branches[0] + expect(b1.condition!.content).toBe(`ok`) + expect(b1.children.length).toBe(1) + expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b1.children[0] as ElementNode).tag).toBe(`div`) + + const b2 = node.branches[1] + expect(b2.condition!.content).toBe(`orNot`) + expect(b2.children.length).toBe(1) + expect(b2.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b2.children[0] as ElementNode).tag).toBe(`p`) + + const b3 = node.branches[2] + expect(b3.condition).toBeUndefined() + expect(b3.children.length).toBe(1) + expect(b3.children[0].type).toBe(NodeTypes.TEXT) + expect((b3.children[0] as TextNode).content).toBe(`fine`) + }) + + test('comment between branches', () => { + const ast = parse(` +

+ +

+ + + `) + transform(ast, { + nodeTransforms: [transformIf] + }) + // should fold branches + expect(ast.children.length).toBe(1) + + const node = ast.children[0] as IfNode + expect(node.type).toBe(NodeTypes.IF) + expect(node.branches.length).toBe(3) + + const b1 = node.branches[0] + expect(b1.condition!.content).toBe(`ok`) + expect(b1.children.length).toBe(1) + expect(b1.children[0].type).toBe(NodeTypes.ELEMENT) + expect((b1.children[0] as ElementNode).tag).toBe(`div`) + + const b2 = node.branches[1] + expect(b2.condition!.content).toBe(`orNot`) + expect(b2.children.length).toBe(2) + expect(b2.children[0].type).toBe(NodeTypes.COMMENT) + expect((b2.children[0] as CommentNode).content).toBe(`foo`) + expect(b2.children[1].type).toBe(NodeTypes.ELEMENT) + expect((b2.children[1] as ElementNode).tag).toBe(`p`) + + const b3 = node.branches[2] + expect(b3.condition).toBeUndefined() + expect(b3.children.length).toBe(2) + expect(b3.children[0].type).toBe(NodeTypes.COMMENT) + expect((b3.children[0] as CommentNode).content).toBe(`bar`) + expect(b3.children[1].type).toBe(NodeTypes.TEXT) + expect((b3.children[1] as TextNode).content).toBe(`fine`) + }) + + test('error on v-else missing adjacent v-if', () => { + const ast = parse(`

`) + const spy = jest.fn() + transform(ast, { + nodeTransforms: [transformIf], + onError: spy + }) + expect(spy.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, + loc: ast.children[0].loc + } + ]) + + const ast2 = parse(`
`) + const spy2 = jest.fn() + transform(ast2, { + nodeTransforms: [transformIf], + onError: spy2 + }) + expect(spy2.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, + loc: ast2.children[1].loc + } + ]) + + const ast3 = parse(`
foo
`) + const spy3 = jest.fn() + transform(ast3, { + nodeTransforms: [transformIf], + onError: spy3 + }) + expect(spy3.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, + loc: ast3.children[2].loc + } + ]) + }) + + test('error on v-else-if missing adjacent v-if', () => { + const ast = parse(`
`) + const spy = jest.fn() + transform(ast, { + nodeTransforms: [transformIf], + onError: spy + }) + expect(spy.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + loc: ast.children[0].loc + } + ]) + + const ast2 = parse(`
`) + const spy2 = jest.fn() + transform(ast2, { + nodeTransforms: [transformIf], + onError: spy2 + }) + expect(spy2.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + loc: ast2.children[1].loc + } + ]) + + const ast3 = parse(`
foo
`) + const spy3 = jest.fn() + transform(ast3, { + nodeTransforms: [transformIf], + onError: spy3 + }) + expect(spy3.mock.calls[0]).toMatchObject([ + { + code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, + loc: ast3.children[2].loc + } + ]) }) }) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 296c1e0b..a495ddce 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -366,7 +366,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) { const { push, indent, deindent, newline } = context const { properties } = node const multilines = properties.length > 1 - push(`{`, node) + push(multilines ? `{` : `{ `, node) multilines && indent() for (let i = 0; i < properties.length; i++) { const { key, value } = properties[i] @@ -385,7 +385,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) { } } multilines && deindent() - push(`}`) + push(multilines ? `}` : ` }`) } function genArrayExpression(node: ArrayExpression, context: CodegenContext) { diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 6f6959b6..431feab4 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -1,8 +1,8 @@ -import { Position } from './ast' +import { SourceLocation } from './ast' export interface CompilerError extends SyntaxError { code: ErrorCodes - loc: Position + loc: SourceLocation } export function defaultOnError(error: CompilerError) { @@ -11,12 +11,12 @@ export function defaultOnError(error: CompilerError) { export function createCompilerError( code: ErrorCodes, - loc: Position + loc: SourceLocation ): CompilerError { const error = new SyntaxError( - `${__DEV__ || !__BROWSER__ ? errorMessages[code] : code} (${loc.line}:${ - loc.column - })` + `${__DEV__ || !__BROWSER__ ? errorMessages[code] : code} (${ + loc.start.line + }:${loc.start.column})` ) as CompilerError error.code = code error.loc = loc diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index fb91b332..2409c920 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -6,6 +6,8 @@ import { isString } from '@vue/shared' import { transformIf } from './transforms/vIf' import { transformFor } from './transforms/vFor' import { prepareElementForCodegen } from './transforms/element' +import { transformOn } from './transforms/vOn' +import { transformBind } from './transforms/vBind' export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions @@ -24,7 +26,8 @@ export function compile( ...(options.nodeTransforms || []) // user transforms ], directiveTransforms: { - // TODO include built-in directive transforms + on: transformOn, + bind: transformBind, ...(options.directiveTransforms || {}) // user transforms } }) diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 73aa22f2..e6f95be7 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -842,7 +842,13 @@ function emitError( loc.offset += offset loc.column += offset } - context.options.onError(createCompilerError(code, loc)) + context.options.onError( + createCompilerError(code, { + start: loc, + end: loc, + source: '' + }) + ) } function isEnd( diff --git a/packages/compiler-core/src/transforms/element.ts b/packages/compiler-core/src/transforms/element.ts index 066874c3..42194252 100644 --- a/packages/compiler-core/src/transforms/element.ts +++ b/packages/compiler-core/src/transforms/element.ts @@ -123,10 +123,7 @@ function buildProps( mergeArgs.push(prop.exp) } else { context.onError( - createCompilerError( - ErrorCodes.X_V_BIND_NO_EXPRESSION, - prop.loc.start - ) + createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc) ) } continue diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts index 70b786d1..1b131a32 100644 --- a/packages/compiler-core/src/transforms/vBind.ts +++ b/packages/compiler-core/src/transforms/vBind.ts @@ -1 +1,24 @@ -// TODO +import { DirectiveTransform } from '../transform' +import { createObjectProperty, createExpression } from '../ast' +import { createCompilerError, ErrorCodes } from '../errors' + +// v-bind without arg is handled directly in ./element.ts due to it affecting +// codegen for the entire props object. This transform here is only for v-bind +// *with* args. +export const transformBind: DirectiveTransform = (dir, context) => { + if (!dir.exp) { + context.onError( + createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, dir.loc) + ) + } + // TODO handle .prop modifier + // TODO handle .sync modifier + return { + props: createObjectProperty( + dir.arg!, + dir.exp || createExpression('', true, dir.loc), + dir.loc + ), + needRuntime: false + } +} diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 8e25ab09..54dd3de0 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -32,15 +32,12 @@ export const transformFor = createStructuralDirectiveTransform( }) } else { context.onError( - createCompilerError( - ErrorCodes.X_FOR_MALFORMED_EXPRESSION, - dir.loc.start - ) + createCompilerError(ErrorCodes.X_FOR_MALFORMED_EXPRESSION, dir.loc) ) } } else { context.onError( - createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start) + createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc) ) } } diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 85f2165d..69b5db1e 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -49,7 +49,7 @@ export const transformIf = createStructuralDirectiveTransform( dir.name === 'else' ? ErrorCodes.X_ELSE_NO_ADJACENT_IF : ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, - node.loc.start + node.loc ) ) } diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index 70b786d1..b7eae459 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -1 +1,24 @@ -// TODO +import { DirectiveTransform } from '../transform' +import { createObjectProperty, createExpression } from '../ast' +import { capitalize } from '@vue/shared' + +// v-on without arg is handled directly in ./element.ts due to it affecting +// codegen for the entire props object. This transform here is only for v-on +// *with* args. +export const transformOn: DirectiveTransform = (dir, context) => { + const arg = dir.arg! + const eventName = arg.isStatic + ? createExpression(`on${capitalize(arg.content)}`, true, arg.loc) + : // TODO inject capitalize helper + createExpression(`'on' + capitalize(${arg.content})`, false, arg.loc) + // TODO .once modifier handling since it is platform agnostic + // other modifiers are handled in compiler-dom + return { + props: createObjectProperty( + eventName, + dir.exp || createExpression(`() => {}`, false, dir.loc), + dir.loc + ), + needRuntime: false + } +}