import type { CodegenOptions } from './options' import { type ArrayExpression, type AssignmentExpression, type CacheExpression, type CallExpression, type CommentNode, type CompoundExpressionNode, type ConditionalExpression, type ExpressionNode, type FunctionExpression, type IfStatement, type InterpolationNode, type JSChildNode, NodeTypes, type ObjectExpression, type Position, type ReturnStatement, type RootNode, type SSRCodegenNode, type SequenceExpression, type SimpleExpressionNode, type TemplateChildNode, type TemplateLiteral, type TextNode, type VNodeCall, getVNodeBlockHelper, getVNodeHelper, locStub, } from './ast' import { SourceMapGenerator } from 'source-map-js' import { advancePositionWithMutation, assert, isSimpleIdentifier, toValidAssetId, } from './utils' import { PatchFlagNames, type PatchFlags, isArray, isString, isSymbol, } from '@vue/shared' import { CREATE_COMMENT, CREATE_ELEMENT_VNODE, CREATE_STATIC, CREATE_TEXT, CREATE_VNODE, OPEN_BLOCK, RESOLVE_COMPONENT, RESOLVE_DIRECTIVE, RESOLVE_FILTER, SET_BLOCK_TRACKING, TO_DISPLAY_STRING, WITH_CTX, WITH_DIRECTIVES, helperNameMap, } from './runtimeHelpers' import type { ImportItem } from './transform' /** * The `SourceMapGenerator` type from `source-map-js` is a bit incomplete as it * misses `toJSON()`. We also need to add types for internal properties which we * need to access for better performance. * * Since TS 5.3, dts generation starts to strangely include broken triple slash * references for source-map-js, so we are inlining all source map related types * here to to workaround that. */ export interface CodegenSourceMapGenerator { setSourceContent(sourceFile: string, sourceContent: string): void // SourceMapGenerator has this method but the types do not include it toJSON(): RawSourceMap _sources: Set _names: Set _mappings: { add(mapping: MappingItem): void } } export interface RawSourceMap { file?: string sourceRoot?: string version: string sources: string[] names: string[] sourcesContent?: string[] mappings: string } interface MappingItem { source: string generatedLine: number generatedColumn: number originalLine: number originalColumn: number name: string | null } const PURE_ANNOTATION = `/*@__PURE__*/` const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}` type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode export interface CodegenResult { code: string preamble: string ast: RootNode map?: RawSourceMap } enum NewlineType { Start = 0, End = -1, None = -2, Unknown = -3, } export interface CodegenContext extends Omit, 'bindingMetadata' | 'inline'> { source: string code: string line: number column: number offset: number indentLevel: number pure: boolean map?: CodegenSourceMapGenerator helper(key: symbol): string push(code: string, newlineIndex?: number, node?: CodegenNode): void indent(): void deindent(withoutNewLine?: boolean): void newline(): void } function createCodegenContext( ast: RootNode, { mode = 'function', prefixIdentifiers = mode === 'module', sourceMap = false, filename = `template.vue.html`, scopeId = null, optimizeImports = false, runtimeGlobalName = `Vue`, runtimeModuleName = `vue`, ssrRuntimeModuleName = 'vue/server-renderer', ssr = false, isTS = false, inSSR = false, }: CodegenOptions, ): CodegenContext { const context: CodegenContext = { mode, prefixIdentifiers, sourceMap, filename, scopeId, optimizeImports, runtimeGlobalName, runtimeModuleName, ssrRuntimeModuleName, ssr, isTS, inSSR, source: ast.source, code: ``, column: 1, line: 1, offset: 0, indentLevel: 0, pure: false, map: undefined, helper(key) { return `_${helperNameMap[key]}` }, push(code, newlineIndex = NewlineType.None, node) { context.code += code if (!__BROWSER__ && context.map) { if (node) { let name if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) { const content = node.content.replace(/^_ctx\./, '') if (content !== node.content && isSimpleIdentifier(content)) { name = content } } addMapping(node.loc.start, name) } if (newlineIndex === NewlineType.Unknown) { // multiple newlines, full iteration advancePositionWithMutation(context, code) } else { // fast paths context.offset += code.length if (newlineIndex === NewlineType.None) { // no newlines; fast path to avoid newline detection if (__TEST__ && code.includes('\n')) { throw new Error( `CodegenContext.push() called newlineIndex: none, but contains` + `newlines: ${code.replace(/\n/g, '\\n')}`, ) } context.column += code.length } else { // single newline at known index if (newlineIndex === NewlineType.End) { newlineIndex = code.length - 1 } if ( __TEST__ && (code.charAt(newlineIndex) !== '\n' || code.slice(0, newlineIndex).includes('\n') || code.slice(newlineIndex + 1).includes('\n')) ) { throw new Error( `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` + `but does not conform: ${code.replace(/\n/g, '\\n')}`, ) } context.line++ context.column = code.length - newlineIndex } } if (node && node.loc !== locStub) { addMapping(node.loc.end) } } }, indent() { newline(++context.indentLevel) }, deindent(withoutNewLine = false) { if (withoutNewLine) { --context.indentLevel } else { newline(--context.indentLevel) } }, newline() { newline(context.indentLevel) }, } function newline(n: number) { context.push('\n' + ` `.repeat(n), NewlineType.Start) } function addMapping(loc: Position, name: string | null = null) { // we use the private property to directly add the mapping // because the addMapping() implementation in source-map-js has a bunch of // unnecessary arg and validation checks that are pure overhead in our case. const { _names, _mappings } = context.map! if (name !== null && !_names.has(name)) _names.add(name) _mappings.add({ originalLine: loc.line, originalColumn: loc.column - 1, // source-map column is 0 based generatedLine: context.line, generatedColumn: context.column - 1, source: filename, name, }) } if (!__BROWSER__ && sourceMap) { // lazy require source-map implementation, only in non-browser builds context.map = new SourceMapGenerator() as unknown as CodegenSourceMapGenerator context.map.setSourceContent(filename, context.source) context.map._sources.add(filename) } return context } export function generate( ast: RootNode, options: CodegenOptions & { onContextCreated?: (context: CodegenContext) => void } = {}, ): CodegenResult { const context = createCodegenContext(ast, options) if (options.onContextCreated) options.onContextCreated(context) const { mode, push, prefixIdentifiers, indent, deindent, newline, scopeId, ssr, } = context const helpers = Array.from(ast.helpers) const hasHelpers = helpers.length > 0 const useWithBlock = !prefixIdentifiers && mode !== 'module' const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module' const isSetupInlined = !__BROWSER__ && !!options.inline // preambles // in setup() inline mode, the preamble is generated in a sub context // and returned separately. const preambleContext = isSetupInlined ? createCodegenContext(ast, options) : context if (!__BROWSER__ && mode === 'module') { genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined) } else { genFunctionPreamble(ast, preambleContext) } // enter render function const functionName = ssr ? `ssrRender` : `render` const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache'] if (!__BROWSER__ && options.bindingMetadata && !options.inline) { // binding optimization args args.push('$props', '$setup', '$data', '$options') } const signature = !__BROWSER__ && options.isTS ? args.map(arg => `${arg}: any`).join(',') : args.join(', ') if (isSetupInlined) { push(`(${signature}) => {`) } else { push(`function ${functionName}(${signature}) {`) } indent() if (useWithBlock) { push(`with (_ctx) {`) indent() // function mode const declarations should be inside with block // also they should be renamed to avoid collision with user properties if (hasHelpers) { push( `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`, NewlineType.End, ) newline() } } // generate asset resolution statements if (ast.components.length) { genAssets(ast.components, 'component', context) if (ast.directives.length || ast.temps > 0) { newline() } } if (ast.directives.length) { genAssets(ast.directives, 'directive', context) if (ast.temps > 0) { newline() } } if (__COMPAT__ && ast.filters && ast.filters.length) { newline() genAssets(ast.filters, 'filter', context) newline() } if (ast.temps > 0) { push(`let `) for (let i = 0; i < ast.temps; i++) { push(`${i > 0 ? `, ` : ``}_temp${i}`) } } if (ast.components.length || ast.directives.length || ast.temps) { push(`\n`, NewlineType.Start) newline() } // generate the VNode tree expression if (!ssr) { push(`return `) } if (ast.codegenNode) { genNode(ast.codegenNode, context) } else { push(`null`) } if (useWithBlock) { deindent() push(`}`) } deindent() push(`}`) return { ast, code: context.code, preamble: isSetupInlined ? preambleContext.code : ``, map: context.map ? context.map.toJSON() : undefined, } } function genFunctionPreamble(ast: RootNode, context: CodegenContext) { const { ssr, prefixIdentifiers, push, newline, runtimeModuleName, runtimeGlobalName, ssrRuntimeModuleName, } = context const VueBinding = !__BROWSER__ && ssr ? `require(${JSON.stringify(runtimeModuleName)})` : runtimeGlobalName // Generate const declaration for helpers // In prefix mode, we place the const declaration at top so it's done // only once; But if we not prefixing, we place the declaration inside the // with block so it doesn't incur the `in` check cost for every helper access. const helpers = Array.from(ast.helpers) if (helpers.length > 0) { if (!__BROWSER__ && prefixIdentifiers) { push( `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`, NewlineType.End, ) } else { // "with" mode. // save Vue in a separate variable to avoid collision push(`const _Vue = ${VueBinding}\n`, NewlineType.End) // in "with" mode, helpers are declared inside the with block to avoid // has check cost, but hoists are lifted out of the function - we need // to provide the helper here. if (ast.hoists.length) { const staticHelpers = [ CREATE_VNODE, CREATE_ELEMENT_VNODE, CREATE_COMMENT, CREATE_TEXT, CREATE_STATIC, ] .filter(helper => helpers.includes(helper)) .map(aliasHelper) .join(', ') push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End) } } } // generate variables for ssr helpers if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) { // ssr guarantees prefixIdentifier: true push( `const { ${ast.ssrHelpers .map(aliasHelper) .join(', ')} } = require("${ssrRuntimeModuleName}")\n`, NewlineType.End, ) } genHoists(ast.hoists, context) newline() push(`return `) } function genModulePreamble( ast: RootNode, context: CodegenContext, genScopeId: boolean, inline?: boolean, ) { const { push, newline, optimizeImports, runtimeModuleName, ssrRuntimeModuleName, } = context // generate import statements for helpers if (ast.helpers.size) { const helpers = Array.from(ast.helpers) if (optimizeImports) { // when bundled with webpack with code-split, calling an import binding // as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`, // incurring both payload size increase and potential perf overhead. // therefore we assign the imports to variables (which is a constant ~50b // cost per-component instead of scaling with template size) push( `import { ${helpers .map(s => helperNameMap[s]) .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`, NewlineType.End, ) push( `\n// Binding optimization for webpack code-split\nconst ${helpers .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`) .join(', ')}\n`, NewlineType.End, ) } else { push( `import { ${helpers .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`) .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`, NewlineType.End, ) } } if (ast.ssrHelpers && ast.ssrHelpers.length) { push( `import { ${ast.ssrHelpers .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`) .join(', ')} } from "${ssrRuntimeModuleName}"\n`, NewlineType.End, ) } if (ast.imports.length) { genImports(ast.imports, context) newline() } genHoists(ast.hoists, context) newline() if (!inline) { push(`export `) } } function genAssets( assets: string[], type: 'component' | 'directive' | 'filter', { helper, push, newline, isTS }: CodegenContext, ) { const resolver = helper( __COMPAT__ && type === 'filter' ? RESOLVE_FILTER : type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE, ) for (let i = 0; i < assets.length; i++) { let id = assets[i] // potential component implicit self-reference inferred from SFC filename const maybeSelfReference = id.endsWith('__self') if (maybeSelfReference) { id = id.slice(0, -6) } push( `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${ maybeSelfReference ? `, true` : `` })${isTS ? `!` : ``}`, ) if (i < assets.length - 1) { newline() } } } function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) { if (!hoists.length) { return } context.pure = true const { push, newline } = context newline() for (let i = 0; i < hoists.length; i++) { const exp = hoists[i] if (exp) { push(`const _hoisted_${i + 1} = `) genNode(exp, context) newline() } } context.pure = false } function genImports(importsOptions: ImportItem[], context: CodegenContext) { if (!importsOptions.length) { return } importsOptions.forEach(imports => { context.push(`import `) genNode(imports.exp, context) context.push(` from '${imports.path}'`) context.newline() }) } function isText(n: string | CodegenNode) { return ( isString(n) || n.type === NodeTypes.SIMPLE_EXPRESSION || n.type === NodeTypes.TEXT || n.type === NodeTypes.INTERPOLATION || n.type === NodeTypes.COMPOUND_EXPRESSION ) } function genNodeListAsArray( nodes: (string | CodegenNode | TemplateChildNode[])[], context: CodegenContext, ) { const multilines = nodes.length > 3 || ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n))) context.push(`[`) multilines && context.indent() genNodeList(nodes, context, multilines) multilines && context.deindent() context.push(`]`) } function genNodeList( nodes: (string | symbol | CodegenNode | TemplateChildNode[])[], context: CodegenContext, multilines: boolean = false, comma: boolean = true, ) { const { push, newline } = context for (let i = 0; i < nodes.length; i++) { const node = nodes[i] if (isString(node)) { push(node, NewlineType.Unknown) } else if (isArray(node)) { genNodeListAsArray(node, context) } else { genNode(node, context) } if (i < nodes.length - 1) { if (multilines) { comma && push(',') newline() } else { comma && push(', ') } } } } function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { if (isString(node)) { context.push(node, NewlineType.Unknown) return } if (isSymbol(node)) { context.push(context.helper(node)) return } switch (node.type) { case NodeTypes.ELEMENT: case NodeTypes.IF: case NodeTypes.FOR: __DEV__ && assert( node.codegenNode != null, `Codegen node is missing for element/if/for node. ` + `Apply appropriate transforms first.`, ) genNode(node.codegenNode!, context) break case NodeTypes.TEXT: genText(node, context) break case NodeTypes.SIMPLE_EXPRESSION: genExpression(node, context) break case NodeTypes.INTERPOLATION: genInterpolation(node, context) break case NodeTypes.TEXT_CALL: genNode(node.codegenNode, context) break case NodeTypes.COMPOUND_EXPRESSION: genCompoundExpression(node, context) break case NodeTypes.COMMENT: genComment(node, context) break case NodeTypes.VNODE_CALL: genVNodeCall(node, context) break case NodeTypes.JS_CALL_EXPRESSION: genCallExpression(node, context) break case NodeTypes.JS_OBJECT_EXPRESSION: genObjectExpression(node, context) break case NodeTypes.JS_ARRAY_EXPRESSION: genArrayExpression(node, context) break case NodeTypes.JS_FUNCTION_EXPRESSION: genFunctionExpression(node, context) break case NodeTypes.JS_CONDITIONAL_EXPRESSION: genConditionalExpression(node, context) break case NodeTypes.JS_CACHE_EXPRESSION: genCacheExpression(node, context) break case NodeTypes.JS_BLOCK_STATEMENT: genNodeList(node.body, context, true, false) break // SSR only types case NodeTypes.JS_TEMPLATE_LITERAL: !__BROWSER__ && genTemplateLiteral(node, context) break case NodeTypes.JS_IF_STATEMENT: !__BROWSER__ && genIfStatement(node, context) break case NodeTypes.JS_ASSIGNMENT_EXPRESSION: !__BROWSER__ && genAssignmentExpression(node, context) break case NodeTypes.JS_SEQUENCE_EXPRESSION: !__BROWSER__ && genSequenceExpression(node, context) break case NodeTypes.JS_RETURN_STATEMENT: !__BROWSER__ && genReturnStatement(node, context) break /* v8 ignore start */ case NodeTypes.IF_BRANCH: // noop break default: if (__DEV__) { assert(false, `unhandled codegen node type: ${(node as any).type}`) // make sure we exhaust all possible types const exhaustiveCheck: never = node return exhaustiveCheck } /* v8 ignore stop */ } } function genText( node: TextNode | SimpleExpressionNode, context: CodegenContext, ) { context.push(JSON.stringify(node.content), NewlineType.Unknown, node) } function genExpression(node: SimpleExpressionNode, context: CodegenContext) { const { content, isStatic } = node context.push( isStatic ? JSON.stringify(content) : content, NewlineType.Unknown, node, ) } function genInterpolation(node: InterpolationNode, context: CodegenContext) { const { push, helper, pure } = context if (pure) push(PURE_ANNOTATION) push(`${helper(TO_DISPLAY_STRING)}(`) genNode(node.content, context) push(`)`) } function genCompoundExpression( node: CompoundExpressionNode, context: CodegenContext, ) { for (let i = 0; i < node.children!.length; i++) { const child = node.children![i] if (isString(child)) { context.push(child, NewlineType.Unknown) } else { genNode(child, context) } } } function genExpressionAsPropertyKey( node: ExpressionNode, context: CodegenContext, ) { const { push } = context if (node.type === NodeTypes.COMPOUND_EXPRESSION) { push(`[`) genCompoundExpression(node, context) push(`]`) } else if (node.isStatic) { // only quote keys if necessary const text = isSimpleIdentifier(node.content) ? node.content : JSON.stringify(node.content) push(text, NewlineType.None, node) } else { push(`[${node.content}]`, NewlineType.Unknown, node) } } function genComment(node: CommentNode, context: CodegenContext) { const { push, helper, pure } = context if (pure) { push(PURE_ANNOTATION) } push( `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, NewlineType.Unknown, node, ) } function genVNodeCall(node: VNodeCall, context: CodegenContext) { const { push, helper, pure } = context const { tag, props, children, patchFlag, dynamicProps, directives, isBlock, disableTracking, isComponent, } = node // add dev annotations to patch flags let patchFlagString if (patchFlag) { if (__DEV__) { if (patchFlag < 0) { // special flags (negative and mutually exclusive) patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */` } else { // bitwise flags const flagNames = Object.keys(PatchFlagNames) .map(Number) .filter(n => n > 0 && patchFlag & n) .map(n => PatchFlagNames[n as PatchFlags]) .join(`, `) patchFlagString = patchFlag + ` /* ${flagNames} */` } } else { patchFlagString = String(patchFlag) } } if (directives) { push(helper(WITH_DIRECTIVES) + `(`) } if (isBlock) { push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `) } if (pure) { push(PURE_ANNOTATION) } const callHelper: symbol = isBlock ? getVNodeBlockHelper(context.inSSR, isComponent) : getVNodeHelper(context.inSSR, isComponent) push(helper(callHelper) + `(`, NewlineType.None, node) genNodeList( genNullableArgs([tag, props, children, patchFlagString, dynamicProps]), context, ) push(`)`) if (isBlock) { push(`)`) } if (directives) { push(`, `) genNode(directives, context) push(`)`) } } function genNullableArgs(args: any[]): CallExpression['arguments'] { let i = args.length while (i--) { if (args[i] != null) break } return args.slice(0, i + 1).map(arg => arg || `null`) } // JavaScript function genCallExpression(node: CallExpression, context: CodegenContext) { const { push, helper, pure } = context const callee = isString(node.callee) ? node.callee : helper(node.callee) if (pure) { push(PURE_ANNOTATION) } push(callee + `(`, NewlineType.None, node) genNodeList(node.arguments, context) push(`)`) } function genObjectExpression(node: ObjectExpression, context: CodegenContext) { const { push, indent, deindent, newline } = context const { properties } = node if (!properties.length) { push(`{}`, NewlineType.None, node) return } const multilines = properties.length > 1 || ((!__BROWSER__ || __DEV__) && properties.some(p => p.value.type !== NodeTypes.SIMPLE_EXPRESSION)) push(multilines ? `{` : `{ `) multilines && indent() for (let i = 0; i < properties.length; i++) { const { key, value } = properties[i] // key genExpressionAsPropertyKey(key, context) push(`: `) // value genNode(value, context) if (i < properties.length - 1) { // will only reach this if it's multilines push(`,`) newline() } } multilines && deindent() push(multilines ? `}` : ` }`) } function genArrayExpression(node: ArrayExpression, context: CodegenContext) { genNodeListAsArray(node.elements as CodegenNode[], context) } function genFunctionExpression( node: FunctionExpression, context: CodegenContext, ) { const { push, indent, deindent } = context const { params, returns, body, newline, isSlot } = node if (isSlot) { // wrap slot functions with owner context push(`_${helperNameMap[WITH_CTX]}(`) } push(`(`, NewlineType.None, node) if (isArray(params)) { genNodeList(params, context) } else if (params) { genNode(params, context) } push(`) => `) if (newline || body) { push(`{`) indent() } if (returns) { if (newline) { push(`return `) } if (isArray(returns)) { genNodeListAsArray(returns, context) } else { genNode(returns, context) } } else if (body) { genNode(body, context) } if (newline || body) { deindent() push(`}`) } if (isSlot) { if (__COMPAT__ && node.isNonScopedSlot) { push(`, undefined, true`) } push(`)`) } } function genConditionalExpression( node: ConditionalExpression, context: CodegenContext, ) { const { test, consequent, alternate, newline: needNewline } = node const { push, indent, deindent, newline } = context if (test.type === NodeTypes.SIMPLE_EXPRESSION) { const needsParens = !isSimpleIdentifier(test.content) needsParens && push(`(`) genExpression(test, context) needsParens && push(`)`) } else { push(`(`) genNode(test, context) push(`)`) } needNewline && indent() context.indentLevel++ needNewline || push(` `) push(`? `) genNode(consequent, context) context.indentLevel-- needNewline && newline() needNewline || push(` `) push(`: `) const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION if (!isNested) { context.indentLevel++ } genNode(alternate, context) if (!isNested) { context.indentLevel-- } needNewline && deindent(true /* without newline */) } function genCacheExpression(node: CacheExpression, context: CodegenContext) { const { push, helper, indent, deindent, newline } = context const { needPauseTracking, needArraySpread } = node if (needArraySpread) { push(`[...(`) } push(`_cache[${node.index}] || (`) if (needPauseTracking) { indent() push(`${helper(SET_BLOCK_TRACKING)}(-1),`) newline() push(`(`) } push(`_cache[${node.index}] = `) genNode(node.value, context) if (needPauseTracking) { push(`).cacheIndex = ${node.index},`) newline() push(`${helper(SET_BLOCK_TRACKING)}(1),`) newline() push(`_cache[${node.index}]`) deindent() } push(`)`) if (needArraySpread) { push(`)]`) } } function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) { const { push, indent, deindent } = context push('`') const l = node.elements.length const multilines = l > 3 for (let i = 0; i < l; i++) { const e = node.elements[i] if (isString(e)) { push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown) } else { push('${') if (multilines) indent() genNode(e, context) if (multilines) deindent() push('}') } } push('`') } function genIfStatement(node: IfStatement, context: CodegenContext) { const { push, indent, deindent } = context const { test, consequent, alternate } = node push(`if (`) genNode(test, context) push(`) {`) indent() genNode(consequent, context) deindent() push(`}`) if (alternate) { push(` else `) if (alternate.type === NodeTypes.JS_IF_STATEMENT) { genIfStatement(alternate, context) } else { push(`{`) indent() genNode(alternate, context) deindent() push(`}`) } } } function genAssignmentExpression( node: AssignmentExpression, context: CodegenContext, ) { genNode(node.left, context) context.push(` = `) genNode(node.right, context) } function genSequenceExpression( node: SequenceExpression, context: CodegenContext, ) { context.push(`(`) genNodeList(node.expressions, context) context.push(`)`) } function genReturnStatement( { returns }: ReturnStatement, context: CodegenContext, ) { context.push(`return `) if (isArray(returns)) { genNodeListAsArray(returns, context) } else { genNode(returns, context) } }