feat(compiler): basic v-bind & v-on transforms

This commit is contained in:
Evan You 2019-09-22 22:19:42 -04:00
parent 3ab016e44f
commit 914087edea
14 changed files with 749 additions and 715 deletions

View File

@ -195,7 +195,9 @@ describe('compiler: parse', () => {
[ [
{ {
code: ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE, 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, 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, code: ErrorCodes.CONTROL_CHARACTER_REFERENCE,
loc: { offset: 0, line: 1, column: 1 } loc: {
start: { offset: 0, line: 1, column: 1 }
}
} }
] ]
]) ])
@ -1254,21 +1260,25 @@ describe('compiler: parse', () => {
{ {
code: ErrorCodes.X_MISSING_END_TAG, code: ErrorCodes.X_MISSING_END_TAG,
loc: { loc: {
start: {
offset: 13, offset: 13,
line: 3, line: 3,
column: 1 column: 1
} }
} }
}
], ],
[ [
{ {
code: ErrorCodes.X_INVALID_END_TAG, code: ErrorCodes.X_INVALID_END_TAG,
loc: { loc: {
start: {
offset: 20, offset: 20,
line: 4, line: 4,
column: 1 column: 1
} }
} }
}
] ]
]) ])
@ -2386,7 +2396,7 @@ describe('compiler: parse', () => {
expect( expect(
spy.mock.calls.map(([err]) => ({ spy.mock.calls.map(([err]) => ({
type: err.code, type: err.code,
loc: err.loc loc: err.loc.start
})) }))
).toMatchObject(errors) ).toMatchObject(errors)
expect(ast).toMatchSnapshot() expect(ast).toMatchSnapshot()

View File

@ -162,10 +162,10 @@ describe('compiler: transform', () => {
test('onError option', () => { test('onError option', () => {
const ast = parse(`<div/>`) const ast = parse(`<div/>`)
const loc = ast.children[0].loc.start const loc = ast.children[0].loc
const plugin: NodeTransform = (node, context) => { const plugin: NodeTransform = (node, context) => {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc.start) createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc)
) )
} }
const spy = jest.fn() const spy = jest.fn()

View File

@ -0,0 +1,3 @@
describe('compiler: element transform', () => {
test.todo('should work')
})

View File

@ -4,8 +4,7 @@ import { transformFor } from '../../src/transforms/vFor'
import { ForNode, NodeTypes } from '../../src/ast' import { ForNode, NodeTypes } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
describe('v-for', () => { describe('compiler: transform v-for', () => {
describe('transform', () => {
test('number expression', () => { test('number expression', () => {
const node = parse('<span v-for="index in 5" />') const node = parse('<span v-for="index in 5" />')
@ -306,18 +305,14 @@ describe('v-for', () => {
source.indexOf('item') - 1 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( expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
source.indexOf('item')
)
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 source.indexOf('item') + 4
) )
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe( expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
source.indexOf('items') - 1
)
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(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
@ -341,18 +336,14 @@ describe('v-for', () => {
source.indexOf('item') - 1 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( expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
source.indexOf('item')
)
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 source.indexOf('item') + 4
) )
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe( expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
source.indexOf('items') - 1
)
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(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
@ -385,9 +376,7 @@ describe('v-for', () => {
) )
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe( expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
source.indexOf('items') - 1
)
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(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
@ -411,18 +400,14 @@ describe('v-for', () => {
source.indexOf('item') - 1 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( expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
source.indexOf('item')
)
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 source.indexOf('item') + 4
) )
expect(forNode.keyAlias!.content).toBe('key') expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.keyAlias!.loc.start.offset).toBe( expect(forNode.keyAlias!.loc.start.offset).toBe(source.indexOf('key') - 1)
source.indexOf('key') - 1
)
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(source.indexOf('key'))
expect(forNode.keyAlias!.loc.end.line).toBe(1) expect(forNode.keyAlias!.loc.end.line).toBe(1)
@ -442,9 +427,7 @@ describe('v-for', () => {
) )
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe( expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
source.indexOf('items') - 1
)
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(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
@ -468,9 +451,7 @@ describe('v-for', () => {
source.indexOf('item') - 1 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( expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
source.indexOf('item')
)
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 source.indexOf('item') + 4
@ -490,9 +471,7 @@ describe('v-for', () => {
) )
expect(forNode.source.content).toBe('items') expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe( expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
source.indexOf('items') - 1
)
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(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
@ -500,4 +479,3 @@ describe('v-for', () => {
}) })
}) })
}) })
})

View File

@ -10,8 +10,7 @@ import {
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
describe('compiler: v-if', () => { describe('compiler: transform v-if', () => {
describe('transform', () => {
test('basic v-if', () => { test('basic v-if', () => {
const ast = parse(`<div v-if="ok"/>`) const ast = parse(`<div v-if="ok"/>`)
transform(ast, { transform(ast, {
@ -178,7 +177,7 @@ describe('compiler: v-if', () => {
expect(spy.mock.calls[0]).toMatchObject([ expect(spy.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
loc: ast.children[0].loc.start loc: ast.children[0].loc
} }
]) ])
@ -191,7 +190,7 @@ describe('compiler: v-if', () => {
expect(spy2.mock.calls[0]).toMatchObject([ expect(spy2.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
loc: ast2.children[1].loc.start loc: ast2.children[1].loc
} }
]) ])
@ -204,7 +203,7 @@ describe('compiler: v-if', () => {
expect(spy3.mock.calls[0]).toMatchObject([ expect(spy3.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
loc: ast3.children[2].loc.start loc: ast3.children[2].loc
} }
]) ])
}) })
@ -219,7 +218,7 @@ describe('compiler: v-if', () => {
expect(spy.mock.calls[0]).toMatchObject([ expect(spy.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
loc: ast.children[0].loc.start loc: ast.children[0].loc
} }
]) ])
@ -232,7 +231,7 @@ describe('compiler: v-if', () => {
expect(spy2.mock.calls[0]).toMatchObject([ expect(spy2.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
loc: ast2.children[1].loc.start loc: ast2.children[1].loc
} }
]) ])
@ -245,13 +244,8 @@ describe('compiler: v-if', () => {
expect(spy3.mock.calls[0]).toMatchObject([ expect(spy3.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
loc: ast3.children[2].loc.start loc: ast3.children[2].loc
} }
]) ])
}) })
}) })
describe('codegen', () => {
// TODO
})
})

View File

@ -366,7 +366,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline } = context const { push, indent, deindent, newline } = context
const { properties } = node const { properties } = node
const multilines = properties.length > 1 const multilines = properties.length > 1
push(`{`, node) push(multilines ? `{` : `{ `, node)
multilines && indent() multilines && indent()
for (let i = 0; i < properties.length; i++) { for (let i = 0; i < properties.length; i++) {
const { key, value } = properties[i] const { key, value } = properties[i]
@ -385,7 +385,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
} }
} }
multilines && deindent() multilines && deindent()
push(`}`) push(multilines ? `}` : ` }`)
} }
function genArrayExpression(node: ArrayExpression, context: CodegenContext) { function genArrayExpression(node: ArrayExpression, context: CodegenContext) {

View File

@ -1,8 +1,8 @@
import { Position } from './ast' import { SourceLocation } from './ast'
export interface CompilerError extends SyntaxError { export interface CompilerError extends SyntaxError {
code: ErrorCodes code: ErrorCodes
loc: Position loc: SourceLocation
} }
export function defaultOnError(error: CompilerError) { export function defaultOnError(error: CompilerError) {
@ -11,12 +11,12 @@ export function defaultOnError(error: CompilerError) {
export function createCompilerError( export function createCompilerError(
code: ErrorCodes, code: ErrorCodes,
loc: Position loc: SourceLocation
): CompilerError { ): CompilerError {
const error = new SyntaxError( const error = new SyntaxError(
`${__DEV__ || !__BROWSER__ ? errorMessages[code] : code} (${loc.line}:${ `${__DEV__ || !__BROWSER__ ? errorMessages[code] : code} (${
loc.column loc.start.line
})` }:${loc.start.column})`
) as CompilerError ) as CompilerError
error.code = code error.code = code
error.loc = loc error.loc = loc

View File

@ -6,6 +6,8 @@ import { isString } from '@vue/shared'
import { transformIf } from './transforms/vIf' import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor' import { transformFor } from './transforms/vFor'
import { prepareElementForCodegen } from './transforms/element' import { prepareElementForCodegen } from './transforms/element'
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
@ -24,7 +26,8 @@ export function compile(
...(options.nodeTransforms || []) // user transforms ...(options.nodeTransforms || []) // user transforms
], ],
directiveTransforms: { directiveTransforms: {
// TODO include built-in directive transforms on: transformOn,
bind: transformBind,
...(options.directiveTransforms || {}) // user transforms ...(options.directiveTransforms || {}) // user transforms
} }
}) })

View File

@ -842,7 +842,13 @@ function emitError(
loc.offset += offset loc.offset += offset
loc.column += offset loc.column += offset
} }
context.options.onError(createCompilerError(code, loc)) context.options.onError(
createCompilerError(code, {
start: loc,
end: loc,
source: ''
})
)
} }
function isEnd( function isEnd(

View File

@ -123,10 +123,7 @@ function buildProps(
mergeArgs.push(prop.exp) mergeArgs.push(prop.exp)
} else { } else {
context.onError( context.onError(
createCompilerError( createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc)
ErrorCodes.X_V_BIND_NO_EXPRESSION,
prop.loc.start
)
) )
} }
continue continue

View File

@ -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
}
}

View File

@ -32,15 +32,12 @@ export const transformFor = createStructuralDirectiveTransform(
}) })
} else { } else {
context.onError( context.onError(
createCompilerError( createCompilerError(ErrorCodes.X_FOR_MALFORMED_EXPRESSION, dir.loc)
ErrorCodes.X_FOR_MALFORMED_EXPRESSION,
dir.loc.start
)
) )
} }
} else { } else {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start) createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc)
) )
} }
} }

View File

@ -49,7 +49,7 @@ export const transformIf = createStructuralDirectiveTransform(
dir.name === 'else' dir.name === 'else'
? ErrorCodes.X_ELSE_NO_ADJACENT_IF ? ErrorCodes.X_ELSE_NO_ADJACENT_IF
: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, : ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
node.loc.start node.loc
) )
) )
} }

View File

@ -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
}
}