// noinspection DuplicatedCode /* * Copyright 2025 the original author or authors. *

* Licensed under the Moderne Source Available License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://docs.moderne.io/licensing/moderne-source-available-license *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {JS, JSX} from "./tree"; import {JavaScriptVisitor} from "./visitor"; import {PrintOutputCapture, TreePrinters} from "../print"; import {Cursor, isTree, Tree} from "../tree"; import {Comment, emptySpace, J, Statement, TextComment, TrailingComma, TypedTree} from "../java"; import {findMarker, Marker, Markers} from "../markers"; import {DelegatedYield, FunctionDeclaration, Generator, NonNullAssertion, Optional} from "./markers"; export class JavaScriptPrinter extends JavaScriptVisitor { JAVA_SCRIPT_MARKER_WRAPPER: (out: string) => string = (out) => `/*~~${out}${out.length === 0 ? "" : "~~"}>*/`; override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: PrintOutputCapture): Promise { await this.beforeSyntax(cu, p); await this.visitRightPaddedLocal(cu.statements, "", p); await this.visitSpace(cu.eof, p); await this.afterSyntax(cu, p); return cu; } override async visitAlias(alias: JS.Alias, p: PrintOutputCapture): Promise { await this.beforeSyntax(alias, p); await this.visitRightPadded(alias.propertyName, p); p.append("as"); await this.visit(alias.alias, p); await this.afterSyntax(alias, p); return alias; } override async visitAwait(awaitExpr: JS.Await, p: PrintOutputCapture): Promise { await this.beforeSyntax(awaitExpr, p); p.append("await"); await this.visit(awaitExpr.expression, p); await this.afterSyntax(awaitExpr, p); return awaitExpr; } override async visitBindingElement(binding: JS.BindingElement, p: PrintOutputCapture): Promise { await this.beforeSyntax(binding, p); if (binding.propertyName) { await this.visitRightPadded(binding.propertyName, p); p.append(":"); } await this.visit(binding.name, p); binding.initializer && await this.visitLeftPaddedLocal("=", binding.initializer, p); await this.afterSyntax(binding, p); return binding; } override async visitDelete(del: JS.Delete, p: PrintOutputCapture): Promise { await this.beforeSyntax(del, p); p.append("delete"); await this.visit(del.expression, p); await this.afterSyntax(del, p); return del; } override async visitExpressionStatement(statement: JS.ExpressionStatement, p: PrintOutputCapture): Promise { await this.beforeSyntax(statement, p); await this.visit(statement.expression, p); await this.afterSyntax(statement, p); return statement; } override async visitStatementExpression(statementExpression: JS.StatementExpression, p: PrintOutputCapture): Promise { await this.beforeSyntax(statementExpression, p); await this.visit(statementExpression.statement, p); await this.afterSyntax(statementExpression, p); return statementExpression; } override async visitSpread(spread: JS.Spread, p: PrintOutputCapture): Promise { await this.beforeSyntax(spread, p); p.append("..."); await this.visit(spread.expression, p); await this.afterSyntax(spread, p); return spread; } override async visitInferType(inferType: JS.InferType, p: PrintOutputCapture): Promise { await this.beforeSyntax(inferType, p); await this.visitLeftPaddedLocal("infer", inferType.typeParameter, p); await this.afterSyntax(inferType, p); return inferType; } override async visitJsxTag(element: JSX.Tag, p: PrintOutputCapture): Promise { await this.beforeSyntax(element, p); // Print < first, then the space after < (openName.before), then the tag name p.append("<"); await this.visitSpace(element.openName.before, p); await this.visit(element.openName.element, p); if (element.typeArguments) { await this.visitContainerLocal("<", element.typeArguments, ",", ">", p); } await this.visitSpace(element.afterName, p); await this.visitRightPaddedLocal(element.attributes, "", p); if (element.selfClosing) { await this.visitSpace(element.selfClosing, p); p.append("/>"); } else { p.append(">"); if (element.children) { for (let i = 0; i < element.children.length; i++) { await this.visit(element.children[i], p) } // Print "); } } await this.afterSyntax(element, p); return element; } override async visitJsxAttribute(attribute: JSX.Attribute, p: PrintOutputCapture): Promise { await this.beforeSyntax(attribute, p); await this.visit(attribute.key, p); if (attribute.value) { p.append("="); await this.visit(attribute.value.element, p); } await this.afterSyntax(attribute, p); return attribute; } override async visitJsxSpreadAttribute(spread: JSX.SpreadAttribute, p: PrintOutputCapture): Promise { await this.beforeSyntax(spread, p); p.append("{"); await this.visitSpace(spread.dots, p); p.append("..."); await this.visitRightPaddedLocal([spread.expression], "}", p); p.append("}"); await this.afterSyntax(spread, p); return spread; } override async visitJsxEmbeddedExpression(expr: JSX.EmbeddedExpression, p: PrintOutputCapture): Promise { await this.beforeSyntax(expr, p); p.append("{"); if (expr.expression) { await this.visitRightPaddedLocal([expr.expression], "}", p); } p.append("}"); await this.afterSyntax(expr, p); return expr; } override async visitJsxNamespacedName(ns: JSX.NamespacedName, p: PrintOutputCapture): Promise { await this.beforeSyntax(ns, p); await this.visit(ns.namespace, p); p.append(":"); await this.visitLeftPadded(ns.name, p); await this.afterSyntax(ns, p); return ns; } override async visitImportDeclaration(jsImport: JS.Import, p: PrintOutputCapture): Promise { for (const it of jsImport.modifiers) { await this.visitDefined(it, p); } await this.beforeSyntax(jsImport, p); p.append("import"); jsImport.importClause && await this.visit(jsImport.importClause, p); await this.visitLeftPaddedLocal(jsImport.importClause ? "from" : "", jsImport.moduleSpecifier, p); jsImport.attributes && await this.visit(jsImport.attributes, p); if (jsImport.initializer) { p.append("="); await this.visitLeftPadded(jsImport.initializer, p); } await this.afterSyntax(jsImport, p); return jsImport; } override async visitImportClause(jsImportClause: JS.ImportClause, p: PrintOutputCapture): Promise { await this.beforeSyntax(jsImportClause, p); if (jsImportClause.typeOnly) { p.append("type"); } if (jsImportClause.name) { await this.visitRightPadded(jsImportClause.name, p); if (jsImportClause.namedBindings) { p.append(","); } } jsImportClause.namedBindings && await this.visit(jsImportClause.namedBindings, p); await this.afterSyntax(jsImportClause, p); return jsImportClause; } override async visitTypeTreeExpression(typeTreeExpression: JS.TypeTreeExpression, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeTreeExpression, p); await this.visit(typeTreeExpression.expression, p); await this.afterSyntax(typeTreeExpression, p); return typeTreeExpression; } override async visitNamespaceDeclaration(namespaceDeclaration: JS.NamespaceDeclaration, p: PrintOutputCapture): Promise { await this.beforeSyntax(namespaceDeclaration, p); for (const it of namespaceDeclaration.modifiers) { await this.visitModifier(it, p); } await this.visitSpace(namespaceDeclaration.keywordType.before, p); switch (namespaceDeclaration.keywordType.element) { case JS.NamespaceDeclaration.KeywordType.Namespace: p.append("namespace"); break; case JS.NamespaceDeclaration.KeywordType.Module: p.append("module"); break; default: break; } await this.visitRightPadded(namespaceDeclaration.name, p); if (namespaceDeclaration.body) { await this.visit(namespaceDeclaration.body, p); } await this.afterSyntax(namespaceDeclaration, p); return namespaceDeclaration; } override async visitSatisfiesExpression(satisfiesExpression: JS.SatisfiesExpression, p: PrintOutputCapture): Promise { await this.beforeSyntax(satisfiesExpression, p); await this.visit(satisfiesExpression.expression, p); await this.visitLeftPaddedLocal("satisfies", satisfiesExpression.satisfiesType, p); await this.afterSyntax(satisfiesExpression, p); return satisfiesExpression; } override async visitVoid(aVoid: JS.Void, p: PrintOutputCapture): Promise { await this.beforeSyntax(aVoid, p); p.append("void"); await this.visit(aVoid.expression, p); await this.afterSyntax(aVoid, p); return aVoid; } override async visitYield(aYield: J.Yield, p: PrintOutputCapture): Promise { await this.beforeSyntax(aYield, p); p.append("yield"); const delegated = findMarker(aYield, JS.Markers.DelegatedYield); if (delegated) { await this.visitSpace(delegated.prefix, p); p.append("*"); } aYield.value && await this.visit(aYield.value, p); await this.afterSyntax(aYield, p); return aYield; } override async visitTry(aTry: J.Try, p: PrintOutputCapture): Promise { await this.beforeSyntax(aTry, p); p.append("try"); await this.visit(aTry.body, p); await this.visitNodes(aTry.catches, p); aTry.finally && await this.visitLeftPaddedLocal("finally", aTry.finally, p); await this.afterSyntax(aTry, p); return aTry; } override async visitTryCatch(aCatch: J.Try.Catch, p: PrintOutputCapture): Promise { await this.beforeSyntax(aCatch, p); p.append("catch"); if (aCatch.parameter.tree.element.variables.length > 0) { await this.visit(aCatch.parameter, p); } await this.visit(aCatch.body, p); await this.afterSyntax(aCatch, p); return aCatch; } override async visitArrayDimension(arrayDimension: J.ArrayDimension, p: PrintOutputCapture): Promise { await this.beforeSyntax(arrayDimension, p); p.append("["); await this.visitRightPaddedLocalSingle(arrayDimension.index, "]", p); await this.afterSyntax(arrayDimension, p); return arrayDimension; } override async visitArrayType(arrayType: J.ArrayType, p: PrintOutputCapture): Promise { await this.beforeSyntax(arrayType, p); let type: TypedTree = arrayType; while (type.kind === J.Kind.ArrayType) { type = (type as J.ArrayType).elementType; } await this.visit(type, p); await this.visitNodes(arrayType.annotations, p); if (arrayType.dimension) { await this.visitSpace(arrayType.dimension.before, p); p.append("["); await this.visitSpace(arrayType.dimension.element, p); p.append("]"); if (arrayType.elementType.kind === J.Kind.ArrayType) { await this.printDimensions(arrayType.elementType as J.ArrayType, p); } } await this.afterSyntax(arrayType, p); return arrayType; } private async printDimensions(arrayType: J.ArrayType, p: PrintOutputCapture) { await this.beforeSyntax(arrayType, p); await this.visitNodes(arrayType.annotations, p); await this.visitSpace(arrayType.dimension?.before ?? emptySpace, p); p.append("["); await this.visitSpace(arrayType.dimension?.element ?? emptySpace, p); p.append("]"); if (arrayType.elementType.kind === J.Kind.ArrayType) { await this.printDimensions(arrayType.elementType as J.ArrayType, p); } await this.afterSyntax(arrayType, p); } override async visitTernary(ternary: J.Ternary, p: PrintOutputCapture): Promise { await this.beforeSyntax(ternary, p); await this.visit(ternary.condition, p); await this.visitLeftPaddedLocal("?", ternary.truePart, p); await this.visitLeftPaddedLocal(":", ternary.falsePart, p); await this.afterSyntax(ternary, p); return ternary; } override async visitThrow(thrown: J.Throw, p: PrintOutputCapture): Promise { await this.beforeSyntax(thrown, p); p.append("throw"); await this.visit(thrown.exception, p); await this.afterSyntax(thrown, p); return thrown; } override async visitIf(iff: J.If, p: PrintOutputCapture): Promise { await this.beforeSyntax(iff, p); p.append("if"); await this.visit(iff.ifCondition, p); await this.visitStatementLocal(iff.thenPart, p); iff.elsePart && await this.visit(iff.elsePart, p); await this.afterSyntax(iff, p); return iff; } override async visitElse(else_: J.If.Else, p: PrintOutputCapture): Promise { await this.beforeSyntax(else_, p); p.append("else"); await this.visitStatementLocal(else_.body, p); await this.afterSyntax(else_, p); return else_; } override async visitDoWhileLoop(doWhileLoop: J.DoWhileLoop, p: PrintOutputCapture): Promise { await this.beforeSyntax(doWhileLoop, p); p.append("do"); await this.visitStatementLocal(doWhileLoop.body, p); await this.visitLeftPaddedLocal("while", doWhileLoop.whileCondition, p); await this.afterSyntax(doWhileLoop, p); return doWhileLoop; } override async visitWhileLoop(whileLoop: J.WhileLoop, p: PrintOutputCapture): Promise { await this.beforeSyntax(whileLoop, p); p.append("while"); await this.visit(whileLoop.condition, p); await this.visitStatementLocal(whileLoop.body, p); await this.afterSyntax(whileLoop, p); return whileLoop; } override async visitInstanceOf(instanceOf: J.InstanceOf, p: PrintOutputCapture): Promise { await this.beforeSyntax(instanceOf, p); await this.visitRightPaddedLocalSingle(instanceOf.expression, "instanceof", p); await this.visit(instanceOf.class, p); instanceOf.pattern && await this.visit(instanceOf.pattern, p); await this.afterSyntax(instanceOf, p); return instanceOf; } override async visitLiteral(literal: J.Literal, p: PrintOutputCapture): Promise { await this.beforeSyntax(literal, p); const unicodeEscapes = literal.unicodeEscapes; if (!unicodeEscapes) { p.append(literal.valueSource!); } else if (literal.valueSource) { const surrogateIter = unicodeEscapes[Symbol.iterator](); let surrogate = surrogateIter.next().value ?? null; let i = 0; if (surrogate && surrogate.valueSourceIndex === 0) { p.append("\\u").append(surrogate.codePoint); surrogate = surrogateIter.next().value ?? null; } const valueSource = literal.valueSource; for (let j = 0; j < valueSource.length; j++) { const c = valueSource[j]; p.append(c); if (surrogate && surrogate.valueSourceIndex === ++i) { while (surrogate && surrogate.valueSourceIndex === i) { p.append("\\u").append(surrogate.codePoint); surrogate = surrogateIter.next().value ?? null; } } } } await this.afterSyntax(literal, p); return literal; } override async visitScopedVariableDeclarations(variableDeclarations: JS.ScopedVariableDeclarations, p: PrintOutputCapture): Promise { await this.beforeSyntax(variableDeclarations, p); for (const m of variableDeclarations.modifiers) { await this.visitModifier(m, p); } await this.visitRightPaddedLocal(variableDeclarations.variables, ",", p); await this.afterSyntax(variableDeclarations, p); return variableDeclarations; } override async visitShebang(shebang: JS.Shebang, p: PrintOutputCapture): Promise { await this.beforeSyntax(shebang, p); p.append(shebang.text); await this.afterSyntax(shebang, p); return shebang; } override async visitVariableDeclarations(multiVariable: J.VariableDeclarations, p: PrintOutputCapture): Promise { await this.beforeSyntax(multiVariable, p); await this.visitNodes(multiVariable.leadingAnnotations, p); for (const it of multiVariable.modifiers) { await this.visitModifier(it, p); } const variables = multiVariable.variables; for (let i = 0; i < variables.length; i++) { const variable = variables[i]; await this.beforeSyntax(variable.element, p); if (multiVariable.varargs) { p.append("..."); } await this.visit(variable.element.name, p); // print non-null assertions or optional await this.postVisit(variable.element, p); await this.visitSpace(variable.after, p); if (multiVariable.typeExpression) { await this.visit(multiVariable.typeExpression, p); } if (variable.element.initializer) { await this.visitLeftPaddedLocal("=", variable.element.initializer, p); } await this.afterSyntax(variable.element, p); if (i < variables.length - 1) { p.append(","); } else if (findMarker(variable, J.Markers.Semicolon)) { p.append(";"); } } await this.afterSyntax(multiVariable, p); return multiVariable; } override async visitVariable(variable: J.VariableDeclarations.NamedVariable, p: PrintOutputCapture): Promise { await this.beforeSyntax(variable, p); await this.visit(variable.name, p); variable.initializer && await this.visitLeftPaddedLocal("=", variable.initializer, p); await this.afterSyntax(variable, p); return variable; } override async visitIdentifier(ident: J.Identifier, p: PrintOutputCapture): Promise { await this.visitSpace(emptySpace, p); await this.visitNodes(ident.annotations, p); await this.beforeSyntax(ident, p); p.append(ident.simpleName); await this.afterSyntax(ident, p); return ident; } override async visitBlock(block: J.Block, p: PrintOutputCapture): Promise { await this.beforeSyntax(block, p); if (block.static.element) { p.append("static"); await this.visitRightPadded(block.static, p); } p.append("{"); await this.visitStatements(block.statements, p); await this.visitSpace(block.end, p); p.append("}"); await this.afterSyntax(block, p); return block; } override async visitTypeInfo(typeInfo: JS.TypeInfo, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeInfo, p); p.append(":"); await this.visit(typeInfo.typeIdentifier, p); await this.afterSyntax(typeInfo, p); return typeInfo; } override async visitReturn(return_: J.Return, p: PrintOutputCapture): Promise { await this.beforeSyntax(return_, p); p.append("return"); return_.expression && await this.visit(return_.expression, p); await this.afterSyntax(return_, p); return return_; } override async visitModifier(mod: J.Modifier, p: PrintOutputCapture): Promise { await this.visitNodes(mod.annotations, p); let keyword: string; switch (mod.type) { case J.ModifierType.Default: keyword = "default"; break; case J.ModifierType.Public: keyword = "public"; break; case J.ModifierType.Protected: keyword = "protected"; break; case J.ModifierType.Private: keyword = "private"; break; case J.ModifierType.Abstract: keyword = "abstract"; break; case J.ModifierType.Async: keyword = "async"; break; case J.ModifierType.Static: keyword = "static"; break; case J.ModifierType.Final: keyword = "const"; break; case J.ModifierType.Native: keyword = "native"; break; case J.ModifierType.NonSealed: keyword = "non-sealed"; break; case J.ModifierType.Sealed: keyword = "sealed"; break; case J.ModifierType.Strictfp: keyword = "strictfp"; break; case J.ModifierType.Synchronized: keyword = "synchronized"; break; case J.ModifierType.Transient: keyword = "transient"; break; case J.ModifierType.Volatile: keyword = "volatile"; break; default: keyword = mod.keyword ?? ""; } await this.beforeSyntax(mod, p); p.append(keyword); await this.afterSyntax(mod, p); return mod; } override async visitFunctionCall(functionCall: JS.FunctionCall, p: PrintOutputCapture): Promise { await this.beforeSyntax(functionCall, p); if (functionCall.function) { await this.visitRightPadded(functionCall.function, p); if (functionCall.function.element.markers.markers.find(m => m.kind === JS.Markers.Optional)) { p.append("?."); } } functionCall.typeParameters && await this.visitContainerLocal("<", functionCall.typeParameters, ",", ">", p); await this.visitContainerLocal("(", functionCall.arguments, ",", ")", p); await this.afterSyntax(functionCall, p); return functionCall; } override async visitFunctionType(functionType: JS.FunctionType, p: PrintOutputCapture): Promise { await this.beforeSyntax(functionType, p); for (const m of functionType.modifiers) { await this.visitModifier(m, p); } if (functionType.constructorType.element) { await this.visitLeftPaddedLocal("new", functionType.constructorType, p); } const typeParameters = functionType.typeParameters; if (typeParameters) { await this.visitNodes(typeParameters.annotations, p); await this.visitSpace(typeParameters.prefix, p); await this.visitMarkers(typeParameters.markers, p); p.append("<"); await this.visitRightPaddedLocal(typeParameters.typeParameters, ",", p); p.append(">"); } await this.visitContainerLocal("(", functionType.parameters, ",", ")", p); await this.visitLeftPaddedLocal("=>", functionType.returnType, p); await this.afterSyntax(functionType, p); return functionType; } override async visitClassDeclaration(classDecl: J.ClassDeclaration, p: PrintOutputCapture): Promise { let kind = ""; switch (classDecl.classKind.type) { case J.ClassDeclaration.Kind.Type.Class: kind = "class"; break; case J.ClassDeclaration.Kind.Type.Enum: kind = "enum"; break; case J.ClassDeclaration.Kind.Type.Interface: kind = "interface"; break; case J.ClassDeclaration.Kind.Type.Annotation: kind = "@interface"; break; case J.ClassDeclaration.Kind.Type.Record: kind = "record"; break; } await this.beforeSyntax(classDecl, p); await this.visitSpace(emptySpace, p); await this.visitNodes(classDecl.leadingAnnotations, p); for (const m of classDecl.modifiers) { await this.visitModifier(m, p); } await this.visitNodes(classDecl.classKind.annotations, p); await this.visitSpace(classDecl.classKind.prefix, p); p.append(kind); await this.visit(classDecl.name, p); classDecl.typeParameters && await this.visitContainerLocal("<", classDecl.typeParameters, ",", ">", p); classDecl.primaryConstructor && await this.visitContainerLocal("(", classDecl.primaryConstructor, ",", ")", p); classDecl.extends && await this.visitLeftPaddedLocal("extends", classDecl.extends, p); classDecl.implements && await this.visitContainerLocal(classDecl.classKind.type === J.ClassDeclaration.Kind.Type.Interface ? "extends" : "implements", classDecl.implements, ",", null, p); await this.visit(classDecl.body, p); await this.afterSyntax(classDecl, p); return classDecl; } override async visitMethodDeclaration(method: J.MethodDeclaration, p: PrintOutputCapture): Promise { await this.beforeSyntax(method, p); await this.visitSpace(emptySpace, p); await this.visitNodes(method.leadingAnnotations, p); for (const m of method.modifiers) { await this.visitModifier(m, p); } let m; if ((m = findMarker(method, JS.Markers.FunctionDeclaration))) { await this.visitSpace(m.prefix, p); p.append("function"); } const asterisk = findMarker(method, JS.Markers.Generator); if (asterisk) { await this.visitSpace(asterisk.prefix, p); p.append("*"); } await this.visit(method.name, p); const typeParameters = method.typeParameters; if (typeParameters) { await this.visitNodes(typeParameters.annotations, p); await this.visitSpace(typeParameters.prefix, p); await this.visitMarkers(typeParameters.markers, p); p.append("<"); await this.visitRightPaddedLocal(typeParameters.typeParameters, ",", p); p.append(">"); } await this.visitContainerLocal("(", method.parameters, ",", ")", p); if (method.returnTypeExpression) { await this.visit(method.returnTypeExpression, p); } method.body && await this.visit(method.body, p); await this.afterSyntax(method, p); return method; } override async visitComputedPropertyMethodDeclaration(method: JS.ComputedPropertyMethodDeclaration, p: PrintOutputCapture): Promise { await this.beforeSyntax(method, p); await this.visitSpace(emptySpace, p); await this.visitNodes(method.leadingAnnotations, p); for (const it of method.modifiers) { await this.visitModifier(it, p); } const generator = findMarker(method, JS.Markers.Generator); if (generator) { await this.visitSpace(generator.prefix, p); p.append("*"); } await this.visit(method.name, p); const typeParameters = method.typeParameters; if (typeParameters) { await this.visitNodes(typeParameters.annotations, p); await this.visitSpace(typeParameters.prefix, p); await this.visitMarkers(typeParameters.markers, p); p.append("<"); await this.visitRightPaddedLocal(typeParameters.typeParameters, ",", p); p.append(">"); } await this.visitContainerLocal("(", method.parameters, ",", ")", p); if (method.returnTypeExpression) { await this.visit(method.returnTypeExpression, p); } method.body && await this.visit(method.body, p); await this.afterSyntax(method, p); return method; } override async visitMethodInvocation(method: J.MethodInvocation, p: PrintOutputCapture): Promise { await this.beforeSyntax(method, p); if (method.name.simpleName.length === 0) { method.select && await this.visitRightPadded(method.select, p); } else { if (method.select) { await this.visitRightPadded(method.select, p); if (!method.select.element.markers.markers.find(m => m.kind === JS.Markers.Optional)) { p.append("."); } else { p.append("?."); } } await this.visit(method.name, p); } method.typeParameters && await this.visitContainerLocal("<", method.typeParameters, ",", ">", p); await this.visitContainerLocal("(", method.arguments, ",", ")", p); await this.afterSyntax(method, p); return method; } override async visitTypeParameter(typeParameter: J.TypeParameter, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeParameter, p); await this.visitNodes(typeParameter.annotations, p); for (const m of typeParameter.modifiers) { await this.visitModifier(m, p); } await this.visit(typeParameter.name, p); const bounds = typeParameter.bounds; if (bounds) { await this.visitSpace(bounds.before, p); const constraintType = bounds.elements[0]; if (!(constraintType.element.kind === J.Kind.Empty)) { p.append("extends"); await this.visitRightPadded(constraintType, p); } const defaultType = bounds.elements[1]; if (!(defaultType.element.kind === J.Kind.Empty)) { p.append("="); await this.visitRightPadded(defaultType, p); } } await this.afterSyntax(typeParameter, p); return typeParameter; } override async visitArrowFunction(arrowFunction: JS.ArrowFunction, p: PrintOutputCapture): Promise { await this.beforeSyntax(arrowFunction, p); await this.visitNodes(arrowFunction.leadingAnnotations, p); for (const m of arrowFunction.modifiers) { await this.visitModifier(m, p); } const typeParameters = arrowFunction.typeParameters; if (typeParameters) { await this.visitNodes(typeParameters.annotations, p); await this.visitSpace(typeParameters.prefix, p); await this.visitMarkers(typeParameters.markers, p); p.append("<"); await this.visitRightPaddedLocal(typeParameters.typeParameters, ",", p); p.append(">"); } const lambda = arrowFunction.lambda; if (lambda.parameters.parenthesized) { await this.visitSpace(lambda.parameters.prefix, p); p.append("("); await this.visitRightPaddedLocal(lambda.parameters.parameters, ",", p); p.append(")"); } else { await this.visitRightPaddedLocal(lambda.parameters.parameters, ",", p); } if (arrowFunction.returnTypeExpression) { await this.visit(arrowFunction.returnTypeExpression, p); } await this.visitSpace(lambda.arrow, p); p.append("=>"); await this.visit(lambda.body, p); await this.afterSyntax(arrowFunction, p); return arrowFunction; } override async visitConditionalType(conditionalType: JS.ConditionalType, p: PrintOutputCapture): Promise { await this.beforeSyntax(conditionalType, p); await this.visit(conditionalType.checkType, p); await this.visitLeftPaddedLocal("extends", conditionalType.condition, p); await this.afterSyntax(conditionalType, p); return conditionalType; } override async visitExpressionWithTypeArguments(type: JS.ExpressionWithTypeArguments, p: PrintOutputCapture): Promise { await this.beforeSyntax(type, p); await this.visit(type.clazz, p); type.typeArguments && await this.visitContainerLocal("<", type.typeArguments, ",", ">", p); await this.afterSyntax(type, p); return type; } override async visitImportType(importType: JS.ImportType, p: PrintOutputCapture): Promise { await this.beforeSyntax(importType, p); if (importType.hasTypeof.element) { p.append("typeof"); await this.visitRightPadded(importType.hasTypeof, p); } p.append("import"); await this.visitContainerLocal("(", importType.argumentAndAttributes, ",", ")", p); importType.qualifier && await this.visitLeftPaddedLocal(".", importType.qualifier, p); importType.typeArguments && await this.visitContainerLocal("<", importType.typeArguments, ",", ">", p); await this.afterSyntax(importType, p); return importType; } override async visitTypeDeclaration(typeDeclaration: JS.TypeDeclaration, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeDeclaration, p); for (const m of typeDeclaration.modifiers) { await this.visitModifier(m, p); } await this.visitLeftPaddedLocal("type", typeDeclaration.name, p); const typeParameters = typeDeclaration.typeParameters; if (typeParameters) { await this.visitNodes(typeParameters.annotations, p); await this.visitSpace(typeParameters.prefix, p); await this.visitMarkers(typeParameters.markers, p); p.append("<"); await this.visitRightPaddedLocal(typeParameters.typeParameters, ",", p); p.append(">"); } await this.visitLeftPaddedLocal("=", typeDeclaration.initializer, p); await this.afterSyntax(typeDeclaration, p); return typeDeclaration; } override async visitLiteralType(literalType: JS.LiteralType, p: PrintOutputCapture): Promise { await this.beforeSyntax(literalType, p); await this.visit(literalType.literal, p); await this.afterSyntax(literalType, p); return literalType; } override async visitNamedImports(namedImports: JS.NamedImports, p: PrintOutputCapture): Promise { await this.beforeSyntax(namedImports, p); await this.visitContainerLocal("{", namedImports.elements, ",", "}", p); await this.afterSyntax(namedImports, p); return namedImports; } override async visitImportSpecifier(jis: JS.ImportSpecifier, p: PrintOutputCapture): Promise { await this.beforeSyntax(jis, p); if (jis.importType.element) { await this.visitLeftPaddedLocal("type", jis.importType, p); } await this.visit(jis.specifier, p); await this.afterSyntax(jis, p); return jis; } override async visitExportDeclaration(ed: JS.ExportDeclaration, p: PrintOutputCapture): Promise { await this.beforeSyntax(ed, p); p.append("export"); for (const it of ed.modifiers) { await this.visitModifier(it, p); } if (ed.typeOnly.element) { await this.visitLeftPaddedLocal("type", ed.typeOnly, p); } ed.exportClause && await this.visit(ed.exportClause, p); ed.moduleSpecifier && await this.visitLeftPaddedLocal("from", ed.moduleSpecifier, p); ed.attributes && await this.visit(ed.attributes, p); await this.afterSyntax(ed, p); return ed; } override async visitExportAssignment(es: JS.ExportAssignment, p: PrintOutputCapture): Promise { await this.beforeSyntax(es, p); p.append("export"); await this.visitLeftPaddedLocal(es.exportEquals ? "=" : "default", es.expression, p); await this.afterSyntax(es, p); return es; } override async visitIndexedAccessType(iat: JS.IndexedAccessType, p: PrintOutputCapture): Promise { await this.beforeSyntax(iat, p); await this.visit(iat.objectType, p); // expect that this element is printed accordingly // [index] await this.visit(iat.indexType, p); await this.afterSyntax(iat, p); return iat; } override async visitIndexedAccessTypeIndexType(indexType: JS.IndexedAccessType.IndexType, p: PrintOutputCapture): Promise { await this.beforeSyntax(indexType, p); p.append("["); await this.visitRightPadded(indexType.element, p); p.append("]"); await this.afterSyntax(indexType, p); return indexType; } override async visitWithStatement(withStatement: JS.WithStatement, p: PrintOutputCapture): Promise { await this.beforeSyntax(withStatement, p); p.append("with"); await this.visit(withStatement.expression, p); await this.visitRightPadded(withStatement.body, p); await this.afterSyntax(withStatement, p); return withStatement; } override async visitExportSpecifier(es: JS.ExportSpecifier, p: PrintOutputCapture): Promise { await this.beforeSyntax(es, p); if (es.typeOnly.element) { await this.visitLeftPaddedLocal("type", es.typeOnly, p); } await this.visit(es.specifier, p); await this.afterSyntax(es, p); return es; } override async visitNamedExports(ne: JS.NamedExports, p: PrintOutputCapture): Promise { await this.beforeSyntax(ne, p); await this.visitContainerLocal("{", ne.elements, ",", "}", p); await this.afterSyntax(ne, p); return ne; } override async visitImportAttributes(importAttributes: JS.ImportAttributes, p: PrintOutputCapture): Promise { await this.beforeSyntax(importAttributes, p); p.append((importAttributes.token === JS.ImportAttributes.Token.With ? "with" : "assert")); await this.visitContainerLocal("{", importAttributes.elements, ",", "}", p); await this.afterSyntax(importAttributes, p); return importAttributes; } override async visitImportAttribute(importAttribute: JS.ImportAttribute, p: PrintOutputCapture): Promise { await this.beforeSyntax(importAttribute, p); await this.visit(importAttribute.name, p); await this.visitLeftPaddedLocal(":", importAttribute.value, p); await this.afterSyntax(importAttribute, p); return importAttribute; } override async visitImportTypeAttributes(importAttributes: JS.ImportTypeAttributes, p: PrintOutputCapture): Promise { await this.beforeSyntax(importAttributes, p); p.append("{"); await this.visitRightPadded(importAttributes.token, p); p.append(":"); await this.visitContainerLocal("{", importAttributes.elements, ",", "}", p); await this.visitSpace(importAttributes.end, p); p.append("}"); await this.afterSyntax(importAttributes, p); return importAttributes; } override async visitArrayBindingPattern(abp: JS.ArrayBindingPattern, p: PrintOutputCapture): Promise { await this.beforeSyntax(abp, p); await this.visitContainerLocal("[", abp.elements, ",", "]", p); await this.afterSyntax(abp, p); return abp; } override async visitMappedType(mappedType: JS.MappedType, p: PrintOutputCapture): Promise { await this.beforeSyntax(mappedType, p); p.append("{"); if (mappedType.prefixToken) { await this.visitLeftPadded(mappedType.prefixToken, p); } if (mappedType.hasReadonly.element) { await this.visitLeftPaddedLocal("readonly", mappedType.hasReadonly, p); } await this.visitMappedTypeKeysRemapping(mappedType.keysRemapping, p); if (mappedType.suffixToken) { await this.visitLeftPadded(mappedType.suffixToken, p); } if (mappedType.hasQuestionToken.element) { await this.visitLeftPaddedLocal("?", mappedType.hasQuestionToken, p); } const colon = mappedType.valueType.elements[0].element.kind === J.Kind.Empty ? "" : ":"; await this.visitContainerLocal(colon, mappedType.valueType, "", "", p); p.append("}"); await this.afterSyntax(mappedType, p); return mappedType; } override async visitMappedTypeKeysRemapping(mappedTypeKeys: JS.MappedType.KeysRemapping, p: PrintOutputCapture): Promise { await this.beforeSyntax(mappedTypeKeys, p); p.append("["); await this.visitRightPadded(mappedTypeKeys.typeParameter, p); if (mappedTypeKeys.nameType) { p.append("as"); await this.visitRightPadded(mappedTypeKeys.nameType, p); } p.append("]"); await this.afterSyntax(mappedTypeKeys, p); return mappedTypeKeys; } override async visitMappedTypeParameter(mappedTypeParameter: JS.MappedType.Parameter, p: PrintOutputCapture): Promise { await this.beforeSyntax(mappedTypeParameter, p); await this.visit(mappedTypeParameter.name, p); await this.visitLeftPaddedLocal("in", mappedTypeParameter.iterateType, p); await this.afterSyntax(mappedTypeParameter, p); return mappedTypeParameter; } override async visitObjectBindingPattern(objectBindingPattern: JS.ObjectBindingPattern, p: PrintOutputCapture): Promise { await this.beforeSyntax(objectBindingPattern, p); await this.visitNodes(objectBindingPattern.leadingAnnotations, p); for (const m of objectBindingPattern.modifiers) { await this.visitModifier(m, p); } objectBindingPattern.typeExpression && await this.visit(objectBindingPattern.typeExpression, p); await this.visitContainerLocal("{", objectBindingPattern.bindings, ",", "}", p); objectBindingPattern.initializer && await this.visitLeftPaddedLocal("=", objectBindingPattern.initializer, p); await this.afterSyntax(objectBindingPattern, p); return objectBindingPattern; } override async visitTaggedTemplateExpression(taggedTemplateExpression: JS.TaggedTemplateExpression, p: PrintOutputCapture): Promise { await this.beforeSyntax(taggedTemplateExpression, p); taggedTemplateExpression.tag && await this.visitRightPadded(taggedTemplateExpression.tag, p); taggedTemplateExpression.typeArguments && await this.visitContainerLocal("<", taggedTemplateExpression.typeArguments, ",", ">", p); await this.visit(taggedTemplateExpression.templateExpression, p); await this.afterSyntax(taggedTemplateExpression, p); return taggedTemplateExpression; } override async visitTemplateExpression(templateExpression: JS.TemplateExpression, p: PrintOutputCapture): Promise { await this.beforeSyntax(templateExpression, p); await this.visit(templateExpression.head, p); await this.visitRightPaddedLocal(templateExpression.spans, "", p); await this.afterSyntax(templateExpression, p); return templateExpression; } override async visitTemplateExpressionSpan(value: JS.TemplateExpression.Span, p: PrintOutputCapture): Promise { await this.beforeSyntax(value, p); await this.visit(value.expression, p); await this.visit(value.tail, p); await this.afterSyntax(value, p); return value; } override async visitTuple(tuple: JS.Tuple, p: PrintOutputCapture): Promise { await this.beforeSyntax(tuple, p); await this.visitContainerLocal("[", tuple.elements, ",", "]", p); await this.afterSyntax(tuple, p); return tuple; } override async visitTypeQuery(typeQuery: JS.TypeQuery, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeQuery, p); p.append("typeof"); await this.visit(typeQuery.typeExpression, p); typeQuery.typeArguments && await this.visitContainerLocal("<", typeQuery.typeArguments, ",", ">", p); await this.afterSyntax(typeQuery, p); return typeQuery; } override async visitTypeOf(typeOf: JS.TypeOf, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeOf, p); p.append("typeof"); await this.visit(typeOf.expression, p); await this.afterSyntax(typeOf, p); return typeOf; } protected async visitComputedPropertyName(computedPropertyName: JS.ComputedPropertyName, p: PrintOutputCapture): Promise { await this.beforeSyntax(computedPropertyName, p); p.append("["); await this.visitRightPaddedLocalSingle(computedPropertyName.expression, "]", p); await this.afterSyntax(computedPropertyName, p); return computedPropertyName; } override async visitTypeOperator(typeOperator: JS.TypeOperator, p: PrintOutputCapture): Promise { await this.beforeSyntax(typeOperator, p); let keyword = ""; if (typeOperator.operator === JS.TypeOperator.Type.ReadOnly) { keyword = "readonly"; } else if (typeOperator.operator === JS.TypeOperator.Type.KeyOf) { keyword = "keyof"; } else if (typeOperator.operator === JS.TypeOperator.Type.Unique) { keyword = "unique"; } p.append(keyword); await this.visitLeftPadded(typeOperator.expression, p); await this.afterSyntax(typeOperator, p); return typeOperator; } override async visitTypePredicate(typePredicate: JS.TypePredicate, p: PrintOutputCapture): Promise { await this.beforeSyntax(typePredicate, p); if (typePredicate.asserts.element) { await this.visitLeftPaddedLocal("asserts", typePredicate.asserts, p); } await this.visit(typePredicate.parameterName, p); typePredicate.expression && await this.visitLeftPaddedLocal("is", typePredicate.expression, p); await this.afterSyntax(typePredicate, p); return typePredicate; } override async visitIndexSignatureDeclaration(isd: JS.IndexSignatureDeclaration, p: PrintOutputCapture): Promise { await this.beforeSyntax(isd, p); for (const m of isd.modifiers) { await this.visitModifier(m, p); } await this.visitContainerLocal("[", isd.parameters, "", "]", p); await this.visitLeftPaddedLocal(":", isd.typeExpression, p); await this.afterSyntax(isd, p); return isd; } override async visitAnnotation(annotation: J.Annotation, p: PrintOutputCapture): Promise { await this.beforeSyntax(annotation, p); p.append("@"); await this.visit(annotation.annotationType, p); annotation.arguments && await this.visitContainerLocal("(", annotation.arguments, ",", ")", p); await this.afterSyntax(annotation, p); return annotation; } override async visitNewArray(newArray: J.NewArray, p: PrintOutputCapture): Promise { await this.beforeSyntax(newArray, p); newArray.typeExpression && await this.visit(newArray.typeExpression, p); await this.visitNodes(newArray.dimensions, p); newArray.initializer && await this.visitContainerLocal("[", newArray.initializer, ",", "]", p); await this.afterSyntax(newArray, p); return newArray; } override async visitNewClass(newClass: J.NewClass, p: PrintOutputCapture): Promise { await this.beforeSyntax(newClass, p); newClass.enclosing && await this.visitRightPaddedLocalSingle(newClass.enclosing, ".", p); await this.visitSpace(newClass.new, p); if (newClass.class) { p.append("new"); await this.visit(newClass.class, p); if (!newClass.arguments.markers.markers.find(m => m.kind === J.Markers.OmitParentheses)) { await this.visitContainerLocal("(", newClass.arguments, ",", ")", p); } } newClass.body && await this.visit(newClass.body, p); await this.afterSyntax(newClass, p); return newClass; } override async visitSwitch(switch_: J.Switch, p: PrintOutputCapture): Promise { await this.beforeSyntax(switch_, p); p.append("switch"); await this.visit(switch_.selector, p); await this.visit(switch_.cases, p); await this.afterSyntax(switch_, p); return switch_; } override async visitCase(case_: J.Case, p: PrintOutputCapture): Promise { await this.beforeSyntax(case_, p); const elem = case_.caseLabels.elements[0].element; if (elem.kind !== J.Kind.Identifier || (elem as J.Identifier).simpleName !== "default") { p.append("case"); } await this.visitContainerLocal("", case_.caseLabels, ",", "", p); await this.visitSpace(case_.statements.before, p); p.append(case_.type === J.Case.Type.Statement ? ":" : "->"); await this.visitStatements(case_.statements.elements, p); await this.afterSyntax(case_, p); return case_; } override async visitLabel(label: J.Label, p: PrintOutputCapture): Promise { await this.beforeSyntax(label, p); await this.visitRightPaddedLocalSingle(label.label, ":", p); await this.visit(label.statement, p); await this.afterSyntax(label, p); return label; } override async visitContinue(continueStatement: J.Continue, p: PrintOutputCapture): Promise { await this.beforeSyntax(continueStatement, p); p.append("continue"); continueStatement.label && await this.visit(continueStatement.label, p); await this.afterSyntax(continueStatement, p); return continueStatement; } override async visitBreak(breakStatement: J.Break, p: PrintOutputCapture): Promise { await this.beforeSyntax(breakStatement, p); p.append("break"); breakStatement.label && await this.visit(breakStatement.label, p); await this.afterSyntax(breakStatement, p); return breakStatement; } override async visitFieldAccess(fieldAccess: J.FieldAccess, p: PrintOutputCapture): Promise { await this.beforeSyntax(fieldAccess, p); await this.visit(fieldAccess.target, p); await this.visitLeftPaddedLocal(".", fieldAccess.name, p); await this.afterSyntax(fieldAccess, p); return fieldAccess; } override async visitTypeLiteral(tl: JS.TypeLiteral, p: PrintOutputCapture): Promise { await this.beforeSyntax(tl, p); await this.visit(tl.members, p); await this.afterSyntax(tl, p); return tl; } override async visitParentheses(parens: J.Parentheses, p: PrintOutputCapture): Promise { await this.beforeSyntax(parens, p); p.append('('); await this.visitRightPaddedLocalSingle(parens.tree, ")", p); await this.afterSyntax(parens, p); return parens; } override async visitParameterizedType(type: J.ParameterizedType, p: PrintOutputCapture): Promise { await this.beforeSyntax(type, p); await this.visit(type.class, p); type.typeParameters && await this.visitContainerLocal("<", type.typeParameters, ",", ">", p); await this.afterSyntax(type, p); return type; } override async visitAs(as_: JS.As, p: PrintOutputCapture): Promise { await this.beforeSyntax(as_, p); await this.visitRightPadded(as_.left, p); p.append("as"); await this.visit(as_.right, p); await this.afterSyntax(as_, p); return as_; } override async visitAssignment(assignment: J.Assignment, p: PrintOutputCapture): Promise { await this.beforeSyntax(assignment, p); await this.visit(assignment.variable, p); await this.visitLeftPaddedLocal("=", assignment.assignment, p); await this.afterSyntax(assignment, p); return assignment; } override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, p: PrintOutputCapture): Promise { await this.beforeSyntax(propertyAssignment, p); await this.visitRightPadded(propertyAssignment.name, p); if (propertyAssignment.initializer) { // if the property is not null, we should print it like `{ a: b }` // otherwise, it is a shorthand assignment where we have stuff like `{ a }` only if (propertyAssignment.assigmentToken === JS.PropertyAssignment.Token.Colon) { p.append(':'); } else if (propertyAssignment.assigmentToken === JS.PropertyAssignment.Token.Equals) { p.append('='); } await this.visit(propertyAssignment.initializer, p); } await this.afterSyntax(propertyAssignment, p); return propertyAssignment; } override async visitAssignmentOperation(assignOp: J.AssignmentOperation, p: PrintOutputCapture): Promise { let keyword = ""; switch (assignOp.operator.element) { case J.AssignmentOperation.Type.Addition: keyword = "+="; break; case J.AssignmentOperation.Type.Subtraction: keyword = "-="; break; case J.AssignmentOperation.Type.Multiplication: keyword = "*="; break; case J.AssignmentOperation.Type.Division: keyword = "/="; break; case J.AssignmentOperation.Type.Modulo: keyword = "%="; break; case J.AssignmentOperation.Type.BitAnd: keyword = "&="; break; case J.AssignmentOperation.Type.BitOr: keyword = "|="; break; case J.AssignmentOperation.Type.BitXor: keyword = "^="; break; case J.AssignmentOperation.Type.LeftShift: keyword = "<<="; break; case J.AssignmentOperation.Type.RightShift: keyword = ">>="; break; case J.AssignmentOperation.Type.UnsignedRightShift: keyword = ">>>="; break; } await this.beforeSyntax(assignOp, p); await this.visit(assignOp.variable, p); await this.visitSpace(assignOp.operator.before, p); p.append(keyword); await this.visit(assignOp.assignment, p); await this.afterSyntax(assignOp, p); return assignOp; } override async visitAssignmentOperationExtensions(assignOp: JS.AssignmentOperation, p: PrintOutputCapture): Promise { let keyword = ""; switch (assignOp.operator.element) { case JS.AssignmentOperation.Type.QuestionQuestion: keyword = "??="; break; case JS.AssignmentOperation.Type.And: keyword = "&&="; break; case JS.AssignmentOperation.Type.Or: keyword = "||="; break; case JS.AssignmentOperation.Type.Power: keyword = "**"; break; case JS.AssignmentOperation.Type.Exp: keyword = "**="; break; } await this.beforeSyntax(assignOp, p); await this.visit(assignOp.variable, p); await this.visitSpace(assignOp.operator.before, p); p.append(keyword); await this.visit(assignOp.assignment, p); await this.afterSyntax(assignOp, p); return assignOp; } override async visitEnumValue(enum_: J.EnumValue, p: PrintOutputCapture): Promise { await this.beforeSyntax(enum_, p); await this.visit(enum_.name, p); const initializer = enum_.initializer; if (initializer) { await this.visitSpace(initializer.prefix, p); p.append("="); // There can be only one argument const expression = initializer.arguments.elements[0]; await this.visitRightPadded(expression, p); return enum_; } await this.afterSyntax(enum_, p); return enum_; } override async visitEnumValueSet(enums: J.EnumValueSet, p: PrintOutputCapture): Promise { await this.beforeSyntax(enums, p); await this.visitRightPaddedLocal(enums.enums, ",", p); if (enums.terminatedWithSemicolon) { p.append(","); } await this.afterSyntax(enums, p); return enums; } override async visitBinary(binary: J.Binary, p: PrintOutputCapture): Promise { let keyword = ""; switch (binary.operator.element) { case J.Binary.Type.Addition: keyword = "+"; break; case J.Binary.Type.Subtraction: keyword = "-"; break; case J.Binary.Type.Multiplication: keyword = "*"; break; case J.Binary.Type.Division: keyword = "/"; break; case J.Binary.Type.Modulo: keyword = "%"; break; case J.Binary.Type.LessThan: keyword = "<"; break; case J.Binary.Type.GreaterThan: keyword = ">"; break; case J.Binary.Type.LessThanOrEqual: keyword = "<="; break; case J.Binary.Type.GreaterThanOrEqual: keyword = ">="; break; case J.Binary.Type.Equal: keyword = "=="; break; case J.Binary.Type.NotEqual: keyword = "!="; break; case J.Binary.Type.BitAnd: keyword = "&"; break; case J.Binary.Type.BitOr: keyword = "|"; break; case J.Binary.Type.BitXor: keyword = "^"; break; case J.Binary.Type.LeftShift: keyword = "<<"; break; case J.Binary.Type.RightShift: keyword = ">>"; break; case J.Binary.Type.UnsignedRightShift: keyword = ">>>"; break; case J.Binary.Type.Or: keyword = "||"; break; case J.Binary.Type.And: keyword = "&&"; break; } await this.beforeSyntax(binary, p); await this.visit(binary.left, p); await this.visitSpace(binary.operator.before, p); p.append(keyword); await this.visit(binary.right, p); await this.afterSyntax(binary, p); return binary; } override async visitBinaryExtensions(binary: JS.Binary, p: PrintOutputCapture): Promise { await this.beforeSyntax(binary, p); await this.visit(binary.left, p); let keyword = ""; switch (binary.operator.element) { case JS.Binary.Type.IdentityEquals: keyword = "==="; break; case JS.Binary.Type.IdentityNotEquals: keyword = "!=="; break; case JS.Binary.Type.In: keyword = "in"; break; case JS.Binary.Type.QuestionQuestion: keyword = "??"; break; case JS.Binary.Type.Comma: keyword = ","; break; } await this.visitSpace(binary.operator.before, p); p.append(keyword); await this.visit(binary.right, p); await this.afterSyntax(binary, p); return binary; } override async visitUnary(unary: J.Unary, p: PrintOutputCapture): Promise { await this.beforeSyntax(unary, p); switch (unary.operator.element) { case J.Unary.Type.PreIncrement: p.append("++"); await this.visit(unary.expression, p); break; case J.Unary.Type.PreDecrement: p.append("--"); await this.visit(unary.expression, p); break; case J.Unary.Type.PostIncrement: await this.visit(unary.expression, p); await this.visitSpace(unary.operator.before, p); p.append("++"); break; case J.Unary.Type.PostDecrement: await this.visit(unary.expression, p); await this.visitSpace(unary.operator.before, p); p.append("--"); break; case J.Unary.Type.Positive: p.append('+'); await this.visit(unary.expression, p); break; case J.Unary.Type.Negative: p.append('-'); await this.visit(unary.expression, p); break; case J.Unary.Type.Complement: p.append('~'); await this.visit(unary.expression, p); break; case J.Unary.Type.Not: default: p.append('!'); await this.visit(unary.expression, p); } await this.afterSyntax(unary, p); return unary; } override async visitUnion(union: JS.Union, p: PrintOutputCapture): Promise { await this.beforeSyntax(union, p); await this.visitRightPaddedLocal(union.types, "|", p); await this.afterSyntax(union, p); return union; } override async visitIntersection(intersection: JS.Intersection, p: PrintOutputCapture): Promise { await this.beforeSyntax(intersection, p); await this.visitRightPaddedLocal(intersection.types, "&", p); await this.afterSyntax(intersection, p); return intersection; } override async visitForLoop(forLoop: J.ForLoop, p: PrintOutputCapture): Promise { await this.beforeSyntax(forLoop, p); p.append("for"); const ctrl = forLoop.control; await this.visitSpace(ctrl.prefix, p); p.append('('); await this.visitRightPaddedLocal(ctrl.init, ",", p); p.append(';'); ctrl.condition && await this.visitRightPaddedLocalSingle(ctrl.condition, ";", p); await this.visitRightPaddedLocal(ctrl.update, ",", p); p.append(')'); await this.visitStatementLocal(forLoop.body, p); await this.afterSyntax(forLoop, p); return forLoop; } override async visitForOfLoop(loop: JS.ForOfLoop, p: PrintOutputCapture): Promise { await this.beforeSyntax(loop, p); p.append("for"); if (loop.await) { await this.visitSpace(loop.await, p); p.append("await"); } const control = loop.loop.control; await this.visitSpace(control.prefix, p); p.append('('); await this.visitRightPadded(control.variable, p); p.append("of"); await this.visitRightPadded(control.iterable, p); p.append(')'); await this.visitRightPadded(loop.loop.body, p); await this.afterSyntax(loop, p); return loop; } override async visitForInLoop(loop: JS.ForInLoop, p: PrintOutputCapture): Promise { await this.beforeSyntax(loop, p); p.append("for"); const control = loop.control; await this.visitSpace(control.prefix, p); p.append('('); await this.visitRightPadded(control.variable, p); p.append("in"); await this.visitRightPadded(control.iterable, p); p.append(')'); await this.visitRightPadded(loop.body, p); await this.afterSyntax(loop, p); return loop; } // ---- print utils private async visitStatements(statements: J.RightPadded[], p: PrintOutputCapture) { const objectLiteral = this.getParentCursor(0)?.value.kind === J.Kind.Block && this.getParentCursor(1)?.value.kind === J.Kind.NewClass; for (let i = 0; i < statements.length; i++) { const paddedStat = statements[i]; await this.visitStatementLocal(paddedStat, p); if (i < statements.length - 1 && objectLiteral) { p.append(","); } } } private async visitStatementLocal(paddedStat: J.RightPadded | undefined, p: PrintOutputCapture) { if (paddedStat) { await this.visit(paddedStat.element, p); await this.visitSpace(paddedStat.after, p); await this.visitMarkers(paddedStat.markers, p); } } private getParentCursor(levels: number): Cursor | undefined { let cursor: Cursor | undefined = this.cursor; for (let i = 0; i < levels && cursor; i++) { cursor = cursor.parent; } return cursor; } protected async afterSyntax(j: J, p: PrintOutputCapture) { await this.afterSyntaxMarkers(j.markers, p); } private async afterSyntaxMarkers(markers: Markers, p: PrintOutputCapture) { for (const marker of markers.markers) { p.append(p.markerPrinter.afterSyntax(marker, new Cursor(marker, this.cursor), this.JAVA_SCRIPT_MARKER_WRAPPER)); } } protected async beforeSyntax(j: J, p: PrintOutputCapture) { await this.beforeSyntaxExt(j.prefix, j.markers, p); } private async beforeSyntaxExt(prefix: J.Space, markers: Markers, p: PrintOutputCapture) { for (const marker of markers.markers) { p.append(p.markerPrinter.beforePrefix(marker, new Cursor(marker, this.cursor), this.JAVA_SCRIPT_MARKER_WRAPPER)); } await this.visitSpace(prefix, p); await this.visitMarkers(markers, p); for (const marker of markers.markers) { p.append(p.markerPrinter.beforeSyntax(marker, new Cursor(marker, this.cursor), this.JAVA_SCRIPT_MARKER_WRAPPER)); } } override async visitSpace(space: J.Space | undefined, p: PrintOutputCapture): Promise { // Recipes can mutate trees in ways that drop required Space fields (e.g. a // RightPadded with `after` left undefined). The hot path is BatchVisit → // print, so throwing here aborts the formatter for the whole file and the // caller falls back to "essential formatting", losing Prettier output. // Treat a missing Space as empty: produces correct text and keeps the // recipe on the fast path. if (!space) { return emptySpace; } p.append(space.whitespace!); const comments = space.comments; for (let i = 0; i < comments.length; i++) { const comment = comments[i]; await this.visitMarkers(comment.markers, p); this.printComment(comment, this.cursor, p); p.append(comment.suffix); } return space; } private async visitRightPaddedLocal(nodes: J.RightPadded[], suffixBetween: string, p: PrintOutputCapture) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; await this.visit(node.element, p); await this.visitSpace(node.after, p); await this.visitMarkers(node.markers, p); if (i < nodes.length - 1) { p.append(suffixBetween); } } } public async visitRightPadded(right: J.RightPadded, p: PrintOutputCapture): Promise> { if (isTree(right.element)) { await this.visit(right.element, p); } await this.visitSpace(right.after, p); await this.visitMarkers(right.markers, p); return right; } private async visitRightPaddedLocalSingle(node: J.RightPadded | undefined, suffix: string, p: PrintOutputCapture) { if (node) { await this.visit(node.element, p); await this.visitSpace(node.after, p); await this.visitMarkers(node.markers, p); p.append(suffix); } } private async visitLeftPaddedLocal(prefix: string | undefined, leftPadded: J.LeftPadded | J.LeftPadded | J.LeftPadded | undefined, p: PrintOutputCapture) { if (leftPadded) { await this.beforeSyntaxExt(leftPadded.before, leftPadded.markers, p); if (prefix) { p.append(prefix); } if (typeof leftPadded.element === 'string') { p.append(leftPadded.element); } else if (typeof leftPadded.element !== 'boolean') { await this.visit(leftPadded.element, p); } await this.afterSyntaxMarkers(leftPadded.markers, p); } } private async visitContainerLocal(before: string, container: J.Container | undefined, suffixBetween: string, after: string | null, p: PrintOutputCapture) { if (!container) { return; } await this.beforeSyntaxExt(container.before, container.markers, p); p.append(before); await this.visitRightPaddedLocal(container.elements, suffixBetween, p); await this.afterSyntaxMarkers(container.markers, p); p.append(after === null ? "" : after); } override async visitMarker(marker: M, p: PrintOutputCapture): Promise { if (marker.kind === J.Markers.Semicolon) { p.append(';'); } if (marker.kind === J.Markers.TrailingComma) { p.append(','); await this.visitSpace((marker as unknown as TrailingComma).suffix, p); } return marker; } protected async preVisit(tree: J, p: PrintOutputCapture): Promise { // Note: Spread is now handled as JS.Spread AST element via visitSpread return tree; } protected async postVisit(tree: J, p: PrintOutputCapture): Promise { for (const marker of tree.markers.markers) { if (marker.kind === JS.Markers.NonNullAssertion) { await this.visitSpace((marker as NonNullAssertion).prefix, p); p.append("!"); } if (marker.kind === JS.Markers.Optional) { await this.visitSpace((marker as Optional).prefix, p); if (this.cursor.parent?.value?.kind !== J.Kind.MethodInvocation && this.cursor.parent?.value?.kind !== JS.Kind.FunctionCall) { p.append("?"); if (this.cursor.parent?.value?.kind === J.Kind.ArrayAccess) { p.append("."); } } } } return tree; } private printComment(comment: Comment, cursor: Cursor, p: PrintOutputCapture): void { for (const marker of comment.markers.markers) { p.append(p.markerPrinter.beforeSyntax(marker, new Cursor(this, cursor), this.JAVA_SCRIPT_MARKER_WRAPPER)); } if (comment.kind === J.Kind.TextComment) { const textComment = comment as TextComment; p.append(textComment.multiline ? `/*${textComment.text}*/` : `//${textComment.text}`); } for (const marker of comment.markers.markers) { p.append(p.markerPrinter.afterSyntax(marker, new Cursor(this, cursor), this.JAVA_SCRIPT_MARKER_WRAPPER)); } } private async visitNodes(nodes: T[] | undefined, p: PrintOutputCapture): Promise { if (nodes) { for (const node of nodes) { await this.visit(node, p); } } } override async visitControlParentheses(controlParens: J.ControlParentheses, p: PrintOutputCapture): Promise { await this.beforeSyntax(controlParens, p); if (this.getParentCursor(1)?.value.kind === J.Kind.TypeCast) { p.append('<'); await this.visitRightPaddedLocalSingle(controlParens.tree, ">", p); } else { p.append('('); await this.visitRightPaddedLocalSingle(controlParens.tree, ")", p); } await this.afterSyntax(controlParens, p); return controlParens; } } TreePrinters.register(JS.Kind.CompilationUnit, () => new JavaScriptPrinter());