wip: Sequence & Conditional expressions for AST

This commit is contained in:
Evan You 2019-10-01 09:27:34 -04:00
parent 2f155dbcb8
commit e31fb3c172
6 changed files with 124 additions and 33 deletions

View File

@ -28,7 +28,9 @@ export const enum NodeTypes {
JS_OBJECT_EXPRESSION, JS_OBJECT_EXPRESSION,
JS_PROPERTY, JS_PROPERTY,
JS_ARRAY_EXPRESSION, JS_ARRAY_EXPRESSION,
JS_SLOT_FUNCTION JS_SLOT_FUNCTION,
JS_SEQUENCE_EXPRESSION,
JS_CONDITIONAL_EXPRESSION
} }
export const enum ElementTypes { export const enum ElementTypes {
@ -61,7 +63,7 @@ export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
export type ChildNode = export type TemplateChildNode =
| ElementNode | ElementNode
| InterpolationNode | InterpolationNode
| CompoundExpressionNode | CompoundExpressionNode
@ -72,7 +74,7 @@ export type ChildNode =
export interface RootNode extends Node { export interface RootNode extends Node {
type: NodeTypes.ROOT type: NodeTypes.ROOT
children: ChildNode[] children: TemplateChildNode[]
imports: string[] imports: string[]
statements: string[] statements: string[]
hoists: JSChildNode[] hoists: JSChildNode[]
@ -85,7 +87,7 @@ export interface ElementNode extends Node {
tagType: ElementTypes tagType: ElementTypes
isSelfClosing: boolean isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode> props: Array<AttributeNode | DirectiveNode>
children: ChildNode[] children: TemplateChildNode[]
codegenNode: CallExpression | undefined codegenNode: CallExpression | undefined
} }
@ -140,12 +142,13 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node { export interface IfNode extends Node {
type: NodeTypes.IF type: NodeTypes.IF
branches: IfBranchNode[] branches: IfBranchNode[]
codegenNode: JSChildNode | undefined
} }
export interface IfBranchNode extends Node { export interface IfBranchNode extends Node {
type: NodeTypes.IF_BRANCH type: NodeTypes.IF_BRANCH
condition: ExpressionNode | undefined // else condition: ExpressionNode | undefined // else
children: ChildNode[] children: TemplateChildNode[]
} }
export interface ForNode extends Node { export interface ForNode extends Node {
@ -154,7 +157,7 @@ export interface ForNode extends Node {
valueAlias: ExpressionNode | undefined valueAlias: ExpressionNode | undefined
keyAlias: ExpressionNode | undefined keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined objectIndexAlias: ExpressionNode | undefined
children: ChildNode[] children: TemplateChildNode[]
} }
// We also include a number of JavaScript AST nodes for code generation. // We also include a number of JavaScript AST nodes for code generation.
@ -166,11 +169,13 @@ export type JSChildNode =
| ArrayExpression | ArrayExpression
| ExpressionNode | ExpressionNode
| SlotFunctionExpression | SlotFunctionExpression
| ConditionalExpression
| SequenceExpression
export interface CallExpression extends Node { export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION type: NodeTypes.JS_CALL_EXPRESSION
callee: string callee: string
arguments: (string | JSChildNode | ChildNode[])[] arguments: (string | JSChildNode | TemplateChildNode[])[]
} }
export interface ObjectExpression extends Node { export interface ObjectExpression extends Node {
@ -192,7 +197,19 @@ export interface ArrayExpression extends Node {
export interface SlotFunctionExpression extends Node { export interface SlotFunctionExpression extends Node {
type: NodeTypes.JS_SLOT_FUNCTION type: NodeTypes.JS_SLOT_FUNCTION
params: ExpressionNode | undefined params: ExpressionNode | undefined
returns: ChildNode[] returns: TemplateChildNode[]
}
export interface SequenceExpression extends Node {
type: NodeTypes.JS_SEQUENCE_EXPRESSION
expressions: JSChildNode[]
}
export interface ConditionalExpression extends Node {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
test: ExpressionNode
consequent: JSChildNode
alternate: JSChildNode
} }
export function createArrayExpression( export function createArrayExpression(
@ -282,7 +299,7 @@ export function createCallExpression(
export function createFunctionExpression( export function createFunctionExpression(
params: ExpressionNode | undefined, params: ExpressionNode | undefined,
returns: ChildNode[], returns: TemplateChildNode[],
loc: SourceLocation loc: SourceLocation
): SlotFunctionExpression { ): SlotFunctionExpression {
return { return {
@ -292,3 +309,33 @@ export function createFunctionExpression(
loc loc
} }
} }
// sequence and conditional expressions are never associated with template nodes,
// so their source locations are just a stub.
const locStub: SourceLocation = {
source: '',
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 }
}
export function createSequenceExpression(expressions: JSChildNode[]) {
return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions,
loc: locStub
}
}
export function createConditionalExpression(
test: ExpressionNode,
consequent: JSChildNode,
alternate: JSChildNode
) {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test,
consequent,
alternate,
loc: locStub
}
}

View File

@ -1,6 +1,6 @@
import { import {
RootNode, RootNode,
ChildNode, TemplateChildNode,
ElementNode, ElementNode,
IfNode, IfNode,
ForNode, ForNode,
@ -19,7 +19,9 @@ import {
CompoundExpressionNode, CompoundExpressionNode,
SimpleExpressionNode, SimpleExpressionNode,
ElementTypes, ElementTypes,
SlotFunctionExpression SlotFunctionExpression,
SequenceExpression,
ConditionalExpression
} from './ast' } from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map' import { SourceMapGenerator, RawSourceMap } from 'source-map'
import { import {
@ -35,7 +37,7 @@ import {
COMMENT COMMENT
} from './runtimeConstants' } from './runtimeConstants'
type CodegenNode = ChildNode | JSChildNode type CodegenNode = TemplateChildNode | JSChildNode
export interface CodegenOptions { export interface CodegenOptions {
// - Module mode will generate ES module import statements for helpers // - Module mode will generate ES module import statements for helpers
@ -269,7 +271,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
// - expression // - expression
// - <slot> outlet, which always produces an array // - <slot> outlet, which always produces an array
function genChildren( function genChildren(
children: ChildNode[], children: TemplateChildNode[],
context: CodegenContext, context: CodegenContext,
allowSingle: boolean = false allowSingle: boolean = false
) { ) {
@ -294,7 +296,7 @@ function genChildren(
} }
function genNodeListAsArray( function genNodeListAsArray(
nodes: (string | CodegenNode | ChildNode[])[], nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext context: CodegenContext
) { ) {
const multilines = const multilines =
@ -312,7 +314,7 @@ function genNodeListAsArray(
} }
function genNodeList( function genNodeList(
nodes: (string | CodegenNode | ChildNode[])[], nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext, context: CodegenContext,
multilines: boolean = false multilines: boolean = false
) { ) {
@ -375,6 +377,12 @@ function genNode(node: CodegenNode, context: CodegenContext) {
case NodeTypes.JS_SLOT_FUNCTION: case NodeTypes.JS_SLOT_FUNCTION:
genSlotFunction(node, context) genSlotFunction(node, context)
break break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
genSequenceExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
/* istanbul ignore next */ /* istanbul ignore next */
default: default:
if (__DEV__) { if (__DEV__) {
@ -593,3 +601,37 @@ function genSlotFunction(
// pre-normalized slots should always return arrays // pre-normalized slots should always return arrays
genNodeListAsArray(node.returns, context) genNodeListAsArray(node.returns, context)
} }
function genConditionalExpression(
node: ConditionalExpression,
context: CodegenContext
) {
const { test, consequent, alternate } = node
const { push, indent, deindent, newline } = context
if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
const needsQuote = !isSimpleIdentifier(test.content)
needsQuote && push(`(`)
genExpression(test, context)
needsQuote && push(`)`)
} else {
genCompoundExpression(test, context)
}
indent()
context.indentLevel++
push(`? `)
genNode(consequent, context)
context.indentLevel--
newline()
push(`: `)
genNode(alternate, context)
deindent(true /* without newline */)
}
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext
) {
context.push(`(`)
genNodeList(node.expressions, context)
context.push(`)`)
}

View File

@ -23,7 +23,7 @@ import {
RootNode, RootNode,
SourceLocation, SourceLocation,
TextNode, TextNode,
ChildNode, TemplateChildNode,
InterpolationNode InterpolationNode
} from './ast' } from './ast'
@ -115,15 +115,15 @@ function parseChildren(
context: ParserContext, context: ParserContext,
mode: TextModes, mode: TextModes,
ancestors: ElementNode[] ancestors: ElementNode[]
): ChildNode[] { ): TemplateChildNode[] {
const parent = last(ancestors) const parent = last(ancestors)
const ns = parent ? parent.ns : Namespaces.HTML const ns = parent ? parent.ns : Namespaces.HTML
const nodes: ChildNode[] = [] const nodes: TemplateChildNode[] = []
while (!isEnd(context, mode, ancestors)) { while (!isEnd(context, mode, ancestors)) {
__DEV__ && assert(context.source.length > 0) __DEV__ && assert(context.source.length > 0)
const s = context.source const s = context.source
let node: ChildNode | ChildNode[] | undefined = undefined let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (startsWith(s, context.options.delimiters[0])) { if (startsWith(s, context.options.delimiters[0])) {
// '{{' // '{{'
@ -197,8 +197,8 @@ function parseChildren(
function pushNode( function pushNode(
context: ParserContext, context: ParserContext,
nodes: ChildNode[], nodes: TemplateChildNode[],
node: ChildNode node: TemplateChildNode
): void { ): void {
// ignore comments in production // ignore comments in production
/* istanbul ignore next */ /* istanbul ignore next */
@ -234,7 +234,7 @@ function pushNode(
function parseCDATA( function parseCDATA(
context: ParserContext, context: ParserContext,
ancestors: ElementNode[] ancestors: ElementNode[]
): ChildNode[] { ): TemplateChildNode[] {
__DEV__ && __DEV__ &&
assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML) assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML)
__DEV__ && assert(startsWith(context.source, '<![CDATA[')) __DEV__ && assert(startsWith(context.source, '<![CDATA['))

View File

@ -2,7 +2,7 @@ import {
RootNode, RootNode,
NodeTypes, NodeTypes,
ParentNode, ParentNode,
ChildNode, TemplateChildNode,
ElementNode, ElementNode,
DirectiveNode, DirectiveNode,
Property, Property,
@ -21,7 +21,7 @@ import { TO_STRING, COMMENT, CREATE_VNODE } from './runtimeConstants'
// Transforms that operate directly on a ChildNode. NodeTransforms may mutate, // Transforms that operate directly on a ChildNode. NodeTransforms may mutate,
// replace or remove the node being processed. // replace or remove the node being processed.
export type NodeTransform = ( export type NodeTransform = (
node: RootNode | ChildNode, node: RootNode | TemplateChildNode,
context: TransformContext context: TransformContext
) => void | (() => void) | (() => void)[] ) => void | (() => void) | (() => void)[]
@ -59,10 +59,10 @@ export interface TransformContext extends Required<TransformOptions> {
identifiers: { [name: string]: number | undefined } identifiers: { [name: string]: number | undefined }
parent: ParentNode | null parent: ParentNode | null
childIndex: number childIndex: number
currentNode: RootNode | ChildNode | null currentNode: RootNode | TemplateChildNode | null
helper(name: string): string helper(name: string): string
replaceNode(node: ChildNode): void replaceNode(node: TemplateChildNode): void
removeNode(node?: ChildNode): void removeNode(node?: TemplateChildNode): void
onNodeRemoved: () => void onNodeRemoved: () => void
addIdentifiers(exp: ExpressionNode): void addIdentifiers(exp: ExpressionNode): void
removeIdentifiers(exp: ExpressionNode): void removeIdentifiers(exp: ExpressionNode): void
@ -207,7 +207,7 @@ export function traverseChildren(
} }
export function traverseNode( export function traverseNode(
node: RootNode | ChildNode, node: RootNode | TemplateChildNode,
context: TransformContext context: TransformContext
) { ) {
// apply transform plugins // apply transform plugins

View File

@ -1,13 +1,15 @@
import { NodeTransform } from '../transform' import { NodeTransform } from '../transform'
import { import {
NodeTypes, NodeTypes,
ChildNode, TemplateChildNode,
TextNode, TextNode,
InterpolationNode, InterpolationNode,
CompoundExpressionNode CompoundExpressionNode
} from '../ast' } from '../ast'
const isText = (node: ChildNode): node is TextNode | InterpolationNode => const isText = (
node: TemplateChildNode
): node is TextNode | InterpolationNode =>
node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
// Merge adjacent text nodes and expressions into a single expression // Merge adjacent text nodes and expressions into a single expression

View File

@ -10,7 +10,7 @@ import {
ElementTypes, ElementTypes,
ExpressionNode, ExpressionNode,
Property, Property,
ChildNode, TemplateChildNode,
SourceLocation SourceLocation
} from '../ast' } from '../ast'
import { TransformContext, NodeTransform } from '../transform' import { TransformContext, NodeTransform } from '../transform'
@ -67,7 +67,7 @@ export function buildSlots(
// 2. Iterate through children and check for template slots // 2. Iterate through children and check for template slots
// <template v-slot:foo="{ prop }"> // <template v-slot:foo="{ prop }">
let hasTemplateSlots = false let hasTemplateSlots = false
let extraneousChild: ChildNode | undefined = undefined let extraneousChild: TemplateChildNode | undefined = undefined
const seenSlotNames = new Set<string>() const seenSlotNames = new Set<string>()
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const child = children[i] const child = children[i]
@ -135,7 +135,7 @@ export function buildSlots(
function buildSlot( function buildSlot(
name: string | ExpressionNode, name: string | ExpressionNode,
slotProps: ExpressionNode | undefined, slotProps: ExpressionNode | undefined,
children: ChildNode[], children: TemplateChildNode[],
loc: SourceLocation loc: SourceLocation
): Property { ): Property {
return createObjectProperty( return createObjectProperty(