import { isString } from '@vue/shared' import { ForParseResult } from './transforms/vFor' import { CREATE_VNODE, WITH_DIRECTIVES, RENDER_SLOT, CREATE_SLOTS, RENDER_LIST, OPEN_BLOCK, CREATE_BLOCK, FRAGMENT } from './runtimeHelpers' import { PropsExpression } from './transforms/transformElement' // Vue template is a platform-agnostic superset of HTML (syntax only). // More namespaces like SVG and MathML are declared by platform specific // compilers. export type Namespace = number export const enum Namespaces { HTML } export const enum NodeTypes { ROOT, ELEMENT, TEXT, COMMENT, SIMPLE_EXPRESSION, INTERPOLATION, ATTRIBUTE, DIRECTIVE, // containers COMPOUND_EXPRESSION, IF, IF_BRANCH, FOR, TEXT_CALL, // codegen JS_CALL_EXPRESSION, JS_OBJECT_EXPRESSION, JS_PROPERTY, JS_ARRAY_EXPRESSION, JS_FUNCTION_EXPRESSION, JS_SEQUENCE_EXPRESSION, JS_CONDITIONAL_EXPRESSION, JS_CACHE_EXPRESSION } export const enum ElementTypes { ELEMENT, COMPONENT, SLOT, TEMPLATE } export interface Node { type: NodeTypes loc: SourceLocation } // The node's range. The `start` is inclusive and `end` is exclusive. // [start, end) export interface SourceLocation { start: Position end: Position source: string } export interface Position { offset: number // from start of file line: number column: number } export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode export type TemplateChildNode = | ElementNode | InterpolationNode | CompoundExpressionNode | TextNode | CommentNode | IfNode | ForNode | TextCallNode export interface RootNode extends Node { type: NodeTypes.ROOT children: TemplateChildNode[] helpers: symbol[] components: string[] directives: string[] hoists: JSChildNode[] cached: number codegenNode: TemplateChildNode | JSChildNode | undefined } export type ElementNode = | PlainElementNode | ComponentNode | SlotOutletNode | TemplateNode export interface BaseElementNode extends Node { type: NodeTypes.ELEMENT ns: Namespace tag: string tagType: ElementTypes isSelfClosing: boolean props: Array children: TemplateChildNode[] codegenNode: | CallExpression | SimpleExpressionNode | CacheExpression | undefined } export interface PlainElementNode extends BaseElementNode { tagType: ElementTypes.ELEMENT codegenNode: | ElementCodegenNode | undefined | SimpleExpressionNode // when hoisted | CacheExpression // when cached by v-once } export interface ComponentNode extends BaseElementNode { tagType: ElementTypes.COMPONENT codegenNode: ComponentCodegenNode | undefined | CacheExpression // when cached by v-once } export interface SlotOutletNode extends BaseElementNode { tagType: ElementTypes.SLOT codegenNode: SlotOutletCodegenNode | undefined | CacheExpression // when cached by v-once } export interface TemplateNode extends BaseElementNode { tagType: ElementTypes.TEMPLATE codegenNode: ElementCodegenNode | undefined | CacheExpression } export interface TextNode extends Node { type: NodeTypes.TEXT content: string } export interface CommentNode extends Node { type: NodeTypes.COMMENT content: string } export interface AttributeNode extends Node { type: NodeTypes.ATTRIBUTE name: string value: TextNode | undefined } export interface DirectiveNode extends Node { type: NodeTypes.DIRECTIVE name: string exp: ExpressionNode | undefined arg: ExpressionNode | undefined modifiers: string[] // optional property to cache the expression parse result for v-for parseResult?: ForParseResult } export interface SimpleExpressionNode extends Node { type: NodeTypes.SIMPLE_EXPRESSION content: string isStatic: boolean isConstant: boolean // an expression parsed as the params of a function will track // the identifiers declared inside the function body. identifiers?: string[] } export interface InterpolationNode extends Node { type: NodeTypes.INTERPOLATION content: ExpressionNode } export interface CompoundExpressionNode extends Node { type: NodeTypes.COMPOUND_EXPRESSION children: ( | SimpleExpressionNode | InterpolationNode | TextNode | string | symbol)[] // an expression parsed as the params of a function will track // the identifiers declared inside the function body. identifiers?: string[] } export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] codegenNode: IfCodegenNode } export interface IfBranchNode extends Node { type: NodeTypes.IF_BRANCH condition: ExpressionNode | undefined // else children: TemplateChildNode[] } export interface ForNode extends Node { type: NodeTypes.FOR source: ExpressionNode valueAlias: ExpressionNode | undefined keyAlias: ExpressionNode | undefined objectIndexAlias: ExpressionNode | undefined children: TemplateChildNode[] codegenNode: ForCodegenNode } export interface TextCallNode extends Node { type: NodeTypes.TEXT_CALL content: TextNode | InterpolationNode | CompoundExpressionNode codegenNode: CallExpression } // We also include a number of JavaScript AST nodes for code generation. // The AST is an intentionally minimal subset just to meet the exact needs of // Vue render function generation. export type JSChildNode = | CallExpression | ObjectExpression | ArrayExpression | ExpressionNode | FunctionExpression | ConditionalExpression | SequenceExpression | CacheExpression export interface CallExpression extends Node { type: NodeTypes.JS_CALL_EXPRESSION callee: string | symbol arguments: ( | string | symbol | JSChildNode | TemplateChildNode | TemplateChildNode[])[] } export interface ObjectExpression extends Node { type: NodeTypes.JS_OBJECT_EXPRESSION properties: Array } export interface Property extends Node { type: NodeTypes.JS_PROPERTY key: ExpressionNode value: JSChildNode } export interface ArrayExpression extends Node { type: NodeTypes.JS_ARRAY_EXPRESSION elements: Array } export interface FunctionExpression extends Node { type: NodeTypes.JS_FUNCTION_EXPRESSION params: ExpressionNode | ExpressionNode[] | undefined returns: TemplateChildNode | TemplateChildNode[] | JSChildNode newline: boolean } 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 interface CacheExpression extends Node { type: NodeTypes.JS_CACHE_EXPRESSION index: number value: JSChildNode isVNode: boolean } // Codegen Node Types ---------------------------------------------------------- // createVNode(...) export interface PlainElementCodegenNode extends CallExpression { callee: typeof CREATE_VNODE | typeof CREATE_BLOCK arguments: // tag, props, children, patchFlag, dynamicProps | [string | symbol] | [string | symbol, PropsExpression] | [string | symbol, 'null' | PropsExpression, TemplateChildNode[]] | [ string | symbol, 'null' | PropsExpression, 'null' | TemplateChildNode[], string ] | [ string | symbol, 'null' | PropsExpression, 'null' | TemplateChildNode[], string, string ] } export type ElementCodegenNode = | PlainElementCodegenNode | CodegenNodeWithDirective // createVNode(...) export interface PlainComponentCodegenNode extends CallExpression { callee: typeof CREATE_VNODE | typeof CREATE_BLOCK arguments: // Comp, props, slots, patchFlag, dynamicProps | [string | symbol] | [string | symbol, PropsExpression] | [string | symbol, 'null' | PropsExpression, SlotsExpression] | [ string | symbol, 'null' | PropsExpression, 'null' | SlotsExpression, string ] | [ string | symbol, 'null' | PropsExpression, 'null' | SlotsExpression, string, string ] } export type ComponentCodegenNode = | PlainComponentCodegenNode | CodegenNodeWithDirective export type SlotsExpression = SlotsObjectExpression | DynamicSlotsExpression // { foo: () => [...] } export interface SlotsObjectExpression extends ObjectExpression { properties: SlotsObjectProperty[] } export interface SlotsObjectProperty extends Property { value: SlotFunctionExpression } export interface SlotFunctionExpression extends FunctionExpression { returns: TemplateChildNode[] } // createSlots({ ... }, [ // foo ? () => [] : undefined, // renderList(list, i => () => [i]) // ]) export interface DynamicSlotsExpression extends CallExpression { callee: typeof CREATE_SLOTS arguments: [SlotsObjectExpression, DynamicSlotEntries] } export interface DynamicSlotEntries extends ArrayExpression { elements: (ConditionalDynamicSlotNode | ListDynamicSlotNode)[] } export interface ConditionalDynamicSlotNode extends ConditionalExpression { consequent: DynamicSlotNode alternate: DynamicSlotNode | SimpleExpressionNode } export interface ListDynamicSlotNode extends CallExpression { callee: typeof RENDER_LIST arguments: [ExpressionNode, ListDynamicSlotIterator] } export interface ListDynamicSlotIterator extends FunctionExpression { returns: DynamicSlotNode } export interface DynamicSlotNode extends ObjectExpression { properties: [Property, DynamicSlotFnProperty] } export interface DynamicSlotFnProperty extends Property { value: SlotFunctionExpression } // withDirectives(createVNode(...), [ // [_directive_foo, someValue], // [_directive_bar, someValue, "arg", { mod: true }] // ]) export interface CodegenNodeWithDirective extends CallExpression { callee: typeof WITH_DIRECTIVES arguments: [T, DirectiveArguments] } export interface DirectiveArguments extends ArrayExpression { elements: DirectiveArgumentNode[] } export interface DirectiveArgumentNode extends ArrayExpression { elements: // dir, exp, arg, modifiers | [string] | [string, ExpressionNode] | [string, ExpressionNode, ExpressionNode] | [string, ExpressionNode, ExpressionNode, ObjectExpression] } // renderSlot(...) export interface SlotOutletCodegenNode extends CallExpression { callee: typeof RENDER_SLOT arguments: // $slots, name, props, fallback | [string, string | ExpressionNode] | [string, string | ExpressionNode, PropsExpression] | [ string, string | ExpressionNode, PropsExpression | '{}', TemplateChildNode[] ] } export type BlockCodegenNode = | ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode export interface IfCodegenNode extends SequenceExpression { expressions: [OpenBlockExpression, IfConditionalExpression] } export interface IfConditionalExpression extends ConditionalExpression { consequent: BlockCodegenNode alternate: BlockCodegenNode | IfConditionalExpression } export interface ForCodegenNode extends SequenceExpression { expressions: [OpenBlockExpression, ForBlockCodegenNode] } export interface ForBlockCodegenNode extends CallExpression { callee: typeof CREATE_BLOCK arguments: [typeof FRAGMENT, 'null', ForRenderListExpression, string] } export interface ForRenderListExpression extends CallExpression { callee: typeof RENDER_LIST arguments: [ExpressionNode, ForIteratorExpression] } export interface ForIteratorExpression extends FunctionExpression { returns: BlockCodegenNode } export interface OpenBlockExpression extends CallExpression { callee: typeof OPEN_BLOCK arguments: [] } // AST Utilities --------------------------------------------------------------- // Some expressions, e.g. sequence and conditional expressions, are never // associated with template nodes, so their source locations are just a stub. // Container types like CompoundExpression also don't need a real location. export const locStub: SourceLocation = { source: '', start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } } export function createArrayExpression( elements: ArrayExpression['elements'], loc: SourceLocation = locStub ): ArrayExpression { return { type: NodeTypes.JS_ARRAY_EXPRESSION, loc, elements } } export function createObjectExpression( properties: ObjectExpression['properties'], loc: SourceLocation = locStub ): ObjectExpression { return { type: NodeTypes.JS_OBJECT_EXPRESSION, loc, properties } } export function createObjectProperty( key: Property['key'] | string, value: Property['value'] ): Property { return { type: NodeTypes.JS_PROPERTY, loc: locStub, key: isString(key) ? createSimpleExpression(key, true) : key, value } } export function createSimpleExpression( content: SimpleExpressionNode['content'], isStatic: SimpleExpressionNode['isStatic'], loc: SourceLocation = locStub, isConstant: boolean = false ): SimpleExpressionNode { return { type: NodeTypes.SIMPLE_EXPRESSION, loc, isConstant, content, isStatic } } export function createInterpolation( content: InterpolationNode['content'] | string, loc: SourceLocation ): InterpolationNode { return { type: NodeTypes.INTERPOLATION, loc, content: isString(content) ? createSimpleExpression(content, false, loc) : content } } export function createCompoundExpression( children: CompoundExpressionNode['children'], loc: SourceLocation = locStub ): CompoundExpressionNode { return { type: NodeTypes.COMPOUND_EXPRESSION, loc, children } } type InferCodegenNodeType = T extends | typeof CREATE_VNODE | typeof CREATE_BLOCK ? PlainElementCodegenNode | PlainComponentCodegenNode : T extends typeof WITH_DIRECTIVES ? | CodegenNodeWithDirective | CodegenNodeWithDirective : T extends typeof RENDER_SLOT ? SlotOutletCodegenNode : CallExpression export function createCallExpression( callee: T, args: CallExpression['arguments'] = [], loc: SourceLocation = locStub ): InferCodegenNodeType { return { type: NodeTypes.JS_CALL_EXPRESSION, loc, callee, arguments: args } as any } export function createFunctionExpression( params: FunctionExpression['params'], returns: FunctionExpression['returns'], newline: boolean = false, loc: SourceLocation = locStub ): FunctionExpression { return { type: NodeTypes.JS_FUNCTION_EXPRESSION, params, returns, newline, loc } } export function createSequenceExpression( expressions: SequenceExpression['expressions'] ): SequenceExpression { return { type: NodeTypes.JS_SEQUENCE_EXPRESSION, expressions, loc: locStub } } export function createConditionalExpression( test: ConditionalExpression['test'], consequent: ConditionalExpression['consequent'], alternate: ConditionalExpression['alternate'] ): ConditionalExpression { return { type: NodeTypes.JS_CONDITIONAL_EXPRESSION, test, consequent, alternate, loc: locStub } } export function createCacheExpression( index: number, value: JSChildNode, isVNode: boolean = false ): CacheExpression { return { type: NodeTypes.JS_CACHE_EXPRESSION, index, value, isVNode, loc: locStub } }