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_PROPERTY,
JS_ARRAY_EXPRESSION,
JS_SLOT_FUNCTION
JS_SLOT_FUNCTION,
JS_SEQUENCE_EXPRESSION,
JS_CONDITIONAL_EXPRESSION
}
export const enum ElementTypes {
@ -61,7 +63,7 @@ export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
export type ChildNode =
export type TemplateChildNode =
| ElementNode
| InterpolationNode
| CompoundExpressionNode
@ -72,7 +74,7 @@ export type ChildNode =
export interface RootNode extends Node {
type: NodeTypes.ROOT
children: ChildNode[]
children: TemplateChildNode[]
imports: string[]
statements: string[]
hoists: JSChildNode[]
@ -85,7 +87,7 @@ export interface ElementNode extends Node {
tagType: ElementTypes
isSelfClosing: boolean
props: Array<AttributeNode | DirectiveNode>
children: ChildNode[]
children: TemplateChildNode[]
codegenNode: CallExpression | undefined
}
@ -140,12 +142,13 @@ export interface CompoundExpressionNode extends Node {
export interface IfNode extends Node {
type: NodeTypes.IF
branches: IfBranchNode[]
codegenNode: JSChildNode | undefined
}
export interface IfBranchNode extends Node {
type: NodeTypes.IF_BRANCH
condition: ExpressionNode | undefined // else
children: ChildNode[]
children: TemplateChildNode[]
}
export interface ForNode extends Node {
@ -154,7 +157,7 @@ export interface ForNode extends Node {
valueAlias: ExpressionNode | undefined
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
children: ChildNode[]
children: TemplateChildNode[]
}
// We also include a number of JavaScript AST nodes for code generation.
@ -166,11 +169,13 @@ export type JSChildNode =
| ArrayExpression
| ExpressionNode
| SlotFunctionExpression
| ConditionalExpression
| SequenceExpression
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
callee: string
arguments: (string | JSChildNode | ChildNode[])[]
arguments: (string | JSChildNode | TemplateChildNode[])[]
}
export interface ObjectExpression extends Node {
@ -192,7 +197,19 @@ export interface ArrayExpression extends Node {
export interface SlotFunctionExpression extends Node {
type: NodeTypes.JS_SLOT_FUNCTION
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(
@ -282,7 +299,7 @@ export function createCallExpression(
export function createFunctionExpression(
params: ExpressionNode | undefined,
returns: ChildNode[],
returns: TemplateChildNode[],
loc: SourceLocation
): SlotFunctionExpression {
return {
@ -292,3 +309,33 @@ export function createFunctionExpression(
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 {
RootNode,
ChildNode,
TemplateChildNode,
ElementNode,
IfNode,
ForNode,
@ -19,7 +19,9 @@ import {
CompoundExpressionNode,
SimpleExpressionNode,
ElementTypes,
SlotFunctionExpression
SlotFunctionExpression,
SequenceExpression,
ConditionalExpression
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
@ -35,7 +37,7 @@ import {
COMMENT
} from './runtimeConstants'
type CodegenNode = ChildNode | JSChildNode
type CodegenNode = TemplateChildNode | JSChildNode
export interface CodegenOptions {
// - Module mode will generate ES module import statements for helpers
@ -269,7 +271,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
// - expression
// - <slot> outlet, which always produces an array
function genChildren(
children: ChildNode[],
children: TemplateChildNode[],
context: CodegenContext,
allowSingle: boolean = false
) {
@ -294,7 +296,7 @@ function genChildren(
}
function genNodeListAsArray(
nodes: (string | CodegenNode | ChildNode[])[],
nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext
) {
const multilines =
@ -312,7 +314,7 @@ function genNodeListAsArray(
}
function genNodeList(
nodes: (string | CodegenNode | ChildNode[])[],
nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext,
multilines: boolean = false
) {
@ -375,6 +377,12 @@ function genNode(node: CodegenNode, context: CodegenContext) {
case NodeTypes.JS_SLOT_FUNCTION:
genSlotFunction(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
genSequenceExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
/* istanbul ignore next */
default:
if (__DEV__) {
@ -593,3 +601,37 @@ function genSlotFunction(
// pre-normalized slots should always return arrays
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,
SourceLocation,
TextNode,
ChildNode,
TemplateChildNode,
InterpolationNode
} from './ast'
@ -115,15 +115,15 @@ function parseChildren(
context: ParserContext,
mode: TextModes,
ancestors: ElementNode[]
): ChildNode[] {
): TemplateChildNode[] {
const parent = last(ancestors)
const ns = parent ? parent.ns : Namespaces.HTML
const nodes: ChildNode[] = []
const nodes: TemplateChildNode[] = []
while (!isEnd(context, mode, ancestors)) {
__DEV__ && assert(context.source.length > 0)
const s = context.source
let node: ChildNode | ChildNode[] | undefined = undefined
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (startsWith(s, context.options.delimiters[0])) {
// '{{'
@ -197,8 +197,8 @@ function parseChildren(
function pushNode(
context: ParserContext,
nodes: ChildNode[],
node: ChildNode
nodes: TemplateChildNode[],
node: TemplateChildNode
): void {
// ignore comments in production
/* istanbul ignore next */
@ -234,7 +234,7 @@ function pushNode(
function parseCDATA(
context: ParserContext,
ancestors: ElementNode[]
): ChildNode[] {
): TemplateChildNode[] {
__DEV__ &&
assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML)
__DEV__ && assert(startsWith(context.source, '<![CDATA['))

View File

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

View File

@ -1,13 +1,15 @@
import { NodeTransform } from '../transform'
import {
NodeTypes,
ChildNode,
TemplateChildNode,
TextNode,
InterpolationNode,
CompoundExpressionNode
} 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
// Merge adjacent text nodes and expressions into a single expression

View File

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