From d24112df59d56b107b46e67e8cb9375b33f73bdb Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 12 Apr 2021 18:43:58 -0700 Subject: [PATCH] Remove preprinter, add parenthesizer callback to emit --- src/compiler/emitter.ts | 8429 +++++++---------- src/compiler/factory/nodeFactory.ts | 2 +- src/compiler/factory/parenthesizerRules.ts | 4 +- src/compiler/transformer.ts | 34 +- src/compiler/types.ts | 1 + .../inlineJsxFactoryDeclarationsLocalTypes.js | 4 +- .../jsxCheckJsxNoTypeArgumentsAllowed.js | 4 +- 7 files changed, 3711 insertions(+), 4767 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f45c86e9ccb8d..df79c2801c7da 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -863,6 +863,9 @@ namespace ts { export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { const { hasGlobalName, + onEmitNode = noEmitNotification, + isEmitNotificationEnabled, + substituteNode = noEmitSubstitution, onBeforeEmitNode, onAfterEmitNode, onBeforeEmitNodeArray, @@ -914,7 +917,15 @@ namespace ts { let hasWrittenComment = false; let commentsDisabled = !!printerOptions.removeComments; const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); - const preprint = createPreprinter(handlers); + + // Pipeline + let contextCallbackCache: ESMap<(emitHint: EmitHint, node: Node) => void, (emitHint: EmitHint, node: Node) => void> | undefined; + let substitutionCallbackCache: ESMap<(emitHint: EmitHint, node: Node) => void, (emitHint: EmitHint, node: Node) => void> | undefined; + let notificationCallbackCache: ESMap<(emitHint: EmitHint, node: Node) => void, (emitHint: EmitHint, node: Node) => void> | undefined; + let binaryLeftOperandParenthesizerCache: ESMap Expression> | undefined; + let binaryRightOperandParenthesizerCache: ESMap Expression> | undefined; + let currentParenthesizerRule: ((node: Node) => Node) | undefined; + const parenthesizer = factory.parenthesizer; const emitBinaryExpression = createEmitBinaryExpression(); reset(); @@ -1137,7 +1148,7 @@ namespace ts { setSourceFile(sourceFile); } - emit(preprint(hint, node)); + pipelineEmit(hint, node, emitWorker, /*parenthesizerRule*/ undefined); } function setSourceFile(sourceFile: SourceFile | undefined) { @@ -1176,15 +1187,25 @@ namespace ts { return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!)); } - function emit(node: Node): void; - function emit(node: Node | undefined): void; - function emit(node: Node | undefined) { + function emit(node: Node, parenthesizerRule?: (node: Node) => Node): void; + function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node): void; + function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node) { if (node === undefined) return; - const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); + pipelineEmit(EmitHint.Unspecified, node, emitWorker, parenthesizerRule); + } - emitWithContext(node, emitWorker); + function emitIdentifierName(node: Identifier): void; + function emitIdentifierName(node: Identifier | undefined): void; + function emitIdentifierName(node: Identifier | undefined) { + if (node === undefined) return; + pipelineEmit(EmitHint.IdentifierName, node, emitWorker, /*parenthesizerRule*/ undefined); + } - recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + function emitExpression(node: Expression, parenthesizerRule?: (node: Expression) => Expression): void; + function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression): void; + function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { + if (node === undefined) return; + pipelineEmit(EmitHint.Expression, node, emitWorker, parenthesizerRule); } function shouldEmitComments(node: Node) { @@ -1200,8 +1221,6 @@ namespace ts { } function beforeEmitWithContext(node: Node, shouldEmitComments: boolean, shouldEmitSourceMaps: boolean) { - onBeforeEmitNode?.(node); - const emitFlags = getEmitFlags(node); const commentRange = shouldEmitComments ? getCommentRange(node) : undefined; const sourceMapRange = shouldEmitSourceMaps ? getSourceMapRange(node) : undefined; @@ -1276,5482 +1295,4402 @@ namespace ts { } preserveSourceNewlines = savedPreserveSourceNewlines; - onAfterEmitNode?.(node); - } - - function emitWithContext(node: T, emitCallback: (node: T) => void) { - const savedPreserveSourceNewlines = preserveSourceNewlines; - const savedContainerPos = containerPos; - const savedContainerEnd = containerEnd; - const savedDeclarationListContainerEnd = declarationListContainerEnd; - const emitComments = shouldEmitComments(node); - const emitSourceMaps = shouldEmitSourceMaps(node); - beforeEmitWithContext(node, emitComments, emitSourceMaps); - emitCallback(node); - afterEmitWithContext(node, emitComments, emitSourceMaps, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd, savedPreserveSourceNewlines); } - function emitWorker(node: Node): void { - switch (node.kind) { - // Literals - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - return emitNumericOrBigIntLiteral(node); - - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return emitLiteral(node, /*jsxAttributeEscape*/ false); - - case SyntaxKind.JsxText: - return emitJsxText(node); - - // Pseudo-literals - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - return emitLiteral(node, /*jsxAttributeEscape*/ false); - - // Identifiers and PrivateIdentifiers - case SyntaxKind.Identifier: - return emitIdentifier(node); - case SyntaxKind.PrivateIdentifier: - return emitPrivateIdentifier(node as PrivateIdentifier); - - // Names - case SyntaxKind.QualifiedName: - return emitQualifiedName(node); - case SyntaxKind.ComputedPropertyName: - return emitComputedPropertyName(node); - - // Signature elements - case SyntaxKind.TypeParameter: - return emitTypeParameter(node); - case SyntaxKind.Parameter: - return emitParameter(node); - case SyntaxKind.Decorator: - return emitDecorator(node); - - // Type members - case SyntaxKind.PropertySignature: - return emitPropertySignature(node); - case SyntaxKind.PropertyDeclaration: - return emitPropertyDeclaration(node); - case SyntaxKind.MethodSignature: - return emitMethodSignature(node); - case SyntaxKind.MethodDeclaration: - return emitMethodDeclaration(node); - case SyntaxKind.Constructor: - return emitConstructor(node); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return emitAccessorDeclaration(node); - case SyntaxKind.CallSignature: - return emitCallSignature(node); - case SyntaxKind.ConstructSignature: - return emitConstructSignature(node); - case SyntaxKind.IndexSignature: - return emitIndexSignature(node); - - // Types - case SyntaxKind.TypePredicate: - return emitTypePredicate(node); - case SyntaxKind.TypeReference: - return emitTypeReference(node); - case SyntaxKind.FunctionType: - return emitFunctionType(node); - case SyntaxKind.ConstructorType: - return emitConstructorType(node); - case SyntaxKind.TypeQuery: - return emitTypeQuery(node); - case SyntaxKind.TypeLiteral: - return emitTypeLiteral(node); - case SyntaxKind.ArrayType: - return emitArrayType(node); - case SyntaxKind.TupleType: - return emitTupleType(node); - case SyntaxKind.OptionalType: - return emitOptionalType(node); - // SyntaxKind.RestType is handled below - case SyntaxKind.UnionType: - return emitUnionType(node); - case SyntaxKind.IntersectionType: - return emitIntersectionType(node); - case SyntaxKind.ConditionalType: - return emitConditionalType(node); - case SyntaxKind.InferType: - return emitInferType(node); - case SyntaxKind.ParenthesizedType: - return emitParenthesizedType(node); - case SyntaxKind.ThisType: - return emitThisType(); - case SyntaxKind.TypeOperator: - return emitTypeOperator(node); - case SyntaxKind.IndexedAccessType: - return emitIndexedAccessType(node); - case SyntaxKind.MappedType: - return emitMappedType(node); - case SyntaxKind.LiteralType: - return emitLiteralType(node); - case SyntaxKind.NamedTupleMember: - return emitNamedTupleMember(node as NamedTupleMember); - case SyntaxKind.TemplateLiteralType: - return emitTemplateType(node); - case SyntaxKind.TemplateLiteralTypeSpan: - return emitTemplateTypeSpan(node); - case SyntaxKind.ImportType: - return emitImportTypeNode(node); - - // Binding patterns - case SyntaxKind.ObjectBindingPattern: - return emitObjectBindingPattern(node); - case SyntaxKind.ArrayBindingPattern: - return emitArrayBindingPattern(node); - case SyntaxKind.BindingElement: - return emitBindingElement(node); - - // Expressions - case SyntaxKind.ArrayLiteralExpression: - return emitArrayLiteralExpression(node); - case SyntaxKind.ObjectLiteralExpression: - return emitObjectLiteralExpression(node); - case SyntaxKind.PropertyAccessExpression: - return emitPropertyAccessExpression(node); - case SyntaxKind.ElementAccessExpression: - return emitElementAccessExpression(node); - case SyntaxKind.CallExpression: - return emitCallExpression(node); - case SyntaxKind.NewExpression: - return emitNewExpression(node); - case SyntaxKind.TaggedTemplateExpression: - return emitTaggedTemplateExpression(node); - case SyntaxKind.TypeAssertionExpression: - return emitTypeAssertionExpression(node); - case SyntaxKind.ParenthesizedExpression: - return emitParenthesizedExpression(node); - case SyntaxKind.FunctionExpression: - return emitFunctionExpression(node); - case SyntaxKind.ArrowFunction: - return emitArrowFunction(node); - case SyntaxKind.DeleteExpression: - return emitDeleteExpression(node); - case SyntaxKind.TypeOfExpression: - return emitTypeOfExpression(node); - case SyntaxKind.VoidExpression: - return emitVoidExpression(node); - case SyntaxKind.AwaitExpression: - return emitAwaitExpression(node); - case SyntaxKind.PrefixUnaryExpression: - return emitPrefixUnaryExpression(node); - case SyntaxKind.PostfixUnaryExpression: - return emitPostfixUnaryExpression(node); - case SyntaxKind.BinaryExpression: - return emitBinaryExpression(node); - case SyntaxKind.ConditionalExpression: - return emitConditionalExpression(node); - case SyntaxKind.TemplateExpression: - return emitTemplateExpression(node); - case SyntaxKind.YieldExpression: - return emitYieldExpression(node); - case SyntaxKind.SpreadElement: - return emitSpreadExpression(node); - case SyntaxKind.ClassExpression: - return emitClassExpression(node); - case SyntaxKind.OmittedExpression: - return; - case SyntaxKind.ExpressionWithTypeArguments: - return emitExpressionWithTypeArguments(node); - case SyntaxKind.AsExpression: - return emitAsExpression(node); - case SyntaxKind.NonNullExpression: - return emitNonNullExpression(node); - case SyntaxKind.MetaProperty: - return emitMetaProperty(node); - case SyntaxKind.SyntheticExpression: - Debug.fail("SyntheticExpression should never be printed."); - break; - - // Misc - case SyntaxKind.TemplateSpan: - return emitTemplateSpan(node); - case SyntaxKind.SemicolonClassElement: - return emitSemicolonClassElement(); - - // Element - case SyntaxKind.Block: - return emitBlock(node); - case SyntaxKind.EmptyStatement: - return emitEmptyStatement(/*isEmbeddedStatement*/ false); - case SyntaxKind.VariableStatement: - return emitVariableStatement(node); - case SyntaxKind.ExpressionStatement: - return emitExpressionStatement(node); - case SyntaxKind.IfStatement: - return emitIfStatement(node); - case SyntaxKind.DoStatement: - return emitDoStatement(node); - case SyntaxKind.WhileStatement: - return emitWhileStatement(node); - case SyntaxKind.ForStatement: - return emitForStatement(node); - case SyntaxKind.ForInStatement: - return emitForInStatement(node); - case SyntaxKind.ForOfStatement: - return emitForOfStatement(node); - case SyntaxKind.ContinueStatement: - return emitContinueStatement(node); - case SyntaxKind.BreakStatement: - return emitBreakStatement(node); - case SyntaxKind.ReturnStatement: - return emitReturnStatement(node); - case SyntaxKind.WithStatement: - return emitWithStatement(node); - case SyntaxKind.SwitchStatement: - return emitSwitchStatement(node); - case SyntaxKind.LabeledStatement: - return emitLabeledStatement(node); - case SyntaxKind.ThrowStatement: - return emitThrowStatement(node); - case SyntaxKind.TryStatement: - return emitTryStatement(node); - case SyntaxKind.DebuggerStatement: - return emitDebuggerStatement(node); - - // Declarations - case SyntaxKind.VariableDeclaration: - return emitVariableDeclaration(node); - case SyntaxKind.VariableDeclarationList: - return emitVariableDeclarationList(node); - case SyntaxKind.FunctionDeclaration: - return emitFunctionDeclaration(node); - case SyntaxKind.ClassDeclaration: - return emitClassDeclaration(node); - case SyntaxKind.InterfaceDeclaration: - return emitInterfaceDeclaration(node); - case SyntaxKind.TypeAliasDeclaration: - return emitTypeAliasDeclaration(node); - case SyntaxKind.EnumDeclaration: - return emitEnumDeclaration(node); - case SyntaxKind.ModuleDeclaration: - return emitModuleDeclaration(node); - case SyntaxKind.ModuleBlock: - return emitModuleBlock(node); - case SyntaxKind.CaseBlock: - return emitCaseBlock(node); - case SyntaxKind.NamespaceExportDeclaration: - return emitNamespaceExportDeclaration(node); - case SyntaxKind.ImportEqualsDeclaration: - return emitImportEqualsDeclaration(node); - case SyntaxKind.ImportDeclaration: - return emitImportDeclaration(node); - case SyntaxKind.ImportClause: - return emitImportClause(node); - case SyntaxKind.NamespaceImport: - return emitNamespaceImport(node); - case SyntaxKind.NamedImports: - return emitNamedImports(node); - case SyntaxKind.ImportSpecifier: - return emitImportSpecifier(node); - case SyntaxKind.ExportAssignment: - return emitExportAssignment(node); - case SyntaxKind.ExportDeclaration: - return emitExportDeclaration(node); - case SyntaxKind.NamedExports: - return emitNamedExports(node); - case SyntaxKind.NamespaceExport: - return emitNamespaceExport(node); - case SyntaxKind.ExportSpecifier: - return emitExportSpecifier(node); - case SyntaxKind.MissingDeclaration: - return; - - // Module references - case SyntaxKind.ExternalModuleReference: - return emitExternalModuleReference(node); - - // JSX - case SyntaxKind.JsxElement: - return emitJsxElement(node); - case SyntaxKind.JsxSelfClosingElement: - return emitJsxSelfClosingElement(node); - case SyntaxKind.JsxFragment: - return emitJsxFragment(node); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxOpeningFragment: - return emitJsxOpeningElementOrFragment(node); - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxClosingFragment: - return emitJsxClosingElementOrFragment(node); - case SyntaxKind.JsxAttribute: - return emitJsxAttribute(node); - case SyntaxKind.JsxAttributes: - return emitJsxAttributes(node); - case SyntaxKind.JsxSpreadAttribute: - return emitJsxSpreadAttribute(node); - case SyntaxKind.JsxExpression: - return emitJsxExpression(node); - - // Clauses - case SyntaxKind.CaseClause: - return emitCaseClause(node); - case SyntaxKind.DefaultClause: - return emitDefaultClause(node); - case SyntaxKind.HeritageClause: - return emitHeritageClause(node); - case SyntaxKind.CatchClause: - return emitCatchClause(node); - - // Property assignments - case SyntaxKind.PropertyAssignment: - return emitPropertyAssignment(node); - case SyntaxKind.ShorthandPropertyAssignment: - return emitShorthandPropertyAssignment(node); - case SyntaxKind.SpreadAssignment: - return emitSpreadAssignment(node as SpreadAssignment); - - // Enum - case SyntaxKind.EnumMember: - return emitEnumMember(node); - - // Unparsed - case SyntaxKind.UnparsedPrologue: - return writeUnparsedNode(node); - case SyntaxKind.UnparsedSource: - case SyntaxKind.UnparsedPrepend: - return emitUnparsedSourceOrPrepend(node); - case SyntaxKind.UnparsedText: - case SyntaxKind.UnparsedInternalText: - return emitUnparsedTextLike(node); - case SyntaxKind.UnparsedSyntheticReference: - return emitUnparsedSyntheticReference(node); - - // Top-level nodes - case SyntaxKind.SourceFile: - return emitSourceFile(node); - case SyntaxKind.Bundle: - Debug.fail("Bundles should be printed using printBundle"); - break; - // SyntaxKind.UnparsedSource (handled above) - case SyntaxKind.InputFiles: - Debug.fail("InputFiles should not be printed"); - break; - - // JSDoc nodes (only used in codefixes currently) - case SyntaxKind.JSDocTypeExpression: - return emitJSDocTypeExpression(node as JSDocTypeExpression); - case SyntaxKind.JSDocNameReference: - return emitJSDocNameReference(node as JSDocNameReference); - case SyntaxKind.JSDocAllType: - return writePunctuation("*"); - case SyntaxKind.JSDocUnknownType: - return writePunctuation("?"); - case SyntaxKind.JSDocNullableType: - return emitJSDocNullableType(node as JSDocNullableType); - case SyntaxKind.JSDocNonNullableType: - return emitJSDocNonNullableType(node as JSDocNonNullableType); - case SyntaxKind.JSDocOptionalType: - return emitJSDocOptionalType(node as JSDocOptionalType); - case SyntaxKind.JSDocFunctionType: - return emitJSDocFunctionType(node as JSDocFunctionType); - case SyntaxKind.RestType: - case SyntaxKind.JSDocVariadicType: - return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); - // SyntaxKind.JSDocNamepathType (missing) - case SyntaxKind.JSDocComment: - return emitJSDoc(node as JSDoc); - case SyntaxKind.JSDocTypeLiteral: - return emitJSDocTypeLiteral(node as JSDocTypeLiteral); - case SyntaxKind.JSDocSignature: - return emitJSDocSignature(node as JSDocSignature); - case SyntaxKind.JSDocTag: - case SyntaxKind.JSDocClassTag: - return emitJSDocSimpleTag(node as JSDocTag); - case SyntaxKind.JSDocAugmentsTag: - case SyntaxKind.JSDocImplementsTag: - return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag); - // SyntaxKind.JSDocAuthorTag (missing) - // SyntaxKind.JSDocDeprecatedTag (missing) - // SyntaxKind.JSDocClassTag (see JSDocTag, above) - // SyntaxKind.JSDocPublicTag (missing) - // SyntaxKind.JSDocPrivateTag (missing) - // SyntaxKind.JSDocProtectedTag (missing) - // SyntaxKind.JSDocReadonlyTag (missing) - case SyntaxKind.JSDocCallbackTag: - return emitJSDocCallbackTag(node as JSDocCallbackTag); - // SyntaxKind.JSDocEnumTag (see below) - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocTypeTag: - return emitJSDocSimpleTypedTag(node as JSDocTypeTag); - case SyntaxKind.JSDocTemplateTag: - return emitJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypedefTag: - return emitJSDocTypedefTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocSeeTag: - return emitJSDocSeeTag(node as JSDocSeeTag); - // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) - - // Synthesized list - case SyntaxKind.SyntaxList: - Debug.fail("SyntaxList should not be printed"); - break; - - // Transformation nodes - case SyntaxKind.NotEmittedStatement: - break; - case SyntaxKind.PartiallyEmittedExpression: - return emitPartiallyEmittedExpression(node); - case SyntaxKind.CommaListExpression: - return emitCommaList(node); - case SyntaxKind.MergeDeclarationMarker: - case SyntaxKind.EndOfDeclarationMarker: - break; - case SyntaxKind.SyntheticReferenceExpression: - Debug.fail("SyntheticReferenceExpression should not be printed"); - } - if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); - if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation); + function shouldEmitWithNotification(node: Node) { + return onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node)); } - function emitMappedTypeParameter(node: TypeParameterDeclaration): void { - emit(node.name); - writeSpace(); - writeKeyword("in"); - writeSpace(); - emit(node.constraint); + function shouldEmitWithSubstitution() { + return substituteNode !== noEmitSubstitution; } - function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { - let result: string[] | undefined; - if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { - return undefined; + function pipelineEmit(emitHint: EmitHint, node: Node, emitCallback: (emitHint: EmitHint, node: Node) => void, parenthesizerRule?: (node: Node) => Node) { + const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); + try { + currentParenthesizerRule = parenthesizerRule; + emitCallback = buildEmitPipeline(node, emitCallback); + onBeforeEmitNode?.(node); + emitCallback(emitHint, node); + onAfterEmitNode?.(node); } - const bundledHelpers = new Map(); - for (const sourceFile of bundle.sourceFiles) { - const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; - const helpers = getSortedEmitHelpers(sourceFile); - if (!helpers) continue; - for (const helper of helpers) { - if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { - bundledHelpers.set(helper.name, true); - (result || (result = [])).push(helper.name); - } - } + finally { + recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + currentParenthesizerRule = undefined; } - - return result; } - function emitHelpers(node: Node) { - let helpersEmitted = false; - const bundle = node.kind === SyntaxKind.Bundle ? node : undefined; - if (bundle && moduleKind === ModuleKind.None) { - return; + function addContextToPipeline(emitCallback: (emitHint: EmitHint, node: Node) => void) { + contextCallbackCache ||= new Map(); + let wrapper = contextCallbackCache.get(emitCallback); + if (!wrapper) { + wrapper = (emitHint: EmitHint, node: Node) => { emitWithContext(emitHint, node, emitCallback); }; + contextCallbackCache.set(emitCallback, wrapper); } - const numPrepends = bundle ? bundle.prepends.length : 0; - const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; - for (let i = 0; i < numNodes; i++) { - const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; - const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; - const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); - const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; - const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); - if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - // Skip the helper if it can be skipped and the noEmitHelpers compiler - // option is set, or if it can be imported and the importHelpers compiler - // option is set. - if (shouldSkip) continue; - - // Skip the helper if it can be bundled but hasn't already been emitted and we - // are emitting a bundled module. - if (shouldBundle) { - if (bundledHelpers.get(helper.name)) { - continue; - } + return wrapper; + } - bundledHelpers.set(helper.name, true); - } - } - else if (bundle) { - // Skip the helper if it is scoped and we are emitting bundled helpers - continue; - } - const pos = getTextPosWithWriteLine(); - if (typeof helper.text === "string") { - writeLines(helper.text); - } - else { - writeLines(helper.text(makeFileLevelOptimisticUniqueName)); - } - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); - helpersEmitted = true; + function addSubstitutionToPipeline(emitCallback: (emitHint: EmitHint, node: Node) => void) { + substitutionCallbackCache ||= new Map(); + let wrapper = substitutionCallbackCache.get(emitCallback); + if (!wrapper) { + wrapper = (emitHint: EmitHint, node: Node) => { + let substitute = substituteNode(emitHint, node) || node; + if (substitute !== node && currentParenthesizerRule) { + substitute = currentParenthesizerRule(substitute); } - } + emitCallback(emitHint, substitute); + }; + substitutionCallbackCache.set(emitCallback, wrapper); } - - return helpersEmitted; - } - - function getSortedEmitHelpers(node: Node) { - const helpers = getEmitHelpers(node); - return helpers && stableSort(helpers, compareEmitHelpers); + return wrapper; } - // - // Literals/Pseudo-literals - // - - // SyntaxKind.NumericLiteral - // SyntaxKind.BigIntLiteral - function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { - emitLiteral(node, /*jsxAttributeEscape*/ false); + function addNotificationToPipeline(emitCallback: (emitHint: EmitHint, node: Node) => void) { + notificationCallbackCache ||= new Map(); + let wrapper = notificationCallbackCache.get(emitCallback); + if (!wrapper) { + wrapper = (emitHint: EmitHint, node: Node) => { onEmitNode(emitHint, node, emitCallback); }; + notificationCallbackCache.set(emitCallback, wrapper); + } + return wrapper; } - // SyntaxKind.StringLiteral - // SyntaxKind.RegularExpressionLiteral - // SyntaxKind.NoSubstitutionTemplateLiteral - // SyntaxKind.TemplateHead - // SyntaxKind.TemplateMiddle - // SyntaxKind.TemplateTail - function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { - const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); - if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) - && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { - writeLiteral(text); + function buildEmitPipeline(node: Node, emitCallback: (emitHint: EmitHint, node: Node) => void) { + emitCallback = addContextToPipeline(emitCallback); + if (shouldEmitWithSubstitution()) { + emitCallback = addSubstitutionToPipeline(emitCallback); } - else { - // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals - writeStringLiteral(text); + if (shouldEmitWithNotification(node)) { + emitCallback = addNotificationToPipeline(emitCallback); } + return emitCallback; } - function emitStringLiteralWithJsxAttributeEscape(node: StringLiteral) { - emitLiteral(node, /*jsxAttributeEscape*/ true); + function emitWithContext(emitHint: EmitHint, node: T, emitCallback: (emitHint: EmitHint, node: T) => void) { + currentParenthesizerRule = undefined; + const savedPreserveSourceNewlines = preserveSourceNewlines; + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + const emitComments = shouldEmitComments(node); + const emitSourceMaps = shouldEmitSourceMaps(node); + beforeEmitWithContext(node, emitComments, emitSourceMaps); + emitCallback(emitHint, node); + afterEmitWithContext(node, emitComments, emitSourceMaps, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd, savedPreserveSourceNewlines); + currentParenthesizerRule = undefined; } - // SyntaxKind.UnparsedSource - // SyntaxKind.UnparsedPrepend - function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { - for (const text of unparsed.texts) { - writeLine(); - emit(text); - } - } + function emitWorker(emitHint: EmitHint, node: Node): void { + if (emitHint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); + if (emitHint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); + if (emitHint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true); + if (emitHint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(emitHint, cast(node, isTypeParameterDeclaration)); + if (emitHint === EmitHint.EmbeddedStatement) return emitEmbeddedEmptyStatement(emitHint, cast(node, isEmptyStatement)); + if (emitHint === EmitHint.Unspecified) { + switch (node.kind) { + // Pseudo-literals + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + return emitLiteral(node, /*jsxAttributeEscape*/ false); - // SyntaxKind.UnparsedPrologue - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - // SyntaxKind.UnparsedSyntheticReference - function writeUnparsedNode(unparsed: UnparsedNode) { - writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); - } + // Identifiers and PrivateIdentifiers + case SyntaxKind.Identifier: + return emitIdentifier(node); + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - function emitUnparsedTextLike(unparsed: UnparsedTextLike) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - updateOrPushBundleFileTextLike( - pos, - writer.getTextPos(), - unparsed.kind === SyntaxKind.UnparsedText ? - BundleFileSectionKind.Text : - BundleFileSectionKind.Internal - ); - } - } + // Names + case SyntaxKind.QualifiedName: + return emitQualifiedName(node); + case SyntaxKind.ComputedPropertyName: + return emitComputedPropertyName(node); - // SyntaxKind.UnparsedSyntheticReference - function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - const section = clone(unparsed.section); - section.pos = pos; - section.end = writer.getTextPos(); - bundleFileInfo.sections.push(section); - } - } + // Signature elements + case SyntaxKind.TypeParameter: + return emitTypeParameter(node); + case SyntaxKind.Parameter: + return emitParameter(node); + case SyntaxKind.Decorator: + return emitDecorator(node); - // - // Identifiers - // + // Type members + case SyntaxKind.PropertySignature: + return emitPropertySignature(node); + case SyntaxKind.PropertyDeclaration: + return emitPropertyDeclaration(node); + case SyntaxKind.MethodSignature: + return emitMethodSignature(node); + case SyntaxKind.MethodDeclaration: + return emitMethodDeclaration(node); + case SyntaxKind.Constructor: + return emitConstructor(node); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return emitAccessorDeclaration(node); + case SyntaxKind.CallSignature: + return emitCallSignature(node); + case SyntaxKind.ConstructSignature: + return emitConstructSignature(node); + case SyntaxKind.IndexSignature: + return emitIndexSignature(node); - function emitIdentifier(node: Identifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); - emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments - } - - // - // Names - // - - function emitPrivateIdentifier(node: PrivateIdentifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); - } - - - function emitQualifiedName(node: QualifiedName) { - emitEntityName(node.left); - writePunctuation("."); - emit(node.right); - } - - function emitEntityName(node: EntityName) { - if (node.kind === SyntaxKind.Identifier) { - emit(node); - } - else { - emit(node); - } - } - - function emitComputedPropertyName(node: ComputedPropertyName) { - writePunctuation("["); - emit(node.expression); - writePunctuation("]"); - } - - // - // Signature elements - // - - function emitTypeParameter(node: TypeParameterDeclaration) { - emit(node.name); - if (node.constraint) { - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.constraint); - } - if (node.default) { - writeSpace(); - writeOperator("="); - writeSpace(); - emit(node.default); - } - } - - function emitParameter(node: ParameterDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.dotDotDotToken); - emitNodeWithWriter(node.name, writeParameter); - emit(node.questionToken); - if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { - emit(node.type); - } - else { - emitTypeAnnotation(node.type); - } - // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.decorators ? node.decorators.end : node.pos, node); - } - - function emitDecorator(decorator: Decorator) { - writePunctuation("@"); - emit(decorator.expression); - } - - // - // Type members - // - - function emitPropertySignature(node: PropertySignature) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitNodeWithWriter(node.name, writeProperty); - emit(node.questionToken); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - } - - function emitPropertyDeclaration(node: PropertyDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); - writeTrailingSemicolon(); - } - - function emitMethodSignature(node: MethodSignature) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } - - function emitMethodDeclaration(node: MethodDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emit(node.asteriskToken); - emit(node.name); - emit(node.questionToken); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitConstructor(node: ConstructorDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("constructor"); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitAccessorDeclaration(node: AccessorDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); - writeSpace(); - emit(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitCallSignature(node: CallSignatureDeclaration) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } - - function emitConstructSignature(node: ConstructSignatureDeclaration) { - pushNameGenerationScope(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("new"); - writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } - - function emitIndexSignature(node: IndexSignatureDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitParametersForIndexSignature(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - } - - function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) { - emit(node.type); - emit(node.literal); - } - - function emitSemicolonClassElement() { - writeTrailingSemicolon(); - } - - // - // Types - // - - function emitTypePredicate(node: TypePredicateNode) { - if (node.assertsModifier) { - emit(node.assertsModifier); - writeSpace(); - } - emit(node.parameterName); - if (node.type) { - writeSpace(); - writeKeyword("is"); - writeSpace(); - emit(node.type); - } - } - - function emitTypeReference(node: TypeReferenceNode) { - emit(node.typeName); - emitTypeArguments(node, node.typeArguments); - } - - function emitFunctionType(node: FunctionTypeNode) { - pushNameGenerationScope(node); - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); - writeSpace(); - writePunctuation("=>"); - writeSpace(); - emit(node.type); - popNameGenerationScope(node); - } - - function emitJSDocFunctionType(node: JSDocFunctionType) { - writeKeyword("function"); - emitParameters(node, node.parameters); - writePunctuation(":"); - emit(node.type); - } - - - function emitJSDocNullableType(node: JSDocNullableType) { - writePunctuation("?"); - emit(node.type); - } - - function emitJSDocNonNullableType(node: JSDocNonNullableType) { - writePunctuation("!"); - emit(node.type); - } - - function emitJSDocOptionalType(node: JSDocOptionalType) { - emit(node.type); - writePunctuation("="); - } - - function emitConstructorType(node: ConstructorTypeNode) { - pushNameGenerationScope(node); - emitModifiers(node, node.modifiers); - writeKeyword("new"); - writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - writeSpace(); - writePunctuation("=>"); - writeSpace(); - emit(node.type); - popNameGenerationScope(node); - } - - function emitTypeQuery(node: TypeQueryNode) { - writeKeyword("typeof"); - writeSpace(); - emit(node.exprName); - } - - function emitTypeLiteral(node: TypeLiteralNode) { - writePunctuation("{"); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; - emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); - writePunctuation("}"); - } - - function emitArrayType(node: ArrayTypeNode) { - emit(node.elementType); - writePunctuation("["); - writePunctuation("]"); - } - - function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { - writePunctuation("..."); - emit(node.type); - } - - function emitTupleType(node: TupleTypeNode) { - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; - emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); - } - - function emitNamedTupleMember(node: NamedTupleMember) { - emit(node.dotDotDotToken); - emit(node.name); - emit(node.questionToken); - emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); - writeSpace(); - emit(node.type); - } - - function emitOptionalType(node: OptionalTypeNode) { - emit(node.type); - writePunctuation("?"); - } - - function emitUnionType(node: UnionTypeNode) { - emitList(node, node.types, ListFormat.UnionTypeConstituents); - } - - function emitIntersectionType(node: IntersectionTypeNode) { - emitList(node, node.types, ListFormat.IntersectionTypeConstituents); - } - - function emitConditionalType(node: ConditionalTypeNode) { - emit(node.checkType); - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.extendsType); - writeSpace(); - writePunctuation("?"); - writeSpace(); - emit(node.trueType); - writeSpace(); - writePunctuation(":"); - writeSpace(); - emit(node.falseType); - } - - function emitInferType(node: InferTypeNode) { - writeKeyword("infer"); - writeSpace(); - emit(node.typeParameter); - } - - function emitParenthesizedType(node: ParenthesizedTypeNode) { - writePunctuation("("); - emit(node.type); - writePunctuation(")"); - } - - function emitThisType() { - writeKeyword("this"); - } - - function emitTypeOperator(node: TypeOperatorNode) { - writeTokenText(node.operator, writeKeyword); - writeSpace(); - emit(node.type); - } - - function emitIndexedAccessType(node: IndexedAccessTypeNode) { - emit(node.objectType); - writePunctuation("["); - emit(node.indexType); - writePunctuation("]"); - } - - function emitMappedType(node: MappedTypeNode) { - const emitFlags = getEmitFlags(node); - writePunctuation("{"); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - increaseIndent(); - } - if (node.readonlyToken) { - emit(node.readonlyToken); - if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - writeKeyword("readonly"); - } - writeSpace(); - } - writePunctuation("["); - - emitWithContext(node.typeParameter, emitMappedTypeParameter); - if (node.nameType) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.nameType); - } - - writePunctuation("]"); - if (node.questionToken) { - emit(node.questionToken); - if (node.questionToken.kind !== SyntaxKind.QuestionToken) { - writePunctuation("?"); - } - } - writePunctuation(":"); - writeSpace(); - emit(node.type); - writeTrailingSemicolon(); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - decreaseIndent(); - } - writePunctuation("}"); - } - - function emitLiteralType(node: LiteralTypeNode) { - emit(node.literal); - } - - function emitTemplateType(node: TemplateLiteralTypeNode) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); - } - - function emitImportTypeNode(node: ImportTypeNode) { - if (node.isTypeOf) { - writeKeyword("typeof"); - writeSpace(); - } - writeKeyword("import"); - writePunctuation("("); - emit(node.argument); - writePunctuation(")"); - if (node.qualifier) { - writePunctuation("."); - emit(node.qualifier); - } - emitTypeArguments(node, node.typeArguments); - } - - // - // Binding patterns - // - - function emitObjectBindingPattern(node: ObjectBindingPattern) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); - writePunctuation("}"); - } - - function emitArrayBindingPattern(node: ArrayBindingPattern) { - writePunctuation("["); - emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); - writePunctuation("]"); - } - - function emitBindingElement(node: BindingElement) { - emit(node.dotDotDotToken); - if (node.propertyName) { - emit(node.propertyName); - writePunctuation(":"); - writeSpace(); - } - emit(node.name); - emitInitializer(node.initializer, node.name.end, node); - } - - // - // Expressions - // - - function emitArrayLiteralExpression(node: ArrayLiteralExpression) { - const elements = node.elements; - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - emitList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine); - } - - function emitObjectLiteralExpression(node: ObjectLiteralExpression) { - forEach(node.properties, generateMemberNames); - - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } - - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile!) ? ListFormat.AllowTrailingComma : ListFormat.None; - emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); - - if (indentedFlag) { - decreaseIndent(); - } - } - - function emitPropertyAccessExpression(node: PropertyAccessExpression) { - emit(node.expression); - const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos); - const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token); - const linesAfterDot = getLinesBetweenNodes(node, token, node.name); - - writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false); - - const shouldEmitDotDot = - token.kind !== SyntaxKind.QuestionDotToken && - mayNeedDotDotForPropertyAccess(node.expression) && - !writer.hasTrailingComment() && - !writer.hasTrailingWhitespace(); - - if (shouldEmitDotDot) { - writePunctuation("."); - } - - if (node.questionDotToken) { - emit(token); - } - else { - emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); - } - writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false); - emit(node.name); - decreaseIndentIf(linesBeforeDot, linesAfterDot); - } - - // 1..toString is a valid property access, emit a dot after the literal - // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal - function mayNeedDotDotForPropertyAccess(expression: Expression) { - expression = skipPartiallyEmittedExpressions(expression); - if (isNumericLiteral(expression)) { - // check if numeric literal is a decimal literal that was originally written with a dot - const text = getLiteralTextOfNode(expression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); - // If he number will be printed verbatim and it doesn't already contain a dot, add one - // if the expression doesn't have any comments that will be emitted. - return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); - } - else if (isAccessExpression(expression)) { - // check if constant enum value is integer - const constantValue = getConstantValue(expression); - // isFinite handles cases when constantValue is undefined - return typeof constantValue === "number" && isFinite(constantValue) - && Math.floor(constantValue) === constantValue; - } - } - - function emitElementAccessExpression(node: ElementAccessExpression) { - emit(node.expression); - emit(node.questionDotToken); - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); - emit(node.argumentExpression); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); - } - - function emitCallExpression(node: CallExpression) { - emit(node.expression); - emit(node.questionDotToken); - emitTypeArguments(node, node.typeArguments); - emitList(node, node.arguments, ListFormat.CallExpressionArguments); - } - - function emitNewExpression(node: NewExpression) { - emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.expression); - emitTypeArguments(node, node.typeArguments); - emitList(node, node.arguments, ListFormat.NewExpressionArguments); - } - - function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { - emit(node.tag); - emitTypeArguments(node, node.typeArguments); - writeSpace(); - emit(node.template); - } - - function emitTypeAssertionExpression(node: TypeAssertion) { - writePunctuation("<"); - emit(node.type); - writePunctuation(">"); - emit(node.expression); - } - - function emitParenthesizedExpression(node: ParenthesizedExpression) { - const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); - const indented = writeLineSeparatorsAndIndentBefore(node.expression, node); - emit(node.expression); - writeLineSeparatorsAfter(node.expression, node); - decreaseIndentIf(indented); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); - } - - function emitFunctionExpression(node: FunctionExpression) { - generateNameIfNeeded(node.name); - emitFunctionDeclarationOrExpression(node); - } - - function emitArrowFunction(node: ArrowFunction) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - emitSignatureAndBody(node, emitArrowFunctionHead); - } - - function emitArrowFunctionHead(node: ArrowFunction) { - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); - emitTypeAnnotation(node.type); - writeSpace(); - emit(node.equalsGreaterThanToken); - } - - function emitDeleteExpression(node: DeleteExpression) { - emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.expression); - } - - function emitTypeOfExpression(node: TypeOfExpression) { - emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.expression); - } - - function emitVoidExpression(node: VoidExpression) { - emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.expression); - } - - function emitAwaitExpression(node: AwaitExpression) { - emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.expression); - } - - function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { - writeTokenText(node.operator, writeOperator); - if (shouldEmitWhitespaceBeforeOperand(node)) { - writeSpace(); - } - emit(node.operand); - } - - function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { - // In some cases, we need to emit a space between the operator and the operand. One obvious case - // is when the operator is an identifier, like delete or typeof. We also need to do this for plus - // and minus expressions in certain cases. Specifically, consider the following two cases (parens - // are just for clarity of exposition, and not part of the source code): - // - // (+(+1)) - // (+(++1)) - // - // We need to emit a space in both cases. In the first case, the absence of a space will make - // the resulting expression a prefix increment operation. And in the second, it will make the resulting - // expression a prefix increment whose operand is a plus expression - (++(+x)) - // The same is true of minus of course. - const operand = node.operand; - return operand.kind === SyntaxKind.PrefixUnaryExpression - && ((node.operator === SyntaxKind.PlusToken && ((operand).operator === SyntaxKind.PlusToken || (operand).operator === SyntaxKind.PlusPlusToken)) - || (node.operator === SyntaxKind.MinusToken && ((operand).operator === SyntaxKind.MinusToken || (operand).operator === SyntaxKind.MinusMinusToken))); - } - - function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { - emit(node.operand); - writeTokenText(node.operator, writeOperator); - } - - function createEmitBinaryExpression() { - interface WorkArea { - stackIndex: number; - preserveSourceNewlinesStack: (boolean | undefined)[]; - containerPosStack: number[]; - containerEndStack: number[]; - declarationListContainerEndStack: number[]; - shouldEmitCommentsStack: boolean[]; - shouldEmitSourceMapsStack: boolean[]; - } - - return createBinaryExpressionTrampoline(onEnter, maybeEmitExpression, onOperator, maybeEmitExpression, onExit, /*foldState*/ undefined); - - function onEnter(node: BinaryExpression, state: WorkArea | undefined) { - if (state) { - state.stackIndex++; - state.preserveSourceNewlinesStack[state.stackIndex] = preserveSourceNewlines; - state.containerPosStack[state.stackIndex] = containerPos; - state.containerEndStack[state.stackIndex] = containerEnd; - state.declarationListContainerEndStack[state.stackIndex] = declarationListContainerEnd; - const emitComments = state.shouldEmitCommentsStack[state.stackIndex] = shouldEmitComments(node); - const emitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex] = shouldEmitSourceMaps(node); - beforeEmitWithContext(node, emitComments, emitSourceMaps); - } - else { - state = { - stackIndex: 0, - preserveSourceNewlinesStack: [undefined], - containerPosStack: [-1], - containerEndStack: [-1], - declarationListContainerEndStack: [-1], - shouldEmitCommentsStack: [false], - shouldEmitSourceMapsStack: [false], - }; - } - return state; - } - - function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) { - const isCommaOperator = operatorToken.kind !== SyntaxKind.CommaToken; - const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken); - const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right); - writeLinesAndIndent(linesBeforeOperator, isCommaOperator); - emitLeadingCommentsOfPosition(operatorToken.pos); - writeTokenNode(operatorToken, operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); - emitTrailingCommentsOfPosition(operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts - writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true); - } - - function onExit(node: BinaryExpression, state: WorkArea) { - const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken); - const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right); - decreaseIndentIf(linesBeforeOperator, linesAfterOperator); - if (state.stackIndex > 0) { - const savedPreserveSourceNewlines = state.preserveSourceNewlinesStack[state.stackIndex]; - const savedContainerPos = state.containerPosStack[state.stackIndex]; - const savedContainerEnd = state.containerEndStack[state.stackIndex]; - const savedDeclarationListContainerEnd = state.declarationListContainerEndStack[state.stackIndex]; - const shouldEmitComments = state.shouldEmitCommentsStack[state.stackIndex]; - const shouldEmitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex]; - afterEmitWithContext(node, shouldEmitComments, shouldEmitSourceMaps, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd, savedPreserveSourceNewlines); - state.stackIndex--; - } - } - - function maybeEmitExpression(next: Expression) { - // Push a new frame for binary expressions, otherwise emit all other expressions. - if (isBinaryExpression(next)) { - return next; - } - - emit(next); - } - } - - function emitConditionalExpression(node: ConditionalExpression) { - const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken); - const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue); - const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken); - const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse); - - emit(node.condition); - writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); - emit(node.questionToken); - writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true); - emit(node.whenTrue); - decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion); - - writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true); - emit(node.colonToken); - writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true); - emit(node.whenFalse); - decreaseIndentIf(linesBeforeColon, linesAfterColon); - } - - function emitTemplateExpression(node: TemplateExpression) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); - } - - function emitYieldExpression(node: YieldExpression) { - emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); - emit(node.asteriskToken); - emitExpressionWithLeadingSpace(node.expression); - } - - function emitSpreadExpression(node: SpreadElement) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emit(node.expression); - } - - function emitClassExpression(node: ClassExpression) { - generateNameIfNeeded(node.name); - emitClassDeclarationOrExpression(node); - } - - function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - emit(node.expression); - emitTypeArguments(node, node.typeArguments); - } - - function emitAsExpression(node: AsExpression) { - emit(node.expression); - if (node.type) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.type); - } - } - - function emitNonNullExpression(node: NonNullExpression) { - emit(node.expression); - writeOperator("!"); - } - - function emitMetaProperty(node: MetaProperty) { - writeToken(node.keywordToken, node.pos, writePunctuation); - writePunctuation("."); - emit(node.name); - } - - // - // Misc - // - - function emitTemplateSpan(node: TemplateSpan) { - emit(node.expression); - emit(node.literal); - } - - // - // Statements - // - - function emitBlock(node: Block) { - emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); - } - - function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); - const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; - emitList(node, node.statements, format); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); - } - - function emitVariableStatement(node: VariableStatement) { - emitModifiers(node, node.modifiers); - emit(node.declarationList); - writeTrailingSemicolon(); - } - - function emitEmptyStatement(isEmbeddedStatement: boolean) { - // While most trailing semicolons are possibly insignificant, an embedded "empty" - // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. - if (isEmbeddedStatement) { - writePunctuation(";"); - } - else { - writeTrailingSemicolon(); - } - } - - function emitEmbeddedEmptyStatement(_node: EmptyStatement) { - emitEmptyStatement(/*isEmbeddedStatement*/ true); - } - - function emitExpressionStatement(node: ExpressionStatement) { - emit(node.expression); - // Emit semicolon in non json files - // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) - if (!isJsonSourceFile(currentSourceFile!) || nodeIsSynthesized(node.expression)) { - writeTrailingSemicolon(); - } - } - - function emitIfStatement(node: IfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.thenStatement); - if (node.elseStatement) { - writeLineOrSpace(node, node.thenStatement, node.elseStatement); - emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); - if (node.elseStatement.kind === SyntaxKind.IfStatement) { - writeSpace(); - emit(node.elseStatement); - } - else { - emitEmbeddedStatement(node, node.elseStatement); - } - } - } - - function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { - const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - } - - function emitDoStatement(node: DoStatement) { - emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); - emitEmbeddedStatement(node, node.statement); - if (isBlock(node.statement) && !preserveSourceNewlines) { - writeSpace(); - } - else { - writeLineOrSpace(node, node.statement, node.expression); - } - - emitWhileClause(node, node.statement.end); - writeTrailingSemicolon(); - } - - function emitWhileStatement(node: WhileStatement) { - emitWhileClause(node, node.pos); - emitEmbeddedStatement(node, node.statement); - } - - function emitForStatement(node: ForStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); - emitForBinding(node.initializer); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.condition); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.incrementor); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitForInStatement(node: ForInStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); - writeSpace(); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitForOfStatement(node: ForOfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitWithTrailingSpace(node.awaitModifier); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); - writeSpace(); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitForBinding(node: VariableDeclarationList | Expression | undefined) { - if (node !== undefined) { - if (node.kind === SyntaxKind.VariableDeclarationList) { - emit(node); - } - else { - emit(node); - } - } - } - - function emitContinueStatement(node: ContinueStatement) { - emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); - writeTrailingSemicolon(); - } - - function emitBreakStatement(node: BreakStatement) { - emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); - writeTrailingSemicolon(); - } - - function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { - const node = getParseTreeNode(contextNode); - const isSimilarNode = node && node.kind === contextNode.kind; - const startPos = pos; - if (isSimilarNode && currentSourceFile) { - pos = skipTrivia(currentSourceFile.text, pos); - } - if (isSimilarNode && contextNode.pos !== startPos) { - const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); - if (needsIndent) { - increaseIndent(); - } - emitLeadingCommentsOfPosition(startPos); - if (needsIndent) { - decreaseIndent(); - } - } - pos = writeTokenText(token, writer, pos); - if (isSimilarNode && contextNode.end !== pos) { - const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression; - emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext); - } - return pos; - } - - function emitReturnStatement(node: ReturnStatement) { - emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); - emitExpressionWithLeadingSpace(node.expression); - writeTrailingSemicolon(); - } - - function emitWithStatement(node: WithStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitSwitchStatement(node: SwitchStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - writeSpace(); - emit(node.caseBlock); - } - - function emitLabeledStatement(node: LabeledStatement) { - emit(node.label); - emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); - writeSpace(); - emit(node.statement); - } - - function emitThrowStatement(node: ThrowStatement) { - emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); - emitExpressionWithLeadingSpace(node.expression); - writeTrailingSemicolon(); - } - - function emitTryStatement(node: TryStatement) { - emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.tryBlock); - if (node.catchClause) { - writeLineOrSpace(node, node.tryBlock, node.catchClause); - emit(node.catchClause); - } - if (node.finallyBlock) { - writeLineOrSpace(node, node.catchClause || node.tryBlock, node.finallyBlock); - emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); - writeSpace(); - emit(node.finallyBlock); - } - } - - function emitDebuggerStatement(node: DebuggerStatement) { - writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); - writeTrailingSemicolon(); - } - - // - // Declarations - // - - function emitVariableDeclaration(node: VariableDeclaration) { - emit(node.name); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node); - } - - function emitVariableDeclarationList(node: VariableDeclarationList) { - writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); - writeSpace(); - emitList(node, node.declarations, ListFormat.VariableDeclarationList); - } - - function emitFunctionDeclaration(node: FunctionDeclaration) { - emitFunctionDeclarationOrExpression(node); - } - - function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("function"); - emit(node.asteriskToken); - writeSpace(); - emit(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } - - function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { - const body = node.body; - if (body) { - if (isBlock(body)) { - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } - - pushNameGenerationScope(node); - forEach(node.parameters, generateNames); - generateNames(node.body); - - emitSignatureHead(node); - emitBlockFunctionBody(body); - popNameGenerationScope(node); - - if (indentedFlag) { - decreaseIndent(); - } - } - else { - emitSignatureHead(node); - writeSpace(); - emit(body); - } - } - else { - emitSignatureHead(node); - writeTrailingSemicolon(); - } - - } - - function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - } - - function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { - // We must emit a function body as a single-line body in the following case: - // * The body has NodeEmitFlags.SingleLine specified. - - // We must emit a function body as a multi-line body in the following cases: - // * The body is explicitly marked as multi-line. - // * A non-synthesized body's start and end position are on different lines. - // * Any statement in the body starts on a new line. - - if (getEmitFlags(body) & EmitFlags.SingleLine) { - return true; - } - - if (body.multiLine) { - return false; - } - - if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile!)) { - return false; - } - - if (getLeadingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines) - || getClosingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines)) { - return false; - } - - let previousStatement: Statement | undefined; - for (const statement of body.statements) { - if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) { - return false; - } - - previousStatement = statement; - } - - return true; - } - - function emitBlockFunctionBody(body: Block) { - onBeforeEmitNode?.(body); - writeSpace(); - writePunctuation("{"); - increaseIndent(); + // Types + case SyntaxKind.TypePredicate: + return emitTypePredicate(node); + case SyntaxKind.TypeReference: + return emitTypeReference(node); + case SyntaxKind.FunctionType: + return emitFunctionType(node); + case SyntaxKind.ConstructorType: + return emitConstructorType(node); + case SyntaxKind.TypeQuery: + return emitTypeQuery(node); + case SyntaxKind.TypeLiteral: + return emitTypeLiteral(node); + case SyntaxKind.ArrayType: + return emitArrayType(node); + case SyntaxKind.TupleType: + return emitTupleType(node); + case SyntaxKind.OptionalType: + return emitOptionalType(node); + // SyntaxKind.RestType is handled below + case SyntaxKind.UnionType: + return emitUnionType(node); + case SyntaxKind.IntersectionType: + return emitIntersectionType(node); + case SyntaxKind.ConditionalType: + return emitConditionalType(node); + case SyntaxKind.InferType: + return emitInferType(node); + case SyntaxKind.ParenthesizedType: + return emitParenthesizedType(node); + case SyntaxKind.ThisType: + return emitThisType(); + case SyntaxKind.TypeOperator: + return emitTypeOperator(node); + case SyntaxKind.IndexedAccessType: + return emitIndexedAccessType(node); + case SyntaxKind.MappedType: + return emitMappedType(node); + case SyntaxKind.LiteralType: + return emitLiteralType(node); + case SyntaxKind.NamedTupleMember: + return emitNamedTupleMember(node as NamedTupleMember); + case SyntaxKind.TemplateLiteralType: + return emitTemplateType(node); + case SyntaxKind.TemplateLiteralTypeSpan: + return emitTemplateTypeSpan(node); + case SyntaxKind.ImportType: + return emitImportTypeNode(node); - const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) - ? emitBlockFunctionBodyOnSingleLine - : emitBlockFunctionBodyWorker; + // Binding patterns + case SyntaxKind.ObjectBindingPattern: + return emitObjectBindingPattern(node); + case SyntaxKind.ArrayBindingPattern: + return emitArrayBindingPattern(node); + case SyntaxKind.BindingElement: + return emitBindingElement(node); - if (emitBodyWithDetachedComments) { - emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); - } - else { - emitBlockFunctionBody(body); - } + // Misc + case SyntaxKind.TemplateSpan: + return emitTemplateSpan(node); + case SyntaxKind.SemicolonClassElement: + return emitSemicolonClassElement(); - decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); - onAfterEmitNode?.(body); - } + // Expressions (but not really an expression) + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments(node); - function emitBlockFunctionBodyOnSingleLine(body: Block) { - emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); - } + // Element + case SyntaxKind.Block: + return emitBlock(node); + case SyntaxKind.EmptyStatement: + return emitEmptyStatement(/*isEmbeddedStatement*/ false); + case SyntaxKind.VariableStatement: + return emitVariableStatement(node); + case SyntaxKind.ExpressionStatement: + return emitExpressionStatement(node); + case SyntaxKind.IfStatement: + return emitIfStatement(node); + case SyntaxKind.DoStatement: + return emitDoStatement(node); + case SyntaxKind.WhileStatement: + return emitWhileStatement(node); + case SyntaxKind.ForStatement: + return emitForStatement(node); + case SyntaxKind.ForInStatement: + return emitForInStatement(node); + case SyntaxKind.ForOfStatement: + return emitForOfStatement(node); + case SyntaxKind.ContinueStatement: + return emitContinueStatement(node); + case SyntaxKind.BreakStatement: + return emitBreakStatement(node); + case SyntaxKind.ReturnStatement: + return emitReturnStatement(node); + case SyntaxKind.WithStatement: + return emitWithStatement(node); + case SyntaxKind.SwitchStatement: + return emitSwitchStatement(node); + case SyntaxKind.LabeledStatement: + return emitLabeledStatement(node); + case SyntaxKind.ThrowStatement: + return emitThrowStatement(node); + case SyntaxKind.TryStatement: + return emitTryStatement(node); + case SyntaxKind.DebuggerStatement: + return emitDebuggerStatement(node); - function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { - // Emit all the prologue directives (like "use strict"). - const statementOffset = emitPrologueDirectives(body.statements); - const pos = writer.getTextPos(); - emitHelpers(body); - if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { - decreaseIndent(); - emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); - increaseIndent(); - } - else { - emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); - } - } + // Declarations + case SyntaxKind.VariableDeclaration: + return emitVariableDeclaration(node); + case SyntaxKind.VariableDeclarationList: + return emitVariableDeclarationList(node); + case SyntaxKind.FunctionDeclaration: + return emitFunctionDeclaration(node); + case SyntaxKind.ClassDeclaration: + return emitClassDeclaration(node); + case SyntaxKind.InterfaceDeclaration: + return emitInterfaceDeclaration(node); + case SyntaxKind.TypeAliasDeclaration: + return emitTypeAliasDeclaration(node); + case SyntaxKind.EnumDeclaration: + return emitEnumDeclaration(node); + case SyntaxKind.ModuleDeclaration: + return emitModuleDeclaration(node); + case SyntaxKind.ModuleBlock: + return emitModuleBlock(node); + case SyntaxKind.CaseBlock: + return emitCaseBlock(node); + case SyntaxKind.NamespaceExportDeclaration: + return emitNamespaceExportDeclaration(node); + case SyntaxKind.ImportEqualsDeclaration: + return emitImportEqualsDeclaration(node); + case SyntaxKind.ImportDeclaration: + return emitImportDeclaration(node); + case SyntaxKind.ImportClause: + return emitImportClause(node); + case SyntaxKind.NamespaceImport: + return emitNamespaceImport(node); + case SyntaxKind.NamedImports: + return emitNamedImports(node); + case SyntaxKind.ImportSpecifier: + return emitImportSpecifier(node); + case SyntaxKind.ExportAssignment: + return emitExportAssignment(node); + case SyntaxKind.ExportDeclaration: + return emitExportDeclaration(node); + case SyntaxKind.NamedExports: + return emitNamedExports(node); + case SyntaxKind.NamespaceExport: + return emitNamespaceExport(node); + case SyntaxKind.ExportSpecifier: + return emitExportSpecifier(node); + case SyntaxKind.MissingDeclaration: + return; - function emitClassDeclaration(node: ClassDeclaration) { - emitClassDeclarationOrExpression(node); - } + // Module references + case SyntaxKind.ExternalModuleReference: + return emitExternalModuleReference(node); - function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { - forEach(node.members, generateMemberNames); + // JSX (non-expression) + case SyntaxKind.JsxText: + return emitJsxText(node); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + return emitJsxOpeningElementOrFragment(node); + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxClosingFragment: + return emitJsxClosingElementOrFragment(node); + case SyntaxKind.JsxAttribute: + return emitJsxAttribute(node); + case SyntaxKind.JsxAttributes: + return emitJsxAttributes(node); + case SyntaxKind.JsxSpreadAttribute: + return emitJsxSpreadAttribute(node); + case SyntaxKind.JsxExpression: + return emitJsxExpression(node); - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("class"); - if (node.name) { - writeSpace(); - emit(node.name); - } + // Clauses + case SyntaxKind.CaseClause: + return emitCaseClause(node); + case SyntaxKind.DefaultClause: + return emitDefaultClause(node); + case SyntaxKind.HeritageClause: + return emitHeritageClause(node); + case SyntaxKind.CatchClause: + return emitCatchClause(node); - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + // Property assignments + case SyntaxKind.PropertyAssignment: + return emitPropertyAssignment(node); + case SyntaxKind.ShorthandPropertyAssignment: + return emitShorthandPropertyAssignment(node); + case SyntaxKind.SpreadAssignment: + return emitSpreadAssignment(node as SpreadAssignment); - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); + // Enum + case SyntaxKind.EnumMember: + return emitEnumMember(node); + + // Unparsed + case SyntaxKind.UnparsedPrologue: + return writeUnparsedNode(node); + case SyntaxKind.UnparsedSource: + case SyntaxKind.UnparsedPrepend: + return emitUnparsedSourceOrPrepend(node); + case SyntaxKind.UnparsedText: + case SyntaxKind.UnparsedInternalText: + return emitUnparsedTextLike(node); + case SyntaxKind.UnparsedSyntheticReference: + return emitUnparsedSyntheticReference(node); + + // Top-level nodes + case SyntaxKind.SourceFile: + return emitSourceFile(node); + case SyntaxKind.Bundle: + return Debug.fail("Bundles should be printed using printBundle"); + // SyntaxKind.UnparsedSource (handled above) + case SyntaxKind.InputFiles: + return Debug.fail("InputFiles should not be printed"); - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.ClassMembers); - writePunctuation("}"); + // JSDoc nodes (only used in codefixes currently) + case SyntaxKind.JSDocTypeExpression: + return emitJSDocTypeExpression(node as JSDocTypeExpression); + case SyntaxKind.JSDocNameReference: + return emitJSDocNameReference(node as JSDocNameReference); + case SyntaxKind.JSDocAllType: + return writePunctuation("*"); + case SyntaxKind.JSDocUnknownType: + return writePunctuation("?"); + case SyntaxKind.JSDocNullableType: + return emitJSDocNullableType(node as JSDocNullableType); + case SyntaxKind.JSDocNonNullableType: + return emitJSDocNonNullableType(node as JSDocNonNullableType); + case SyntaxKind.JSDocOptionalType: + return emitJSDocOptionalType(node as JSDocOptionalType); + case SyntaxKind.JSDocFunctionType: + return emitJSDocFunctionType(node as JSDocFunctionType); + case SyntaxKind.RestType: + case SyntaxKind.JSDocVariadicType: + return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); + case SyntaxKind.JSDocNamepathType: + return; + case SyntaxKind.JSDocComment: + return emitJSDoc(node as JSDoc); + case SyntaxKind.JSDocTypeLiteral: + return emitJSDocTypeLiteral(node as JSDocTypeLiteral); + case SyntaxKind.JSDocSignature: + return emitJSDocSignature(node as JSDocSignature); + case SyntaxKind.JSDocTag: + case SyntaxKind.JSDocClassTag: + return emitJSDocSimpleTag(node as JSDocTag); + case SyntaxKind.JSDocAugmentsTag: + case SyntaxKind.JSDocImplementsTag: + return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag); + case SyntaxKind.JSDocAuthorTag: + case SyntaxKind.JSDocDeprecatedTag: + return; + // SyntaxKind.JSDocClassTag (see JSDocTag, above) + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocPrivateTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocReadonlyTag: + case SyntaxKind.JSDocOverrideTag: + return; + case SyntaxKind.JSDocCallbackTag: + return emitJSDocCallbackTag(node as JSDocCallbackTag); + // SyntaxKind.JSDocEnumTag (see below) + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); + case SyntaxKind.JSDocEnumTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocThisTag: + case SyntaxKind.JSDocTypeTag: + return emitJSDocSimpleTypedTag(node as JSDocTypeTag); + case SyntaxKind.JSDocTemplateTag: + return emitJSDocTemplateTag(node as JSDocTemplateTag); + case SyntaxKind.JSDocTypedefTag: + return emitJSDocTypedefTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocSeeTag: + return emitJSDocSeeTag(node as JSDocSeeTag); + // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) - if (indentedFlag) { - decreaseIndent(); + // Transformation nodes + case SyntaxKind.NotEmittedStatement: + case SyntaxKind.EndOfDeclarationMarker: + case SyntaxKind.MergeDeclarationMarker: + return; + } + if (isExpression(node)) { + emitHint = EmitHint.Expression; + if (shouldEmitWithSubstitution()) { + node = substituteNode(emitHint, node) || node; + } + } } - } - - function emitInterfaceDeclaration(node: InterfaceDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("interface"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.HeritageClauses); - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.InterfaceMembers); - writePunctuation("}"); - } - - function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { - emitDecorators(node, node.decorators); - emitModifiers(node, node.modifiers); - writeKeyword("type"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - writeSpace(); - writePunctuation("="); - writeSpace(); - emit(node.type); - writeTrailingSemicolon(); - } - - function emitEnumDeclaration(node: EnumDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("enum"); - writeSpace(); - emit(node.name); + if (emitHint === EmitHint.Expression) { + switch (node.kind) { + // Literals + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + return emitNumericOrBigIntLiteral(node); - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.EnumMembers); - writePunctuation("}"); - } + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return emitLiteral(node, /*jsxAttributeEscape*/ false); - function emitModuleDeclaration(node: ModuleDeclaration) { - emitModifiers(node, node.modifiers); - if (~node.flags & NodeFlags.GlobalAugmentation) { - writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); - writeSpace(); - } - emit(node.name); + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node); - let body = node.body; - if (!body) return writeTrailingSemicolon(); - while (body && isModuleDeclaration(body)) { - writePunctuation("."); - emit(body.name); - body = body.body; - } + // Expressions + case SyntaxKind.ArrayLiteralExpression: + return emitArrayLiteralExpression(node); + case SyntaxKind.ObjectLiteralExpression: + return emitObjectLiteralExpression(node); + case SyntaxKind.PropertyAccessExpression: + return emitPropertyAccessExpression(node); + case SyntaxKind.ElementAccessExpression: + return emitElementAccessExpression(node); + case SyntaxKind.CallExpression: + return emitCallExpression(node); + case SyntaxKind.NewExpression: + return emitNewExpression(node); + case SyntaxKind.TaggedTemplateExpression: + return emitTaggedTemplateExpression(node); + case SyntaxKind.TypeAssertionExpression: + return emitTypeAssertionExpression(node); + case SyntaxKind.ParenthesizedExpression: + return emitParenthesizedExpression(node); + case SyntaxKind.FunctionExpression: + return emitFunctionExpression(node); + case SyntaxKind.ArrowFunction: + return emitArrowFunction(node); + case SyntaxKind.DeleteExpression: + return emitDeleteExpression(node); + case SyntaxKind.TypeOfExpression: + return emitTypeOfExpression(node); + case SyntaxKind.VoidExpression: + return emitVoidExpression(node); + case SyntaxKind.AwaitExpression: + return emitAwaitExpression(node); + case SyntaxKind.PrefixUnaryExpression: + return emitPrefixUnaryExpression(node); + case SyntaxKind.PostfixUnaryExpression: + return emitPostfixUnaryExpression(node); + case SyntaxKind.BinaryExpression: + return emitBinaryExpression(node); + case SyntaxKind.ConditionalExpression: + return emitConditionalExpression(node); + case SyntaxKind.TemplateExpression: + return emitTemplateExpression(node); + case SyntaxKind.YieldExpression: + return emitYieldExpression(node); + case SyntaxKind.SpreadElement: + return emitSpreadElement(node); + case SyntaxKind.ClassExpression: + return emitClassExpression(node); + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.AsExpression: + return emitAsExpression(node); + case SyntaxKind.NonNullExpression: + return emitNonNullExpression(node); + case SyntaxKind.MetaProperty: + return emitMetaProperty(node); + case SyntaxKind.SyntheticExpression: + return Debug.fail("SyntheticExpression should never be printed."); - writeSpace(); - emit(body); - } + // JSX + case SyntaxKind.JsxElement: + return emitJsxElement(node); + case SyntaxKind.JsxSelfClosingElement: + return emitJsxSelfClosingElement(node); + case SyntaxKind.JsxFragment: + return emitJsxFragment(node); - function emitModuleBlock(node: ModuleBlock) { - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); - popNameGenerationScope(node); - } + // Synthesized list + case SyntaxKind.SyntaxList: + return Debug.fail("SyntaxList should not be printed"); - function emitCaseBlock(node: CaseBlock) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emitList(node, node.clauses, ListFormat.CaseBlockClauses); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + // Transformation nodes + case SyntaxKind.NotEmittedStatement: + return; + case SyntaxKind.PartiallyEmittedExpression: + return emitPartiallyEmittedExpression(node); + case SyntaxKind.CommaListExpression: + return emitCommaList(node); + case SyntaxKind.MergeDeclarationMarker: + case SyntaxKind.EndOfDeclarationMarker: + return; + case SyntaxKind.SyntheticReferenceExpression: + return Debug.fail("SyntheticReferenceExpression should not be printed"); + } + } + if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); + if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation); + Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`); } - function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); - writeSpace(); - if (node.isTypeOnly) { - emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); - writeSpace(); - } + function emitMappedTypeParameter(_hint: EmitHint, node: TypeParameterDeclaration): void { emit(node.name); writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); + writeKeyword("in"); writeSpace(); - emitModuleReference(node.moduleReference); - writeTrailingSemicolon(); + emit(node.constraint); } - function emitModuleReference(node: ModuleReference) { - if (node.kind === SyntaxKind.Identifier) { - emit(node); + function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { + let result: string[] | undefined; + if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { + return undefined; } - else { - emit(node); + const bundledHelpers = new Map(); + for (const sourceFile of bundle.sourceFiles) { + const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; + const helpers = getSortedEmitHelpers(sourceFile); + if (!helpers) continue; + for (const helper of helpers) { + if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { + bundledHelpers.set(helper.name, true); + (result || (result = [])).push(helper.name); + } + } } - } - function emitImportDeclaration(node: ImportDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); - writeSpace(); - if (node.importClause) { - emit(node.importClause); - writeSpace(); - emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); - writeSpace(); - } - emit(node.moduleSpecifier); - writeTrailingSemicolon(); + return result; } - function emitImportClause(node: ImportClause) { - if (node.isTypeOnly) { - emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); - writeSpace(); + function emitHelpers(node: Node) { + let helpersEmitted = false; + const bundle = node.kind === SyntaxKind.Bundle ? node : undefined; + if (bundle && moduleKind === ModuleKind.None) { + return; } - emit(node.name); - if (node.name && node.namedBindings) { - emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); - writeSpace(); + const numPrepends = bundle ? bundle.prepends.length : 0; + const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; + for (let i = 0; i < numNodes; i++) { + const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; + const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!; + const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); + const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; + const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + // Skip the helper if it can be skipped and the noEmitHelpers compiler + // option is set, or if it can be imported and the importHelpers compiler + // option is set. + if (shouldSkip) continue; + + // Skip the helper if it can be bundled but hasn't already been emitted and we + // are emitting a bundled module. + if (shouldBundle) { + if (bundledHelpers.get(helper.name)) { + continue; + } + + bundledHelpers.set(helper.name, true); + } + } + else if (bundle) { + // Skip the helper if it is scoped and we are emitting bundled helpers + continue; + } + const pos = getTextPosWithWriteLine(); + if (typeof helper.text === "string") { + writeLines(helper.text); + } + else { + writeLines(helper.text(makeFileLevelOptimisticUniqueName)); + } + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); + helpersEmitted = true; + } + } } - emit(node.namedBindings); - } - function emitNamespaceImport(node: NamespaceImport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); - writeSpace(); - emit(node.name); + return helpersEmitted; } - function emitNamedImports(node: NamedImports) { - emitNamedImportsOrExports(node); + function getSortedEmitHelpers(node: Node) { + const helpers = getEmitHelpers(node); + return helpers && stableSort(helpers, compareEmitHelpers); } - function emitImportSpecifier(node: ImportSpecifier) { - emitImportOrExportSpecifier(node); + // + // Literals/Pseudo-literals + // + + // SyntaxKind.NumericLiteral + // SyntaxKind.BigIntLiteral + function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { + emitLiteral(node, /*jsxAttributeEscape*/ false); } - function emitExportAssignment(node: ExportAssignment) { - const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.isExportEquals) { - emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); + // SyntaxKind.StringLiteral + // SyntaxKind.RegularExpressionLiteral + // SyntaxKind.NoSubstitutionTemplateLiteral + // SyntaxKind.TemplateHead + // SyntaxKind.TemplateMiddle + // SyntaxKind.TemplateTail + function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { + const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); + if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) + && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { + writeLiteral(text); } else { - emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); + // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals + writeStringLiteral(text); } - writeSpace(); - emit(node.expression); - writeTrailingSemicolon(); } - function emitExportDeclaration(node: ExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.isTypeOnly) { - nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node); - writeSpace(); - } - if (node.exportClause) { - emit(node.exportClause); - } - else { - nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); + function emitStringLiteralWithJsxAttributeEscape(_hint: EmitHint, node: StringLiteral) { + emitLiteral(node, /*jsxAttributeEscape*/ true); + } + + // SyntaxKind.UnparsedSource + // SyntaxKind.UnparsedPrepend + function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { + for (const text of unparsed.texts) { + writeLine(); + emit(text); } - if (node.moduleSpecifier) { - writeSpace(); - const fromPos = node.exportClause ? node.exportClause.end : nextPos; - emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); - writeSpace(); - emit(node.moduleSpecifier); + } + + // SyntaxKind.UnparsedPrologue + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + // SyntaxKind.UnparsedSyntheticReference + function writeUnparsedNode(unparsed: UnparsedNode) { + writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); + } + + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + function emitUnparsedTextLike(unparsed: UnparsedTextLike) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + updateOrPushBundleFileTextLike( + pos, + writer.getTextPos(), + unparsed.kind === SyntaxKind.UnparsedText ? + BundleFileSectionKind.Text : + BundleFileSectionKind.Internal + ); } - writeTrailingSemicolon(); } - function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); - writeSpace(); - emit(node.name); - writeTrailingSemicolon(); + // SyntaxKind.UnparsedSyntheticReference + function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + const section = clone(unparsed.section); + section.pos = pos; + section.end = writer.getTextPos(); + bundleFileInfo.sections.push(section); + } } - function emitNamespaceExport(node: NamespaceExport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); - writeSpace(); - emit(node.name); + // + // Identifiers + // + + function emitIdentifier(node: Identifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments } - function emitNamedExports(node: NamedExports) { - emitNamedImportsOrExports(node); - } + // + // Names + // - function emitExportSpecifier(node: ExportSpecifier) { - emitImportOrExportSpecifier(node); + function emitPrivateIdentifier(node: PrivateIdentifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); } - function emitNamedImportsOrExports(node: NamedImportsOrExports) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); - writePunctuation("}"); + + function emitQualifiedName(node: QualifiedName) { + emitEntityName(node.left); + writePunctuation("."); + emit(node.right); } - function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { - if (node.propertyName) { - emit(node.propertyName); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); - writeSpace(); + function emitEntityName(node: EntityName) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); + } + else { + emit(node); } - - emit(node.name); } - // - // Module references - // - - function emitExternalModuleReference(node: ExternalModuleReference) { - writeKeyword("require"); - writePunctuation("("); - emit(node.expression); - writePunctuation(")"); + function emitComputedPropertyName(node: ComputedPropertyName) { + writePunctuation("["); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfComputedPropertyName); + writePunctuation("]"); } // - // JSX + // Signature elements // - function emitJsxElement(node: JsxElement) { - emit(node.openingElement); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingElement); + function emitTypeParameter(node: TypeParameterDeclaration) { + emit(node.name); + if (node.constraint) { + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.constraint); + } + if (node.default) { + writeSpace(); + writeOperator("="); + writeSpace(); + emit(node.default); + } } - function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { - writePunctuation("<"); - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); - writeSpace(); - emit(node.attributes); - writePunctuation("/>"); + function emitParameter(node: ParameterDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.dotDotDotToken); + emitNodeWithWriter(node.name, writeParameter); + emit(node.questionToken); + if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { + emit(node.type); + } + else { + emitTypeAnnotation(node.type); + } + // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.decorators ? node.decorators.end : node.pos, node, parenthesizer.parenthesizeExpressionForDisallowedComma); } - function emitJsxFragment(node: JsxFragment) { - emit(node.openingFragment); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingFragment); + function emitDecorator(decorator: Decorator) { + writePunctuation("@"); + emitExpression(decorator.expression, parenthesizer.parenthesizeLeftSideOfAccess); } - function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { - writePunctuation("<"); - - if (isJsxOpeningElement(node)) { - const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node); - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); - if (node.attributes.properties && node.attributes.properties.length > 0) { - writeSpace(); - } - emit(node.attributes); - writeLineSeparatorsAfter(node.attributes, node); - decreaseIndentIf(indented); - } + // + // Type members + // - writePunctuation(">"); + function emitPropertySignature(node: PropertySignature) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitNodeWithWriter(node.name, writeProperty); + emit(node.questionToken); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); } - function emitJsxText(node: JsxText) { - writer.writeLiteral(node.text); + function emitPropertyDeclaration(node: PropertyDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); + writeTrailingSemicolon(); } - function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { - writePunctuation(""); + function emitMethodSignature(node: MethodSignature) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); } - function emitJsxAttributes(node: JsxAttributes) { - emitList(node, node.properties, ListFormat.JsxElementAttributes); + function emitMethodDeclaration(node: MethodDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emit(node.asteriskToken); + emit(node.name); + emit(node.questionToken); + emitSignatureAndBody(node, emitSignatureHead); } - function emitJsxAttributeValue(node: StringLiteral | JsxExpression): void { - const emitCallback = isStringLiteral(node) ? emitStringLiteralWithJsxAttributeEscape : emitWorker; - emitWithContext(node, emitCallback); + function emitConstructor(node: ConstructorDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("constructor"); + emitSignatureAndBody(node, emitSignatureHead); } - function emitJsxAttribute(node: JsxAttribute) { + function emitAccessorDeclaration(node: AccessorDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); + writeSpace(); emit(node.name); - emitNodeWithPrefix("=", writePunctuation, node.initializer, emitJsxAttributeValue); + emitSignatureAndBody(node, emitSignatureHead); } - function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { - writePunctuation("{..."); - emit(node.expression); - writePunctuation("}"); + function emitCallSignature(node: CallSignatureDeclaration) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); } - function hasTrailingCommentsAtPosition(pos: number) { - let result = false; - forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); - return result; + function emitConstructSignature(node: ConstructSignatureDeclaration) { + pushNameGenerationScope(node); + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); } - function hasLeadingCommentsAtPosition(pos: number) { - let result = false; - forEachLeadingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); - return result; + function emitIndexSignature(node: IndexSignatureDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitParametersForIndexSignature(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); } - function hasCommentsAtPosition(pos: number) { - return hasTrailingCommentsAtPosition(pos) || hasLeadingCommentsAtPosition(pos); + function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) { + emit(node.type); + emit(node.literal); } - function emitJsxExpression(node: JsxExpression) { - if (node.expression || (!commentsDisabled && !nodeIsSynthesized(node) && hasCommentsAtPosition(node.pos))) { // preserve empty expressions if they contain comments! - const isMultiline = currentSourceFile && !nodeIsSynthesized(node) && getLineAndCharacterOfPosition(currentSourceFile, node.pos).line !== getLineAndCharacterOfPosition(currentSourceFile, node.end).line; - if (isMultiline) { - writer.increaseIndent(); - } - const end = emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emit(node.dotDotDotToken); - emit(node.expression); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.expression?.end || end, writePunctuation, node); - if (isMultiline) { - writer.decreaseIndent(); - } - } + function emitSemicolonClassElement() { + writeTrailingSemicolon(); } - function emitJsxTagName(node: JsxTagNameExpression) { - if (node.kind === SyntaxKind.Identifier) { - emit(node); + // + // Types + // + + function emitTypePredicate(node: TypePredicateNode) { + if (node.assertsModifier) { + emit(node.assertsModifier); + writeSpace(); } - else { - emit(node); + emit(node.parameterName); + if (node.type) { + writeSpace(); + writeKeyword("is"); + writeSpace(); + emit(node.type); } } - // - // Clauses - // + function emitTypeReference(node: TypeReferenceNode) { + emit(node.typeName); + emitTypeArguments(node, node.typeArguments); + } - function emitCaseClause(node: CaseClause) { - emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); + function emitFunctionType(node: FunctionTypeNode) { + pushNameGenerationScope(node); + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); writeSpace(); - emit(node.expression); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } - emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); + function emitJSDocFunctionType(node: JSDocFunctionType) { + writeKeyword("function"); + emitParameters(node, node.parameters); + writePunctuation(":"); + emit(node.type); } - function emitDefaultClause(node: DefaultClause) { - const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); - emitCaseOrDefaultClauseRest(node, node.statements, pos); + + function emitJSDocNullableType(node: JSDocNullableType) { + writePunctuation("?"); + emit(node.type); } - function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { - const emitAsSingleStatement = - statements.length === 1 && - ( - // treat synthesized nodes as located on the same line for emit purposes - nodeIsSynthesized(parentNode) || - nodeIsSynthesized(statements[0]) || - rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile!) - ); + function emitJSDocNonNullableType(node: JSDocNonNullableType) { + writePunctuation("!"); + emit(node.type); + } - let format = ListFormat.CaseOrDefaultClauseStatements; - if (emitAsSingleStatement) { - writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); - writeSpace(); - format &= ~(ListFormat.MultiLine | ListFormat.Indented); - } - else { - emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); - } - emitList(parentNode, statements, format); + function emitJSDocOptionalType(node: JSDocOptionalType) { + emit(node.type); + writePunctuation("="); } - function emitHeritageClause(node: HeritageClause) { + function emitConstructorType(node: ConstructorTypeNode) { + pushNameGenerationScope(node); + emitModifiers(node, node.modifiers); + writeKeyword("new"); writeSpace(); - writeTokenText(node.token, writeKeyword); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); writeSpace(); - emitList(node, node.types, ListFormat.HeritageClauseTypes); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); } - function emitCatchClause(node: CatchClause) { - const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); + function emitTypeQuery(node: TypeQueryNode) { + writeKeyword("typeof"); writeSpace(); - if (node.variableDeclaration) { - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.variableDeclaration); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); - writeSpace(); - } - emit(node.block); + emit(node.exprName); } - // - // Property assignments - // - - function emitPropertyAssignment(node: PropertyAssignment) { - emit(node.name); - writePunctuation(":"); - writeSpace(); - // This is to ensure that we emit comment in the following case: - // For example: - // obj = { - // id: /*comment1*/ ()=>void - // } - // "comment1" is not considered to be leading comment for node.initializer - // but rather a trailing comment on the previous node. - const initializer = node.initializer; - if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { - const commentRange = getCommentRange(initializer); - emitTrailingCommentsOfPosition(commentRange.pos); - } - emit(initializer); + function emitTypeLiteral(node: TypeLiteralNode) { + writePunctuation("{"); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; + emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); + writePunctuation("}"); } - function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { - emit(node.name); - if (node.objectAssignmentInitializer) { - writeSpace(); - writePunctuation("="); - writeSpace(); - emit(node.objectAssignmentInitializer); - } + function emitArrayType(node: ArrayTypeNode) { + emit(node.elementType, parenthesizer.parenthesizeElementTypeOfArrayType); + writePunctuation("["); + writePunctuation("]"); } - function emitSpreadAssignment(node: SpreadAssignment) { - if (node.expression) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emit(node.expression); - } + function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { + writePunctuation("..."); + emit(node.type); } - // - // Enum - // + function emitTupleType(node: TupleTypeNode) { + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; + emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); + } - function emitEnumMember(node: EnumMember) { + function emitNamedTupleMember(node: NamedTupleMember) { + emit(node.dotDotDotToken); emit(node.name); - emitInitializer(node.initializer, node.name.end, node); + emit(node.questionToken); + emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); + writeSpace(); + emit(node.type); } - // - // JSDoc - // - function emitJSDoc(node: JSDoc) { - write("/**"); - if (node.comment) { - const text = getTextOfJSDocComment(node.comment); - if (text) { - const lines = text.split(/\r\n?|\n/g); - for (const line of lines) { - writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - write(line); - } - } - } - if (node.tags) { - if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { - writeSpace(); - emit(node.tags[0]); - } - else { - emitList(node, node.tags, ListFormat.JSDocComment); - } - } - writeSpace(); - write("*/"); + function emitOptionalType(node: OptionalTypeNode) { + emit(node.type, parenthesizer.parenthesizeElementTypeOfArrayType); + writePunctuation("?"); } - function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.typeExpression); - emitJSDocComment(tag.comment); + function emitUnionType(node: UnionTypeNode) { + emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType); } - function emitJSDocSeeTag(tag: JSDocSeeTag) { - emitJSDocTagName(tag.tagName); - emit(tag.name); - emitJSDocComment(tag.comment); + function emitIntersectionType(node: IntersectionTypeNode) { + emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeMemberOfElementType); } - function emitJSDocNameReference(node: JSDocNameReference) { + function emitConditionalType(node: ConditionalTypeNode) { + emit(node.checkType, parenthesizer.parenthesizeMemberOfConditionalType); writeSpace(); - writePunctuation("{"); - emit(node.name); - writePunctuation("}"); + writeKeyword("extends"); + writeSpace(); + emit(node.extendsType, parenthesizer.parenthesizeMemberOfConditionalType); + writeSpace(); + writePunctuation("?"); + writeSpace(); + emit(node.trueType); + writeSpace(); + writePunctuation(":"); + writeSpace(); + emit(node.falseType); } - function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) { - emitJSDocTagName(tag.tagName); + function emitInferType(node: InferTypeNode) { + writeKeyword("infer"); writeSpace(); - writePunctuation("{"); - emit(tag.class); - writePunctuation("}"); - emitJSDocComment(tag.comment); + emit(node.typeParameter); } - function emitJSDocTemplateTag(tag: JSDocTemplateTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.constraint); + function emitParenthesizedType(node: ParenthesizedTypeNode) { + writePunctuation("("); + emit(node.type); + writePunctuation(")"); + } + + function emitThisType() { + writeKeyword("this"); + } + + function emitTypeOperator(node: TypeOperatorNode) { + writeTokenText(node.operator, writeKeyword); writeSpace(); - emitList(tag, tag.typeParameters, ListFormat.CommaListElements); - emitJSDocComment(tag.comment); + emit(node.type, parenthesizer.parenthesizeMemberOfElementType); } - function emitJSDocTypedefTag(tag: JSDocTypedefTag) { - emitJSDocTagName(tag.tagName); - if (tag.typeExpression) { - if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - emitJSDocTypeExpression(tag.typeExpression); + function emitIndexedAccessType(node: IndexedAccessTypeNode) { + emit(node.objectType, parenthesizer.parenthesizeMemberOfElementType); + writePunctuation("["); + emit(node.indexType); + writePunctuation("]"); + } + + function emitMappedType(node: MappedTypeNode) { + const emitFlags = getEmitFlags(node); + writePunctuation("{"); + if (emitFlags & EmitFlags.SingleLine) { + writeSpace(); + } + else { + writeLine(); + increaseIndent(); + } + if (node.readonlyToken) { + emit(node.readonlyToken); + if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + writeKeyword("readonly"); } - else { - writeSpace(); - writePunctuation("{"); - write("Object"); - if (tag.typeExpression.isArrayType) { - writePunctuation("["); - writePunctuation("]"); - } - writePunctuation("}"); + writeSpace(); + } + writePunctuation("["); + + pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter, emitMappedTypeParameter, /*parenthesizerRule*/ undefined); + if (node.nameType) { + writeSpace(); + writeKeyword("as"); + writeSpace(); + emit(node.nameType); + } + + writePunctuation("]"); + if (node.questionToken) { + emit(node.questionToken); + if (node.questionToken.kind !== SyntaxKind.QuestionToken) { + writePunctuation("?"); } } - if (tag.fullName) { + writePunctuation(":"); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + if (emitFlags & EmitFlags.SingleLine) { writeSpace(); - emit(tag.fullName); - } - emitJSDocComment(tag.comment); - if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { - emitJSDocTypeLiteral(tag.typeExpression); } - } - - function emitJSDocCallbackTag(tag: JSDocCallbackTag) { - emitJSDocTagName(tag.tagName); - if (tag.name) { - writeSpace(); - emit(tag.name); + else { + writeLine(); + decreaseIndent(); } - emitJSDocComment(tag.comment); - emitJSDocSignature(tag.typeExpression); + writePunctuation("}"); } - function emitJSDocSimpleTag(tag: JSDocTag) { - emitJSDocTagName(tag.tagName); - emitJSDocComment(tag.comment); + function emitLiteralType(node: LiteralTypeNode) { + emitExpression(node.literal); } - function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { - emitList(lit, factory.createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); + function emitTemplateType(node: TemplateLiteralTypeNode) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); } - function emitJSDocSignature(sig: JSDocSignature) { - if (sig.typeParameters) { - emitList(sig, factory.createNodeArray(sig.typeParameters), ListFormat.JSDocComment); - } - if (sig.parameters) { - emitList(sig, factory.createNodeArray(sig.parameters), ListFormat.JSDocComment); - } - if (sig.type) { - writeLine(); - writeSpace(); - writePunctuation("*"); + function emitImportTypeNode(node: ImportTypeNode) { + if (node.isTypeOf) { + writeKeyword("typeof"); writeSpace(); - emit(sig.type); - } - } - - function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { - emitJSDocTagName(param.tagName); - emitJSDocTypeExpression(param.typeExpression); - writeSpace(); - if (param.isBracketed) { - writePunctuation("["); } - emit(param.name); - if (param.isBracketed) { - writePunctuation("]"); + writeKeyword("import"); + writePunctuation("("); + emit(node.argument); + writePunctuation(")"); + if (node.qualifier) { + writePunctuation("."); + emit(node.qualifier); } - emitJSDocComment(param.comment); + emitTypeArguments(node, node.typeArguments); } - function emitJSDocTagName(tagName: Identifier) { - writePunctuation("@"); - emit(tagName); + // + // Binding patterns + // + + function emitObjectBindingPattern(node: ObjectBindingPattern) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); + writePunctuation("}"); } - function emitJSDocComment(comment: string | NodeArray | undefined) { - const text = getTextOfJSDocComment(comment); - if (text) { - writeSpace(); - write(text); - } + function emitArrayBindingPattern(node: ArrayBindingPattern) { + writePunctuation("["); + emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); + writePunctuation("]"); } - function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { - if (typeExpression) { + function emitBindingElement(node: BindingElement) { + emit(node.dotDotDotToken); + if (node.propertyName) { + emit(node.propertyName); + writePunctuation(":"); writeSpace(); - writePunctuation("{"); - emit(typeExpression.type); - writePunctuation("}"); } + emit(node.name); + emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); } // - // Top-level nodes + // Expressions // - function emitSourceFile(node: SourceFile) { - writeLine(); - const statements = node.statements; - if (emitBodyWithDetachedComments) { - // Emit detached comment if there are no prologue directives or if the first node is synthesized. - // The synthesized node will have no leading comment so some comments may be missed. - const shouldEmitDetachedComment = statements.length === 0 || - !isPrologueDirective(statements[0]) || - nodeIsSynthesized(statements[0]); - if (shouldEmitDetachedComment) { - emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); - return; - } - } - emitSourceFileWorker(node); + function emitArrayLiteralExpression(node: ArrayLiteralExpression) { + const elements = node.elements; + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine, parenthesizer.parenthesizeExpressionForDisallowedComma); } - function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { - emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); - for (const prepend of node.prepends) { - if (isUnparsedSource(prepend) && prepend.syntheticReferences) { - for (const ref of prepend.syntheticReferences) { - emit(ref); - writeLine(); - } - } + function emitObjectLiteralExpression(node: ObjectLiteralExpression) { + forEach(node.properties, generateMemberNames); + + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); } - } - function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { - if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); - } + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile!) ? ListFormat.AllowTrailingComma : ListFormat.None; + emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); - function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { - if (hasNoDefaultLib) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); - writeLine(); + if (indentedFlag) { + decreaseIndent(); } - if (currentSourceFile && currentSourceFile.moduleName) { - writeComment(`/// `); - writeLine(); + } + + function emitPropertyAccessExpression(node: PropertyAccessExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos); + const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token); + const linesAfterDot = getLinesBetweenNodes(node, token, node.name); + + writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false); + + const shouldEmitDotDot = + token.kind !== SyntaxKind.QuestionDotToken && + mayNeedDotDotForPropertyAccess(node.expression) && + !writer.hasTrailingComment() && + !writer.hasTrailingWhitespace(); + + if (shouldEmitDotDot) { + writePunctuation("."); } - if (currentSourceFile && currentSourceFile.amdDependencies) { - for (const dep of currentSourceFile.amdDependencies) { - if (dep.name) { - writeComment(`/// `); - } - else { - writeComment(`/// `); - } - writeLine(); - } + + if (node.questionDotToken) { + emit(token); } - for (const directive of files) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); - writeLine(); + else { + emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); } - for (const directive of types) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); - writeLine(); + writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false); + emit(node.name); + decreaseIndentIf(linesBeforeDot, linesAfterDot); + } + + // 1..toString is a valid property access, emit a dot after the literal + // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal + function mayNeedDotDotForPropertyAccess(expression: Expression) { + expression = skipPartiallyEmittedExpressions(expression); + if (isNumericLiteral(expression)) { + // check if numeric literal is a decimal literal that was originally written with a dot + const text = getLiteralTextOfNode(expression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); + // If he number will be printed verbatim and it doesn't already contain a dot, add one + // if the expression doesn't have any comments that will be emitted. + return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); } - for (const directive of libs) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); - writeLine(); + else if (isAccessExpression(expression)) { + // check if constant enum value is integer + const constantValue = getConstantValue(expression); + // isFinite handles cases when constantValue is undefined + return typeof constantValue === "number" && isFinite(constantValue) + && Math.floor(constantValue) === constantValue; } } - function emitSourceFileWorker(node: SourceFile) { - const statements = node.statements; - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitHelpers(node); - const index = findIndex(statements, statement => !isPrologueDirective(statement)); - emitTripleSlashDirectivesIfNeeded(node); - emitList(node, statements, ListFormat.MultiLine, index === -1 ? statements.length : index); - popNameGenerationScope(node); + function emitElementAccessExpression(node: ElementAccessExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emit(node.questionDotToken); + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); + emitExpression(node.argumentExpression); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); + } + + function emitCallExpression(node: CallExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emit(node.questionDotToken); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + function emitNewExpression(node: NewExpression) { + emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfNew); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { + emitExpression(node.tag, parenthesizer.parenthesizeLeftSideOfAccess); + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emitExpression(node.template); + } + + function emitTypeAssertionExpression(node: TypeAssertion) { + writePunctuation("<"); + emit(node.type); + writePunctuation(">"); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } + + function emitParenthesizedExpression(node: ParenthesizedExpression) { + const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); + const indented = writeLineSeparatorsAndIndentBefore(node.expression, node); + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + writeLineSeparatorsAfter(node.expression, node); + decreaseIndentIf(indented); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); + } + + function emitFunctionExpression(node: FunctionExpression) { + generateNameIfNeeded(node.name); + emitFunctionDeclarationOrExpression(node); } - // Transformation nodes + function emitArrowFunction(node: ArrowFunction) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + emitSignatureAndBody(node, emitArrowFunctionHead); + } - function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { - emit(node.expression); + function emitArrowFunctionHead(node: ArrowFunction) { + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + emitTypeAnnotation(node.type); + writeSpace(); + emit(node.equalsGreaterThanToken); } - function emitCommaList(node: CommaListExpression) { - emitList(node, node.elements, ListFormat.CommaListElements); + function emitDeleteExpression(node: DeleteExpression) { + emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - /** - * Emits any prologue directives at the start of a Statement list, returning the - * number of prologue directives written to the output. - */ - function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Set, recordBundleFileSection?: true): number { - let needsToSetSourceFile = !!sourceFile; - for (let i = 0; i < statements.length; i++) { - const statement = statements[i]; - if (isPrologueDirective(statement)) { - const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; - if (shouldEmitPrologueDirective) { - if (needsToSetSourceFile) { - needsToSetSourceFile = false; - setSourceFile(sourceFile); - } - writeLine(); - const pos = writer.getTextPos(); - emit(statement); - if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); - if (seenPrologueDirectives) { - seenPrologueDirectives.add(statement.expression.text); - } - } - } - else { - // return index of the first non prologue directive - return i; - } - } + function emitTypeOfExpression(node: TypeOfExpression) { + emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - return statements.length; + function emitVoidExpression(node: VoidExpression) { + emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Set) { - for (const prologue of prologues) { - if (!seenPrologueDirectives.has(prologue.data)) { - writeLine(); - const pos = writer.getTextPos(); - emit(prologue); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); - if (seenPrologueDirectives) { - seenPrologueDirectives.add(prologue.data); - } - } - } + function emitAwaitExpression(node: AwaitExpression) { + emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { - if (isSourceFile(sourceFileOrBundle)) { - emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); - } - else { - const seenPrologueDirectives = new Set(); - for (const prepend of sourceFileOrBundle.prepends) { - emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); - } - setSourceFile(undefined); + function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { + writeTokenText(node.operator, writeOperator); + if (shouldEmitWhitespaceBeforeOperand(node)) { + writeSpace(); } + emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPrefixUnary); } - function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { - const seenPrologueDirectives = new Set(); - let prologues: SourceFilePrologueInfo[] | undefined; - for (let index = 0; index < bundle.sourceFiles.length; index++) { - const sourceFile = bundle.sourceFiles[index]; - let directives: SourceFilePrologueDirective[] | undefined; - let end = 0; - for (const statement of sourceFile.statements) { - if (!isPrologueDirective(statement)) break; - if (seenPrologueDirectives.has(statement.expression.text)) continue; - seenPrologueDirectives.add(statement.expression.text); - (directives || (directives = [])).push({ - pos: statement.pos, - end: statement.end, - expression: { - pos: statement.expression.pos, - end: statement.expression.end, - text: statement.expression.text - } - }); - end = end < statement.end ? statement.end : end; - } - if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); - } - return prologues; + function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { + // In some cases, we need to emit a space between the operator and the operand. One obvious case + // is when the operator is an identifier, like delete or typeof. We also need to do this for plus + // and minus expressions in certain cases. Specifically, consider the following two cases (parens + // are just for clarity of exposition, and not part of the source code): + // + // (+(+1)) + // (+(++1)) + // + // We need to emit a space in both cases. In the first case, the absence of a space will make + // the resulting expression a prefix increment operation. And in the second, it will make the resulting + // expression a prefix increment whose operand is a plus expression - (++(+x)) + // The same is true of minus of course. + const operand = node.operand; + return operand.kind === SyntaxKind.PrefixUnaryExpression + && ((node.operator === SyntaxKind.PlusToken && ((operand).operator === SyntaxKind.PlusToken || (operand).operator === SyntaxKind.PlusPlusToken)) + || (node.operator === SyntaxKind.MinusToken && ((operand).operator === SyntaxKind.MinusToken || (operand).operator === SyntaxKind.MinusMinusToken))); } - function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { - if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { - const shebang = getShebang(sourceFileOrBundle.text); - if (shebang) { - writeComment(shebang); - writeLine(); - return true; - } + function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { + emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPostfixUnary); + writeTokenText(node.operator, writeOperator); + } + + function createEmitBinaryExpression() { + interface WorkArea { + stackIndex: number; + preserveSourceNewlinesStack: (boolean | undefined)[]; + containerPosStack: number[]; + containerEndStack: number[]; + declarationListContainerEndStack: number[]; + shouldEmitCommentsStack: boolean[]; + shouldEmitSourceMapsStack: boolean[]; } - else { - for (const prepend of sourceFileOrBundle.prepends) { - Debug.assertNode(prepend, isUnparsedSource); - if (emitShebangIfNeeded(prepend)) { - return true; - } + + return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); + + function onEnter(node: BinaryExpression, state: WorkArea | undefined) { + if (state) { + state.stackIndex++; + state.preserveSourceNewlinesStack[state.stackIndex] = preserveSourceNewlines; + state.containerPosStack[state.stackIndex] = containerPos; + state.containerEndStack[state.stackIndex] = containerEnd; + state.declarationListContainerEndStack[state.stackIndex] = declarationListContainerEnd; + const emitComments = state.shouldEmitCommentsStack[state.stackIndex] = shouldEmitComments(node); + const emitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex] = shouldEmitSourceMaps(node); + onBeforeEmitNode?.(node); + beforeEmitWithContext(node, emitComments, emitSourceMaps); } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - // Emit only the first encountered shebang - if (emitShebangIfNeeded(sourceFile)) { - return true; - } + else { + state = { + stackIndex: 0, + preserveSourceNewlinesStack: [undefined], + containerPosStack: [-1], + containerEndStack: [-1], + declarationListContainerEndStack: [-1], + shouldEmitCommentsStack: [false], + shouldEmitSourceMapsStack: [false], + }; } + return state; } - } - - // - // Helpers - // - - function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { - if (!node) return; - const savedWrite = write; - write = writer; - emit(node); - write = savedWrite; - } - function emitModifiers(node: Node, modifiers: NodeArray | undefined) { - if (modifiers && modifiers.length) { - emitList(node, modifiers, ListFormat.Modifiers); - writeSpace(); + function onLeft(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { + return maybeEmitExpression(next, parent, "left"); } - } - function emitTypeAnnotation(node: TypeNode | undefined) { - if (node) { - writePunctuation(":"); - writeSpace(); - emit(node); + function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) { + const isCommaOperator = operatorToken.kind !== SyntaxKind.CommaToken; + const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken); + const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right); + writeLinesAndIndent(linesBeforeOperator, isCommaOperator); + emitLeadingCommentsOfPosition(operatorToken.pos); + writeTokenNode(operatorToken, operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); + emitTrailingCommentsOfPosition(operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts + writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true); } - } - function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node) { - if (node) { - writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); - writeSpace(); - emit(node); + function onRight(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { + return maybeEmitExpression(next, parent, "right"); } - } - function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: T | undefined, emit: (node: T) => void) { - if (node) { - prefixWriter(prefix); - emit(node); + function onExit(node: BinaryExpression, state: WorkArea) { + const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken); + const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right); + decreaseIndentIf(linesBeforeOperator, linesAfterOperator); + if (state.stackIndex > 0) { + const savedPreserveSourceNewlines = state.preserveSourceNewlinesStack[state.stackIndex]; + const savedContainerPos = state.containerPosStack[state.stackIndex]; + const savedContainerEnd = state.containerEndStack[state.stackIndex]; + const savedDeclarationListContainerEnd = state.declarationListContainerEndStack[state.stackIndex]; + const shouldEmitComments = state.shouldEmitCommentsStack[state.stackIndex]; + const shouldEmitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex]; + afterEmitWithContext(node, shouldEmitComments, shouldEmitSourceMaps, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd, savedPreserveSourceNewlines); + onAfterEmitNode?.(node); + state.stackIndex--; + } } - } - function emitWithLeadingSpace(node: Node | undefined) { - if (node) { - writeSpace(); - emit(node); - } - } + function maybeEmitExpression(next: Expression, parent: BinaryExpression, side: "left" | "right") { + const parenthesizerRule = side === "left" ? + getBinaryLeftParenthesizer(parent.operatorToken.kind) : + getBinaryRightParenthesizer(parent.operatorToken.kind); - function emitExpressionWithLeadingSpace(node: Expression | undefined) { - if (node) { - writeSpace(); - emit(node); + if (!shouldEmitWithNotification(next)) { + if (shouldEmitWithSubstitution()) { + const substitute = cast(substituteNode(EmitHint.Expression, next), isExpression); + if (substitute !== next) { + next = parenthesizerRule(substitute); + } + } + + // Push a new frame for binary expressions, otherwise emit all other expressions. + if (isBinaryExpression(next)) { + return next; + } + } + + emitExpression(next, parenthesizerRule); } } - function emitWithTrailingSpace(node: Node | undefined) { - if (node) { - emit(node); - writeSpace(); + function getBinaryLeftParenthesizer(operatorKind: BinaryOperator) { + binaryLeftOperandParenthesizerCache ||= new Map(); + let parenthesizerRule = binaryLeftOperandParenthesizerCache.get(operatorKind); + if (!parenthesizerRule) { + parenthesizerRule = node => parenthesizer.parenthesizeLeftSideOfBinary(operatorKind, node); + binaryLeftOperandParenthesizerCache.set(operatorKind, parenthesizerRule); } + return parenthesizerRule; } - function emitEmbeddedStatement(parent: Node, node: Statement) { - if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { - writeSpace(); - emit(node); - } - else { - writeLine(); - increaseIndent(); - if (isEmptyStatement(node)) { - emitWithContext(node, emitEmbeddedEmptyStatement); - } - else { - emit(node); - } - decreaseIndent(); + function getBinaryRightParenthesizer(operatorKind: BinaryOperator) { + binaryRightOperandParenthesizerCache ||= new Map(); + let parenthesizerRule = binaryRightOperandParenthesizerCache.get(operatorKind); + if (!parenthesizerRule) { + parenthesizerRule = node => parenthesizer.parenthesizeRightSideOfBinary(operatorKind, /*leftSide*/ undefined, node); + binaryRightOperandParenthesizerCache.set(operatorKind, parenthesizerRule); } + return parenthesizerRule; } - function emitDecorators(parentNode: Node, decorators: NodeArray | undefined) { - emitList(parentNode, decorators, ListFormat.Decorators); - } + function emitConditionalExpression(node: ConditionalExpression) { + const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken); + const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue); + const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken); + const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse); - function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { - emitList(parentNode, typeArguments, ListFormat.TypeArguments); - } + emitExpression(node.condition, parenthesizer.parenthesizeConditionOfConditionalExpression); + writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); + emit(node.questionToken); + writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenTrue, parenthesizer.parenthesizeBranchOfConditionalExpression); + decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion); - function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { - if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures - return emitTypeArguments(parentNode, parentNode.typeArguments); - } - emitList(parentNode, typeParameters, ListFormat.TypeParameters); + writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true); + emit(node.colonToken); + writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenFalse, parenthesizer.parenthesizeBranchOfConditionalExpression); + decreaseIndentIf(linesBeforeColon, linesAfterColon); } - function emitParameters(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.Parameters); + function emitTemplateExpression(node: TemplateExpression) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); } - function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - const parameter = singleOrUndefined(parameters); - return parameter - && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter - && isArrowFunction(parentNode) // only arrow functions may have simple arrow head - && !parentNode.type // arrow function may not have return type annotation - && !some(parentNode.decorators) // parent may not have decorators - && !some(parentNode.modifiers) // parent may not have modifiers - && !some(parentNode.typeParameters) // parent may not have type parameters - && !some(parameter.decorators) // parameter may not have decorators - && !some(parameter.modifiers) // parameter may not have modifiers - && !parameter.dotDotDotToken // parameter may not be rest - && !parameter.questionToken // parameter may not be optional - && !parameter.type // parameter may not have a type annotation - && !parameter.initializer // parameter may not have an initializer - && isIdentifier(parameter.name); // parameter name must be identifier + function emitYieldExpression(node: YieldExpression) { + emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); + emit(node.asteriskToken); + emitExpressionWithLeadingSpace(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); } - function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - if (canEmitSimpleArrowHead(parentNode, parameters)) { - emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); - } - else { - emitParameters(parentNode, parameters); - } + function emitSpreadElement(node: SpreadElement) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); } - function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + function emitClassExpression(node: ClassExpression) { + generateNameIfNeeded(node.name); + emitClassDeclarationOrExpression(node); } - function writeDelimiter(format: ListFormat) { - switch (format & ListFormat.DelimitersMask) { - case ListFormat.None: - break; - case ListFormat.CommaDelimited: - writePunctuation(","); - break; - case ListFormat.BarDelimited: - writeSpace(); - writePunctuation("|"); - break; - case ListFormat.AsteriskDelimited: - writeSpace(); - writePunctuation("*"); - writeSpace(); - break; - case ListFormat.AmpersandDelimited: - writeSpace(); - writePunctuation("&"); - break; - } + function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emitTypeArguments(node, node.typeArguments); } - function emitList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, start = 0, count = children ? children.length - start : 0) { - const isUndefined = children === undefined; - if (isUndefined && format & ListFormat.OptionalIfUndefined) { - return; - } - - const isEmpty = children === undefined || start >= children.length || count === 0; - if (isEmpty && format & ListFormat.OptionalIfEmpty) { - if (onBeforeEmitNodeArray) { - onBeforeEmitNodeArray(children); - } - if (onAfterEmitNodeArray) { - onAfterEmitNodeArray(children); - } - return; - } - - if (format & ListFormat.BracketsMask) { - writePunctuation(getOpeningBracket(format)); - if (isEmpty && children) { - emitTrailingCommentsOfPosition(children.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists - } - } - - if (onBeforeEmitNodeArray) { - onBeforeEmitNodeArray(children); - } - - if (isEmpty) { - // Write a line terminator if the parent node was multi-line - if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!)))) { - writeLine(); - } - else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { - writeSpace(); - } + function emitAsExpression(node: AsExpression) { + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + if (node.type) { + writeSpace(); + writeKeyword("as"); + writeSpace(); + emit(node.type); } - else { - Debug.type>(children); - // Write the opening line terminator or leading whitespace. - const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; - let shouldEmitInterveningComments = mayEmitInterveningComments; - const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children, format); // TODO: GH#18217 - if (leadingLineTerminatorCount) { - writeLine(leadingLineTerminatorCount); - shouldEmitInterveningComments = false; - } - else if (format & ListFormat.SpaceBetweenBraces) { - writeSpace(); - } - - // Increase the indent, if requested. - if (format & ListFormat.Indented) { - increaseIndent(); - } - - // Emit each child. - let previousSibling: Node | undefined; - let previousSourceFileTextKind: ReturnType; - let shouldDecreaseIndentAfterEmit = false; - for (let i = 0; i < count; i++) { - const child = children[start + i]; + } - // Write the delimiter if this is not the first node. - if (format & ListFormat.AsteriskDelimited) { - // always write JSDoc in the format "\n *" - writeLine(); - writeDelimiter(format); - } - else if (previousSibling) { - // i.e - // function commentedParameters( - // /* Parameter a */ - // a - // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline - // , - if (format & ListFormat.DelimitersMask && previousSibling.end !== (parentNode ? parentNode.end : -1)) { - emitLeadingCommentsOfPosition(previousSibling.end); - } - writeDelimiter(format); - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + function emitNonNullExpression(node: NonNullExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + writeOperator("!"); + } - // Write either a line terminator or whitespace to separate the elements. - const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format); - if (separatingLineTerminatorCount > 0) { - // If a synthesized node in a single-line list starts on a new - // line, we should increase the indent. - if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { - increaseIndent(); - shouldDecreaseIndentAfterEmit = true; - } + function emitMetaProperty(node: MetaProperty) { + writeToken(node.keywordToken, node.pos, writePunctuation); + writePunctuation("."); + emit(node.name); + } - writeLine(separatingLineTerminatorCount); - shouldEmitInterveningComments = false; - } - else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { - writeSpace(); - } - } + // + // Misc + // - // Emit this child. - previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); - if (shouldEmitInterveningComments) { - if (emitTrailingCommentsOfPosition) { - const commentRange = getCommentRange(child); - emitTrailingCommentsOfPosition(commentRange.pos); - } - } - else { - shouldEmitInterveningComments = mayEmitInterveningComments; - } + function emitTemplateSpan(node: TemplateSpan) { + emitExpression(node.expression); + emit(node.literal); + } - nextListElementPos = child.pos; - emit(child); + // + // Statements + // - if (shouldDecreaseIndentAfterEmit) { - decreaseIndent(); - shouldDecreaseIndentAfterEmit = false; - } + function emitBlock(node: Block) { + emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); + } - previousSibling = child; - } + function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); + const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; + emitList(node, node.statements, format); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); + } - // Write a trailing comma, if requested. - const emitFlags = previousSibling ? getEmitFlags(previousSibling) : 0; - const skipTrailingComments = commentsDisabled || !!(emitFlags & EmitFlags.NoTrailingComments); - const hasTrailingComma = children?.hasTrailingComma && (format & ListFormat.AllowTrailingComma) && (format & ListFormat.CommaDelimited); - if (hasTrailingComma) { - if (previousSibling && !skipTrailingComments) { - emitTokenWithComment(SyntaxKind.CommaToken, previousSibling.end, writePunctuation, previousSibling); - } - else { - writePunctuation(","); - } - } + function emitVariableStatement(node: VariableStatement) { + emitModifiers(node, node.modifiers); + emit(node.declarationList); + writeTrailingSemicolon(); + } - // Emit any trailing comment of the last element in the list - // i.e - // var array = [... - // 2 - // /* end of element 2 */ - // ]; - if (previousSibling && (parentNode ? parentNode.end : -1) !== previousSibling.end && (format & ListFormat.DelimitersMask) && !skipTrailingComments) { - emitLeadingCommentsOfPosition(hasTrailingComma && children?.end ? children.end : previousSibling.end); - } + function emitEmptyStatement(isEmbeddedStatement: boolean) { + // While most trailing semicolons are possibly insignificant, an embedded "empty" + // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. + if (isEmbeddedStatement) { + writePunctuation(";"); + } + else { + writeTrailingSemicolon(); + } + } - // Decrease the indent, if requested. - if (format & ListFormat.Indented) { - decreaseIndent(); - } + function emitEmbeddedEmptyStatement(_hint: EmitHint, _node: EmptyStatement) { + emitEmptyStatement(/*isEmbeddedStatement*/ true); + } - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + function emitExpressionStatement(node: ExpressionStatement) { + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfExpressionStatement); + // Emit semicolon in non json files + // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) + if (!isJsonSourceFile(currentSourceFile!) || nodeIsSynthesized(node.expression)) { + writeTrailingSemicolon(); + } + } - // Write the closing line terminator or closing whitespace. - const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children, format); - if (closingLineTerminatorCount) { - writeLine(closingLineTerminatorCount); - } - else if (format & (ListFormat.SpaceAfterList | ListFormat.SpaceBetweenBraces)) { + function emitIfStatement(node: IfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.thenStatement); + if (node.elseStatement) { + writeLineOrSpace(node, node.thenStatement, node.elseStatement); + emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); + if (node.elseStatement.kind === SyntaxKind.IfStatement) { writeSpace(); + emit(node.elseStatement); + } + else { + emitEmbeddedStatement(node, node.elseStatement); } } + } - if (onAfterEmitNodeArray) { - onAfterEmitNodeArray(children); - } + function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { + const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + } - if (format & ListFormat.BracketsMask) { - if (isEmpty && children) { - emitLeadingCommentsOfPosition(children.end); // Emit leading comments within empty lists - } - writePunctuation(getClosingBracket(format)); + function emitDoStatement(node: DoStatement) { + emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); + emitEmbeddedStatement(node, node.statement); + if (isBlock(node.statement) && !preserveSourceNewlines) { + writeSpace(); } + else { + writeLineOrSpace(node, node.statement, node.expression); + } + + emitWhileClause(node, node.statement.end); + writeTrailingSemicolon(); } - // Writers + function emitWhileStatement(node: WhileStatement) { + emitWhileClause(node, node.pos); + emitEmbeddedStatement(node, node.statement); + } - function writeLiteral(s: string) { - writer.writeLiteral(s); + function emitForStatement(node: ForStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); + emitForBinding(node.initializer); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.condition); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.incrementor); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); } - function writeStringLiteral(s: string) { - writer.writeStringLiteral(s); + function emitForInStatement(node: ForInStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); } - function writeBase(s: string) { - writer.write(s); + function emitForOfStatement(node: ForOfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitWithTrailingSpace(node.awaitModifier); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); } - function writeSymbol(s: string, sym: Symbol) { - writer.writeSymbol(s, sym); + function emitForBinding(node: VariableDeclarationList | Expression | undefined) { + if (node !== undefined) { + if (node.kind === SyntaxKind.VariableDeclarationList) { + emit(node); + } + else { + emitExpression(node); + } + } } - function writePunctuation(s: string) { - writer.writePunctuation(s); + function emitContinueStatement(node: ContinueStatement) { + emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); } - function writeTrailingSemicolon() { - writer.writeTrailingSemicolon(";"); + function emitBreakStatement(node: BreakStatement) { + emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); } - function writeKeyword(s: string) { - writer.writeKeyword(s); + function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { + const node = getParseTreeNode(contextNode); + const isSimilarNode = node && node.kind === contextNode.kind; + const startPos = pos; + if (isSimilarNode && currentSourceFile) { + pos = skipTrivia(currentSourceFile.text, pos); + } + if (isSimilarNode && contextNode.pos !== startPos) { + const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); + if (needsIndent) { + increaseIndent(); + } + emitLeadingCommentsOfPosition(startPos); + if (needsIndent) { + decreaseIndent(); + } + } + pos = writeTokenText(token, writer, pos); + if (isSimilarNode && contextNode.end !== pos) { + const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression; + emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext); + } + return pos; } - function writeOperator(s: string) { - writer.writeOperator(s); + function emitReturnStatement(node: ReturnStatement) { + emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); + emitExpressionWithLeadingSpace(node.expression); + writeTrailingSemicolon(); } - function writeParameter(s: string) { - writer.writeParameter(s); + function emitWithStatement(node: WithStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); } - function writeComment(s: string) { - writer.writeComment(s); + function emitSwitchStatement(node: SwitchStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + writeSpace(); + emit(node.caseBlock); } - function writeSpace() { - writer.writeSpace(" "); + function emitLabeledStatement(node: LabeledStatement) { + emit(node.label); + emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); + writeSpace(); + emit(node.statement); } - function writeProperty(s: string) { - writer.writeProperty(s); + function emitThrowStatement(node: ThrowStatement) { + emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); + emitExpressionWithLeadingSpace(node.expression); + writeTrailingSemicolon(); + } + + function emitTryStatement(node: TryStatement) { + emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); + writeSpace(); + emit(node.tryBlock); + if (node.catchClause) { + writeLineOrSpace(node, node.tryBlock, node.catchClause); + emit(node.catchClause); + } + if (node.finallyBlock) { + writeLineOrSpace(node, node.catchClause || node.tryBlock, node.finallyBlock); + emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); + writeSpace(); + emit(node.finallyBlock); + } + } + + function emitDebuggerStatement(node: DebuggerStatement) { + writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); + writeTrailingSemicolon(); + } + + // + // Declarations + // + + function emitVariableDeclaration(node: VariableDeclaration) { + emit(node.name); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); } - function writeLine(count = 1) { - for (let i = 0; i < count; i++) { - writer.writeLine(i > 0); - } + function emitVariableDeclarationList(node: VariableDeclarationList) { + writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); + writeSpace(); + emitList(node, node.declarations, ListFormat.VariableDeclarationList); } - function increaseIndent() { - writer.increaseIndent(); + function emitFunctionDeclaration(node: FunctionDeclaration) { + emitFunctionDeclarationOrExpression(node); } - function decreaseIndent() { - writer.decreaseIndent(); + function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("function"); + emit(node.asteriskToken); + writeSpace(); + emitIdentifierName(node.name); + emitSignatureAndBody(node, emitSignatureHead); } - function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { - return !sourceMapsDisabled - ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) - : writeTokenText(token, writer, pos); - } + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { + const body = node.body; + if (body) { + if (isBlock(body)) { + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } - function writeTokenNode(node: Node, writer: (s: string) => void) { - if (onBeforeEmitToken) { - onBeforeEmitToken(node); - } - writer(tokenToString(node.kind)!); - if (onAfterEmitToken) { - onAfterEmitToken(node); - } - } + pushNameGenerationScope(node); + forEach(node.parameters, generateNames); + generateNames(node.body); - function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { - const tokenString = tokenToString(token)!; - writer(tokenString); - return pos! < 0 ? pos! : pos! + tokenString.length; - } + emitSignatureHead(node); + emitBlockFunctionBody(body); + popNameGenerationScope(node); - function writeLineOrSpace(parentNode: Node, prevChildNode: Node, nextChildNode: Node) { - if (getEmitFlags(parentNode) & EmitFlags.SingleLine) { - writeSpace(); - } - else if (preserveSourceNewlines) { - const lines = getLinesBetweenNodes(parentNode, prevChildNode, nextChildNode); - if (lines) { - writeLine(lines); + if (indentedFlag) { + decreaseIndent(); + } } else { + emitSignatureHead(node); writeSpace(); + emitExpression(body, parenthesizer.parenthesizeConciseBodyOfArrowFunction); } } else { - writeLine(); - } - } - - function writeLines(text: string): void { - const lines = text.split(/\r\n?|\n/g); - const indentation = guessIndentation(lines); - for (const lineText of lines) { - const line = indentation ? lineText.slice(indentation) : lineText; - if (line.length) { - writeLine(); - write(line); - } - } - } - - function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) { - if (lineCount) { - increaseIndent(); - writeLine(lineCount); - } - else if (writeSpaceIfNotIndenting) { - writeSpace(); + emitSignatureHead(node); + writeTrailingSemicolon(); } - } - // Helper function to decrease the indent if we previously indented. Allows multiple - // previous indent values to be considered at a time. This also allows caller to just - // call this once, passing in all their appropriate indent values, instead of needing - // to call this helper function multiple times. - function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) { - if (value1) { - decreaseIndent(); - } - if (value2) { - decreaseIndent(); - } } - function getLeadingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (format & ListFormat.PreferNewLine) { - return 1; - } - - const firstChild = children[0]; - if (firstChild === undefined) { - return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; - } - if (firstChild.pos === nextListElementPos) { - // If this child starts at the beginning of a list item in a parent list, its leading - // line terminators have already been written as the separating line terminators of the - // parent list. Example: - // - // class Foo { - // constructor() {} - // public foo() {} - // } - // - // The outer list is the list of class members, with one line terminator between the - // constructor and the method. The constructor is written, the separating line terminator - // is written, and then we start emitting the method. Its modifiers ([public]) constitute an inner - // list, so we look for its leading line terminators. If we didn't know that we had already - // written a newline as part of the parent list, it would appear that we need to write a - // leading newline to start the modifiers. - return 0; - } - if (firstChild.kind === SyntaxKind.JsxText) { - // JsxText will be written with its leading whitespace, so don't add more manually. - return 0; - } - if (parentNode && - !positionIsSynthesized(parentNode.pos) && - !nodeIsSynthesized(firstChild) && - (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) - ) { - if (preserveSourceNewlines) { - return getEffectiveLines( - includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter( - firstChild.pos, - parentNode.pos, - currentSourceFile!, - includeComments)); - } - return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!) ? 0 : 1; - } - if (synthesizedNodeStartsOnNewLine(firstChild, format)) { - return 1; - } - } - return format & ListFormat.MultiLine ? 1 : 0; + function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); } - function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (previousNode === undefined || nextNode === undefined) { - return 0; - } - if (nextNode.kind === SyntaxKind.JsxText) { - // JsxText will be written with its leading whitespace, so don't add more manually. - return 0; - } - else if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { - return getEffectiveLines( - includeComments => getLinesBetweenRangeEndAndRangeStart( - previousNode, - nextNode, - currentSourceFile!, - includeComments)); - } - else if (!preserveSourceNewlines && !nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { - return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!) ? 0 : 1; - } - else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) { - return 1; - } - } - else if (getStartsOnNewLine(nextNode)) { - return 1; - } - return format & ListFormat.MultiLine ? 1 : 0; - } + function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { + // We must emit a function body as a single-line body in the following case: + // * The body has NodeEmitFlags.SingleLine specified. - function getClosingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (format & ListFormat.PreferNewLine) { - return 1; - } + // We must emit a function body as a multi-line body in the following cases: + // * The body is explicitly marked as multi-line. + // * A non-synthesized body's start and end position are on different lines. + // * Any statement in the body starts on a new line. - const lastChild = lastOrUndefined(children); - if (lastChild === undefined) { - return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; - } - if (parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { - if (preserveSourceNewlines) { - const end = isNodeArray(children) && !positionIsSynthesized(children.end) ? children.end : lastChild.end; - return getEffectiveLines( - includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter( - end, - parentNode.end, - currentSourceFile!, - includeComments)); - } - return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!) ? 0 : 1; - } - if (synthesizedNodeStartsOnNewLine(lastChild, format)) { - return 1; - } - } - if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) { - return 1; + if (getEmitFlags(body) & EmitFlags.SingleLine) { + return true; } - return 0; - } - function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) { - // If 'preserveSourceNewlines' is disabled, we should never call this function - // because it could be more expensive than alternative approximations. - Debug.assert(!!preserveSourceNewlines); - // We start by measuring the line difference from a position to its adjacent comments, - // so that this is counted as a one-line difference, not two: - // - // node1; - // // NODE2 COMMENT - // node2; - const lines = getLineDifference(/*includeComments*/ true); - if (lines === 0) { - // However, if the line difference considering comments was 0, we might have this: - // - // node1; // NODE2 COMMENT - // node2; - // - // in which case we should be ignoring node2's comment, so this too is counted as - // a one-line difference, not zero. - return getLineDifference(/*includeComments*/ false); - } - return lines; - } + if (body.multiLine) { + return false; + } - function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { - const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, [node], ListFormat.None); - if (leadingNewlines) { - writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false); + if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile!)) { + return false; } - return !!leadingNewlines; - } - function writeLineSeparatorsAfter(node: Node, parent: Node) { - const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, [node], ListFormat.None); - if (trailingNewlines) { - writeLine(trailingNewlines); + if (getLeadingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines) + || getClosingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines)) { + return false; } - } - function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { - if (nodeIsSynthesized(node)) { - const startsOnNewLine = getStartsOnNewLine(node); - if (startsOnNewLine === undefined) { - return (format & ListFormat.PreferNewLine) !== 0; + let previousStatement: Statement | undefined; + for (const statement of body.statements) { + if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) { + return false; } - return startsOnNewLine; + previousStatement = statement; } - return (format & ListFormat.PreferNewLine) !== 0; + return true; } - function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number { - if (getEmitFlags(parent) & EmitFlags.NoIndentation) { - return 0; - } + function emitBlockFunctionBody(body: Block) { + onBeforeEmitNode?.(body); + writeSpace(); + writePunctuation("{"); + increaseIndent(); - parent = skipSynthesizedParentheses(parent); - node1 = skipSynthesizedParentheses(node1); - node2 = skipSynthesizedParentheses(node2); + const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) + ? emitBlockFunctionBodyOnSingleLine + : emitBlockFunctionBodyWorker; - // Always use a newline for synthesized code if the synthesizer desires it. - if (getStartsOnNewLine(node2)) { - return 1; + if (emitBodyWithDetachedComments) { + emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); } - - if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { - if (preserveSourceNewlines) { - return getEffectiveLines( - includeComments => getLinesBetweenRangeEndAndRangeStart( - node1, - node2, - currentSourceFile!, - includeComments)); - } - return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!) ? 0 : 1; + else { + emitBlockFunctionBody(body); } - return 0; - } - - function isEmptyBlock(block: BlockLike) { - return block.statements.length === 0 - && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile!); + decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); + onAfterEmitNode?.(body); } - function skipSynthesizedParentheses(node: Node) { - while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { - node = (node).expression; - } - - return node; + function emitBlockFunctionBodyOnSingleLine(body: Block) { + emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); } - function getTextOfNode(node: Node, includeTrivia?: boolean): string { - if (isGeneratedIdentifier(node)) { - return generateName(node); - } - else if ((isIdentifier(node) || isPrivateIdentifier(node)) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) { - return idText(node); - } - else if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { - return getTextOfNode((node).textSourceNode!, includeTrivia); + function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { + // Emit all the prologue directives (like "use strict"). + const statementOffset = emitPrologueDirectives(body.statements); + const pos = writer.getTextPos(); + emitHelpers(body); + if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { + decreaseIndent(); + emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); + increaseIndent(); } - else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { - return node.text; + else { + emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, /*parenthesizerRule*/ undefined, statementOffset); } - - return getSourceTextOfNodeFromSourceFile(currentSourceFile!, node, includeTrivia); } - function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { - if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { - const textSourceNode = (node).textSourceNode!; - if (isIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) { - const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode); - return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` : - neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` : - `"${escapeNonAsciiString(text)}"`; - } - else { - return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); - } - } - - const flags = (neverAsciiEscape ? GetLiteralTextFlags.NeverAsciiEscape : 0) - | (jsxAttributeEscape ? GetLiteralTextFlags.JsxAttributeEscape : 0) - | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) - | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); - - return getLiteralText(node, currentSourceFile!, flags); + function emitClassDeclaration(node: ClassDeclaration) { + emitClassDeclarationOrExpression(node); } - /** - * Push a new name generation scope. - */ - function pushNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; + function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { + forEach(node.members, generateMemberNames); + + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("class"); + if (node.name) { + writeSpace(); + emitIdentifierName(node.name); } - tempFlagsStack.push(tempFlags); - tempFlags = 0; - reservedNamesStack.push(reservedNames); - } - /** - * Pop the current name generation scope. - */ - function popNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); } - tempFlags = tempFlagsStack.pop()!; - reservedNames = reservedNamesStack.pop()!; - } - function reserveNameInNestedScopes(name: string) { - if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { - reservedNames = new Set(); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); + + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.ClassMembers); + writePunctuation("}"); + + if (indentedFlag) { + decreaseIndent(); } - reservedNames.add(name); } - function generateNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.Block: - forEach((node).statements, generateNames); - break; - case SyntaxKind.LabeledStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - generateNames((node).statement); - break; - case SyntaxKind.IfStatement: - generateNames((node).thenStatement); - generateNames((node).elseStatement); - break; - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - generateNames((node).initializer); - generateNames((node).statement); - break; - case SyntaxKind.SwitchStatement: - generateNames((node).caseBlock); - break; - case SyntaxKind.CaseBlock: - forEach((node).clauses, generateNames); - break; - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - forEach((node).statements, generateNames); - break; - case SyntaxKind.TryStatement: - generateNames((node).tryBlock); - generateNames((node).catchClause); - generateNames((node).finallyBlock); - break; - case SyntaxKind.CatchClause: - generateNames((node).variableDeclaration); - generateNames((node).block); - break; - case SyntaxKind.VariableStatement: - generateNames((node).declarationList); - break; - case SyntaxKind.VariableDeclarationList: - forEach((node).declarations, generateNames); - break; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - generateNameIfNeeded((node).name); - break; - case SyntaxKind.FunctionDeclaration: - generateNameIfNeeded((node).name); - if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - forEach((node).parameters, generateNames); - generateNames((node).body); - } - break; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - forEach((node).elements, generateNames); - break; - case SyntaxKind.ImportDeclaration: - generateNames((node).importClause); - break; - case SyntaxKind.ImportClause: - generateNameIfNeeded((node).name); - generateNames((node).namedBindings); - break; - case SyntaxKind.NamespaceImport: - generateNameIfNeeded((node).name); - break; - case SyntaxKind.NamespaceExport: - generateNameIfNeeded((node).name); - break; - case SyntaxKind.NamedImports: - forEach((node).elements, generateNames); - break; - case SyntaxKind.ImportSpecifier: - generateNameIfNeeded((node).propertyName || (node).name); - break; - } + function emitInterfaceDeclaration(node: InterfaceDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("interface"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.HeritageClauses); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.InterfaceMembers); + writePunctuation("}"); } - function generateMemberNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - generateNameIfNeeded((node).name); - break; + function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { + emitDecorators(node, node.decorators); + emitModifiers(node, node.modifiers); + writeKeyword("type"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + writeSpace(); + writePunctuation("="); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + } + + function emitEnumDeclaration(node: EnumDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("enum"); + writeSpace(); + emit(node.name); + + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.EnumMembers); + writePunctuation("}"); + } + + function emitModuleDeclaration(node: ModuleDeclaration) { + emitModifiers(node, node.modifiers); + if (~node.flags & NodeFlags.GlobalAugmentation) { + writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); + writeSpace(); + } + emit(node.name); + + let body = node.body; + if (!body) return writeTrailingSemicolon(); + while (body && isModuleDeclaration(body)) { + writePunctuation("."); + emit(body.name); + body = body.body; } + + writeSpace(); + emit(body); } - function generateNameIfNeeded(name: DeclarationName | undefined) { - if (name) { - if (isGeneratedIdentifier(name)) { - generateName(name); - } - else if (isBindingPattern(name)) { - generateNames(name); - } + function emitModuleBlock(node: ModuleBlock) { + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); + popNameGenerationScope(node); + } + + function emitCaseBlock(node: CaseBlock) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emitList(node, node.clauses, ListFormat.CaseBlockClauses); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + } + + function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.isTypeOnly) { + emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); + writeSpace(); } + emit(node.name); + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); + writeSpace(); + emitModuleReference(node.moduleReference); + writeTrailingSemicolon(); } - /** - * Generate the text for a generated identifier. - */ - function generateName(name: GeneratedIdentifier) { - if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { - // Node names generate unique names based on their original node - // and are cached based on that node's id. - return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags); + function emitModuleReference(node: ModuleReference) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } else { - // Auto, Loop, and Unique names are cached based on their unique - // autoGenerateId. - const autoGenerateId = name.autoGenerateId!; - return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); + emit(node); } } - function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) { - const nodeId = getNodeId(node); - return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags)); + function emitImportDeclaration(node: ImportDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.importClause) { + emit(node.importClause); + writeSpace(); + emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); + writeSpace(); + } + emitExpression(node.moduleSpecifier); + writeTrailingSemicolon(); } - /** - * Returns a value indicating whether a name is unique globally, within the current file, - * or within the NameGenerator. - */ - function isUniqueName(name: string): boolean { - return isFileLevelUniqueName(name) - && !generatedNames.has(name) - && !(reservedNames && reservedNames.has(name)); + function emitImportClause(node: ImportClause) { + if (node.isTypeOnly) { + emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); + writeSpace(); + } + emit(node.name); + if (node.name && node.namedBindings) { + emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); + writeSpace(); + } + emit(node.namedBindings); } - /** - * Returns a value indicating whether a name is unique globally or within the current file. - */ - function isFileLevelUniqueName(name: string) { - return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; + function emitNamespaceImport(node: NamespaceImport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); } - /** - * Returns a value indicating whether a name is unique within a container. - */ - function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { - if (node.locals) { - const local = node.locals.get(escapeLeadingUnderscores(name)); - // We conservatively include alias symbols to cover cases where they're emitted as locals - if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { - return false; - } - } - } - return true; + function emitNamedImports(node: NamedImports) { + emitNamedImportsOrExports(node); } - /** - * Return the next available name in the pattern _a ... _z, _0, _1, ... - * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. - * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. - */ - function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { - if (flags && !(tempFlags & flags)) { - const name = flags === TempFlags._i ? "_i" : "_n"; - if (isUniqueName(name)) { - tempFlags |= flags; - if (reservedInNestedScopes) { - reserveNameInNestedScopes(name); - } - return name; - } + function emitImportSpecifier(node: ImportSpecifier) { + emitImportOrExportSpecifier(node); + } + + function emitExportAssignment(node: ExportAssignment) { + const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isExportEquals) { + emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); } - while (true) { - const count = tempFlags & TempFlags.CountMask; - tempFlags++; - // Skip over 'i' and 'n' - if (count !== 8 && count !== 13) { - const name = count < 26 - ? "_" + String.fromCharCode(CharacterCodes.a + count) - : "_" + (count - 26); - if (isUniqueName(name)) { - if (reservedInNestedScopes) { - reserveNameInNestedScopes(name); - } - return name; - } - } + else { + emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); } + writeSpace(); + emitExpression(node.expression, node.isExportEquals ? + getBinaryRightParenthesizer(SyntaxKind.EqualsToken) : + parenthesizer.parenthesizeExpressionOfExportDefault); + writeTrailingSemicolon(); } - /** - * Generate a name that is unique within the current file and doesn't conflict with any names - * in global scope. The name is formed by adding an '_n' suffix to the specified base name, - * where n is a positive integer. Note that names generated by makeTempVariableName and - * makeUniqueName are guaranteed to never conflict. - * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' - */ - function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string { - if (optimistic) { - if (checkFn(baseName)) { - if (scoped) { - reserveNameInNestedScopes(baseName); - } - else { - generatedNames.add(baseName); - } - return baseName; - } - } - // Find the first unique 'name_n', where n is a positive number - if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { - baseName += "_"; + function emitExportDeclaration(node: ExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isTypeOnly) { + nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node); + writeSpace(); } - let i = 1; - while (true) { - const generatedName = baseName + i; - if (checkFn(generatedName)) { - if (scoped) { - reserveNameInNestedScopes(generatedName); - } - else { - generatedNames.add(generatedName); - } - return generatedName; - } - i++; + if (node.exportClause) { + emit(node.exportClause); + } + else { + nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); + } + if (node.moduleSpecifier) { + writeSpace(); + const fromPos = node.exportClause ? node.exportClause.end : nextPos; + emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); + writeSpace(); + emitExpression(node.moduleSpecifier); } + writeTrailingSemicolon(); } - function makeFileLevelOptimisticUniqueName(name: string) { - return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true); + function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); + writeSpace(); + emit(node.name); + writeTrailingSemicolon(); } - /** - * Generates a unique name for a ModuleDeclaration or EnumDeclaration. - */ - function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { - const name = getTextOfNode(node.name); - // Use module/enum name itself if it is unique, otherwise make a unique variation - return isUniqueLocalName(name, node) ? name : makeUniqueName(name); + function emitNamespaceExport(node: NamespaceExport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); } - /** - * Generates a unique name for an ImportDeclaration or ExportDeclaration. - */ - function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { - const expr = getExternalModuleName(node)!; // TODO: GH#18217 - const baseName = isStringLiteral(expr) ? - makeIdentifierFromModuleName(expr.text) : "module"; - return makeUniqueName(baseName); + function emitNamedExports(node: NamedExports) { + emitNamedImportsOrExports(node); } - /** - * Generates a unique name for a default export. - */ - function generateNameForExportDefault() { - return makeUniqueName("default"); + function emitExportSpecifier(node: ExportSpecifier) { + emitImportOrExportSpecifier(node); } - /** - * Generates a unique name for a class expression. - */ - function generateNameForClassExpression() { - return makeUniqueName("class"); + function emitNamedImportsOrExports(node: NamedImportsOrExports) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); + writePunctuation("}"); } - function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { - if (isIdentifier(node.name)) { - return generateNameCached(node.name); + function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { + if (node.propertyName) { + emit(node.propertyName); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); + writeSpace(); } - return makeTempVariableName(TempFlags.Auto); + + emit(node.name); } - /** - * Generates a unique name from a node. - */ - function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string { - switch (node.kind) { - case SyntaxKind.Identifier: - return makeUniqueName( - getTextOfNode(node), - isUniqueName, - !!(flags! & GeneratedIdentifierFlags.Optimistic), - !!(flags! & GeneratedIdentifierFlags.ReservedInNestedScopes) - ); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - return generateNameForModuleOrEnum(node); - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - return generateNameForImportOrExportDeclaration(node); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ExportAssignment: - return generateNameForExportDefault(); - case SyntaxKind.ClassExpression: - return generateNameForClassExpression(); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return generateNameForMethodOrAccessor(node); - case SyntaxKind.ComputedPropertyName: - return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); - default: - return makeTempVariableName(TempFlags.Auto); - } + // + // Module references + // + + function emitExternalModuleReference(node: ExternalModuleReference) { + writeKeyword("require"); + writePunctuation("("); + emitExpression(node.expression); + writePunctuation(")"); } - /** - * Generates a unique identifier for a node. - */ - function makeName(name: GeneratedIdentifier) { - switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { - case GeneratedIdentifierFlags.Auto: - return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); - case GeneratedIdentifierFlags.Loop: - return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); - case GeneratedIdentifierFlags.Unique: - return makeUniqueName( - idText(name), - (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes) - ); - } + // + // JSX + // - return Debug.fail("Unsupported GeneratedIdentifierKind."); + function emitJsxElement(node: JsxElement) { + emit(node.openingElement); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingElement); } - /** - * Gets the node from which a name should be generated. - */ - function getNodeForGeneratedName(name: GeneratedIdentifier) { - const autoGenerateId = name.autoGenerateId; - let node = name as Node; - let original = node.original; - while (original) { - node = original; + function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { + writePunctuation("<"); + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emit(node.attributes); + writePunctuation("/>"); + } - // if "node" is a different generated name (having a different - // "autoGenerateId"), use it and stop traversing. - if (isIdentifier(node) - && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) - && node.autoGenerateId !== autoGenerateId) { - break; - } + function emitJsxFragment(node: JsxFragment) { + emit(node.openingFragment); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingFragment); + } - original = node.original; + function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { + writePunctuation("<"); + + if (isJsxOpeningElement(node)) { + const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node); + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + if (node.attributes.properties && node.attributes.properties.length > 0) { + writeSpace(); + } + emit(node.attributes); + writeLineSeparatorsAfter(node.attributes, node); + decreaseIndentIf(indented); } - // otherwise, return the original node for the source; - return node; + writePunctuation(">"); } - // Comments + function emitJsxText(node: JsxText) { + writer.writeLiteral(node.text); + } - function emitLeadingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number) { - enterComment(); - hasWrittenComment = false; + function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { + writePunctuation(""); + } - // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. - // It is expensive to walk entire tree just to set one kind of node to have no comments. - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + function emitJsxAttributes(node: JsxAttributes) { + emitList(node, node.properties, ListFormat.JsxElementAttributes); + } - // Save current container state on the stack. - if ((pos > 0 || end > 0) && pos !== end) { - // Emit leading comments if the position is not synthesized and the node - // has not opted out from emitting leading comments. - if (!skipLeadingComments) { - emitLeadingComments(pos, /*isEmittedNode*/ node.kind !== SyntaxKind.NotEmittedStatement); - } + function emitJsxAttributeValue(node: StringLiteral | JsxExpression): void { + if (isStringLiteral(node)) { + pipelineEmit(EmitHint.JsxAttributeValue, node, emitStringLiteralWithJsxAttributeEscape, /*parenthesizerRule*/ undefined); + } + else { + pipelineEmit(EmitHint.Unspecified, node, emitWorker, /*parenthesizerRule*/ undefined); + } + } - if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { - // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. - containerPos = pos; - } + function emitJsxAttribute(node: JsxAttribute) { + emit(node.name); + emitNodeWithPrefix("=", writePunctuation, node.initializer, emitJsxAttributeValue); + } - if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { - // As above. - containerEnd = end; + function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { + writePunctuation("{..."); + emitExpression(node.expression); + writePunctuation("}"); + } - // To avoid invalid comment emit in a down-level binding pattern, we - // keep track of the last declaration list container's end - if (node.kind === SyntaxKind.VariableDeclarationList) { - declarationListContainerEnd = end; - } - } - } - forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); - exitComment(); + function hasTrailingCommentsAtPosition(pos: number) { + let result = false; + forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); + return result; } - function emitTrailingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { - enterComment(); - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); - if ((pos > 0 || end > 0) && pos !== end) { - // Restore previous container state. - containerPos = savedContainerPos; - containerEnd = savedContainerEnd; - declarationListContainerEnd = savedDeclarationListContainerEnd; + function hasLeadingCommentsAtPosition(pos: number) { + let result = false; + forEachLeadingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); + return result; + } - // Emit trailing comments if the position is not synthesized and the node - // has not opted out from emitting leading comments and is an emitted node. - if (!skipTrailingComments && node.kind !== SyntaxKind.NotEmittedStatement) { - emitTrailingComments(end); + function hasCommentsAtPosition(pos: number) { + return hasTrailingCommentsAtPosition(pos) || hasLeadingCommentsAtPosition(pos); + } + + function emitJsxExpression(node: JsxExpression) { + if (node.expression || (!commentsDisabled && !nodeIsSynthesized(node) && hasCommentsAtPosition(node.pos))) { // preserve empty expressions if they contain comments! + const isMultiline = currentSourceFile && !nodeIsSynthesized(node) && getLineAndCharacterOfPosition(currentSourceFile, node.pos).line !== getLineAndCharacterOfPosition(currentSourceFile, node.end).line; + if (isMultiline) { + writer.increaseIndent(); + } + const end = emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emit(node.dotDotDotToken); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.expression?.end || end, writePunctuation, node); + if (isMultiline) { + writer.decreaseIndent(); } } - exitComment(); } - function emitLeadingSynthesizedComment(comment: SynthesizedComment) { - if (comment.hasLeadingNewline || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); + function emitJsxTagName(node: JsxTagNameExpression) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } else { - writer.writeSpace(" "); + emit(node); } } - function emitTrailingSynthesizedComment(comment: SynthesizedComment) { - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine) { - writer.writeLine(); - } - } + // + // Clauses + // - function writeSynthesizedComment(comment: SynthesizedComment) { - const text = formatSynthesizedComment(comment); - const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; - writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); + function emitCaseClause(node: CaseClause) { + emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + + emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); } - function formatSynthesizedComment(comment: SynthesizedComment) { - return comment.kind === SyntaxKind.MultiLineCommentTrivia - ? `/*${comment.text}*/` - : `//${comment.text}`; + function emitDefaultClause(node: DefaultClause) { + const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); + emitCaseOrDefaultClauseRest(node, node.statements, pos); } - function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { - enterComment(); - const { pos, end } = detachedRange; - const emitFlags = getEmitFlags(node); - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; - if (!skipLeadingComments) { - emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); - } + function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { + const emitAsSingleStatement = + statements.length === 1 && + ( + // treat synthesized nodes as located on the same line for emit purposes + nodeIsSynthesized(parentNode) || + nodeIsSynthesized(statements[0]) || + rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile!) + ); - exitComment(); - if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { - commentsDisabled = true; - emitCallback(node); - commentsDisabled = false; + let format = ListFormat.CaseOrDefaultClauseStatements; + if (emitAsSingleStatement) { + writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); + writeSpace(); + format &= ~(ListFormat.MultiLine | ListFormat.Indented); } else { - emitCallback(node); + emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); } + emitList(parentNode, statements, format); + } - enterComment(); - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); - } + function emitHeritageClause(node: HeritageClause) { + writeSpace(); + writeTokenText(node.token, writeKeyword); + writeSpace(); + emitList(node, node.types, ListFormat.HeritageClauseTypes); + } + + function emitCatchClause(node: CatchClause) { + const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.variableDeclaration) { + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emit(node.variableDeclaration); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); + writeSpace(); } - exitComment(); + emit(node.block); + } + // + // Property assignments + // + + function emitPropertyAssignment(node: PropertyAssignment) { + emit(node.name); + writePunctuation(":"); + writeSpace(); + // This is to ensure that we emit comment in the following case: + // For example: + // obj = { + // id: /*comment1*/ ()=>void + // } + // "comment1" is not considered to be leading comment for node.initializer + // but rather a trailing comment on the previous node. + const initializer = node.initializer; + if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(initializer); + emitTrailingCommentsOfPosition(commentRange.pos); + } + emitExpression(initializer, parenthesizer.parenthesizeExpressionForDisallowedComma); } - function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) { - if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) { - return false; + function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { + emit(node.name); + if (node.objectAssignmentInitializer) { + writeSpace(); + writePunctuation("="); + writeSpace(); + emitExpression(node.objectAssignmentInitializer, parenthesizer.parenthesizeExpressionForDisallowedComma); } + } - if (nextNode.pos < previousNode.end) { - return false; + function emitSpreadAssignment(node: SpreadAssignment) { + if (node.expression) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); } + } - previousNode = getOriginalNode(previousNode); - nextNode = getOriginalNode(nextNode); - const parent = previousNode.parent; - if (!parent || parent !== nextNode.parent) { - return false; + // + // Enum + // + + function emitEnumMember(node: EnumMember) { + emit(node.name); + emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + // + // JSDoc + // + function emitJSDoc(node: JSDoc) { + write("/**"); + if (node.comment) { + const text = getTextOfJSDocComment(node.comment); + if (text) { + const lines = text.split(/\r\n?|\n/g); + for (const line of lines) { + writeLine(); + writeSpace(); + writePunctuation("*"); + writeSpace(); + write(line); + } + } + } + if (node.tags) { + if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { + writeSpace(); + emit(node.tags[0]); + } + else { + emitList(node, node.tags, ListFormat.JSDocComment); + } } + writeSpace(); + write("*/"); + } - const parentNodeArray = getContainingNodeArray(previousNode); - const prevNodeIndex = parentNodeArray?.indexOf(previousNode); - return prevNodeIndex !== undefined && prevNodeIndex > -1 && parentNodeArray!.indexOf(nextNode) === prevNodeIndex + 1; + function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.typeExpression); + emitJSDocComment(tag.comment); } - function emitLeadingComments(pos: number, isEmittedNode: boolean) { - hasWrittenComment = false; + function emitJSDocSeeTag(tag: JSDocSeeTag) { + emitJSDocTagName(tag.tagName); + emit(tag.name); + emitJSDocComment(tag.comment); + } - if (isEmittedNode) { - if (pos === 0 && currentSourceFile?.isDeclarationFile) { - forEachLeadingCommentToEmit(pos, emitNonTripleSlashLeadingComment); + function emitJSDocNameReference(node: JSDocNameReference) { + writeSpace(); + writePunctuation("{"); + emit(node.name); + writePunctuation("}"); + } + + function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) { + emitJSDocTagName(tag.tagName); + writeSpace(); + writePunctuation("{"); + emit(tag.class); + writePunctuation("}"); + emitJSDocComment(tag.comment); + } + + function emitJSDocTemplateTag(tag: JSDocTemplateTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.constraint); + writeSpace(); + emitList(tag, tag.typeParameters, ListFormat.CommaListElements); + emitJSDocComment(tag.comment); + } + + function emitJSDocTypedefTag(tag: JSDocTypedefTag) { + emitJSDocTagName(tag.tagName); + if (tag.typeExpression) { + if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + emitJSDocTypeExpression(tag.typeExpression); } else { - forEachLeadingCommentToEmit(pos, emitLeadingComment); + writeSpace(); + writePunctuation("{"); + write("Object"); + if (tag.typeExpression.isArrayType) { + writePunctuation("["); + writePunctuation("]"); + } + writePunctuation("}"); } } - else if (pos === 0) { - // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, - // unless it is a triple slash comment at the top of the file. - // For Example: - // /// - // declare var x; - // /// - // interface F {} - // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted - forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + if (tag.fullName) { + writeSpace(); + emit(tag.fullName); + } + emitJSDocComment(tag.comment); + if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { + emitJSDocTypeLiteral(tag.typeExpression); } } - function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + function emitJSDocCallbackTag(tag: JSDocCallbackTag) { + emitJSDocTagName(tag.tagName); + if (tag.name) { + writeSpace(); + emit(tag.name); } + emitJSDocComment(tag.comment); + emitJSDocSignature(tag.typeExpression); } - function emitNonTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } + function emitJSDocSimpleTag(tag: JSDocTag) { + emitJSDocTagName(tag.tagName); + emitJSDocComment(tag.comment); } - function shouldWriteComment(text: string, pos: number) { - if (printerOptions.onlyPrintJsDocStyle) { - return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); - } - return true; + function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { + emitList(lit, factory.createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); } - function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - if (!hasWrittenComment) { - emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); - hasWrittenComment = true; - } - - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); + function emitJSDocSignature(sig: JSDocSignature) { + if (sig.typeParameters) { + emitList(sig, factory.createNodeArray(sig.typeParameters), ListFormat.JSDocComment); } - else if (kind === SyntaxKind.MultiLineCommentTrivia) { - writer.writeSpace(" "); + if (sig.parameters) { + emitList(sig, factory.createNodeArray(sig.parameters), ListFormat.JSDocComment); } - } - - function emitLeadingCommentsOfPosition(pos: number) { - if (commentsDisabled || pos === -1) { - return; + if (sig.type) { + writeLine(); + writeSpace(); + writePunctuation("*"); + writeSpace(); + emit(sig.type); } - - emitLeadingComments(pos, /*isEmittedNode*/ true); } - function emitTrailingComments(pos: number) { - forEachTrailingCommentToEmit(pos, emitTrailingComment); - } - - function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); + function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { + emitJSDocTagName(param.tagName); + emitJSDocTypeExpression(param.typeExpression); + writeSpace(); + if (param.isBracketed) { + writePunctuation("["); } - - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); + emit(param.name); + if (param.isBracketed) { + writePunctuation("]"); } + emitJSDocComment(param.comment); } - function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) { - if (commentsDisabled) { - return; - } - enterComment(); - forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition); - exitComment(); + function emitJSDocTagName(tagName: Identifier) { + writePunctuation("@"); + emit(tagName); } - function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); // still write a newline for single-line comments, so closing tokens aren't written on the same line + function emitJSDocComment(comment: string | NodeArray | undefined) { + const text = getTextOfJSDocComment(comment); + if (text) { + writeSpace(); + write(text); } } - function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - - emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); - } - else { - writer.writeSpace(" "); + function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { + if (typeExpression) { + writeSpace(); + writePunctuation("{"); + emit(typeExpression.type); + writePunctuation("}"); } } - function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { - if (hasDetachedComments(pos)) { - forEachLeadingCommentWithoutDetachedComments(cb); - } - else { - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); + // + // Top-level nodes + // + + function emitSourceFile(node: SourceFile) { + writeLine(); + const statements = node.statements; + if (emitBodyWithDetachedComments) { + // Emit detached comment if there are no prologue directives or if the first node is synthesized. + // The synthesized node will have no leading comment so some comments may be missed. + const shouldEmitDetachedComment = statements.length === 0 || + !isPrologueDirective(statements[0]) || + nodeIsSynthesized(statements[0]); + if (shouldEmitDetachedComment) { + emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); + return; } } + emitSourceFileWorker(node); } - function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { - // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { - forEachTrailingCommentRange(currentSourceFile.text, end, cb); + function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { + emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); + for (const prepend of node.prepends) { + if (isUnparsedSource(prepend) && prepend.syntheticReferences) { + for (const ref of prepend.syntheticReferences) { + emit(ref); + writeLine(); + } + } } } - function hasDetachedComments(pos: number) { - return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { + if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); } - function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // get the leading comments from detachedPos - const pos = last(detachedCommentsInfo!).detachedCommentEndPos; - if (detachedCommentsInfo!.length - 1) { - detachedCommentsInfo!.pop(); + function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { + if (hasNoDefaultLib) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); + writeLine(); } - else { - detachedCommentsInfo = undefined; + if (currentSourceFile && currentSourceFile.moduleName) { + writeComment(`/// `); + writeLine(); } - - forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); - } - - function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { - const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); - if (currentDetachedCommentInfo) { - if (detachedCommentsInfo) { - detachedCommentsInfo.push(currentDetachedCommentInfo); - } - else { - detachedCommentsInfo = [currentDetachedCommentInfo]; + if (currentSourceFile && currentSourceFile.amdDependencies) { + for (const dep of currentSourceFile.amdDependencies) { + if (dep.name) { + writeComment(`/// `); + } + else { + writeComment(`/// `); + } + writeLine(); } } + for (const directive of files) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); + writeLine(); + } + for (const directive of types) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); + writeLine(); + } + for (const directive of libs) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); + writeLine(); + } } - function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; - emitPos(commentPos); - writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - } - - /** - * Determine if the given comment is a triple-slash - * - * @return true if the comment is a triple-slash comment else false - */ - function isTripleSlashComment(commentPos: number, commentEnd: number) { - return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); + function emitSourceFileWorker(node: SourceFile) { + const statements = node.statements; + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitHelpers(node); + const index = findIndex(statements, statement => !isPrologueDirective(statement)); + emitTripleSlashDirectivesIfNeeded(node); + emitList(node, statements, ListFormat.MultiLine, /*parenthesizerRule*/ undefined, index === -1 ? statements.length : index); + popNameGenerationScope(node); } - // Source Maps + // Transformation nodes - function getParsedSourceMap(node: UnparsedSource) { - if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { - node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; - } - return node.parsedSourceMap || undefined; + function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { + emitExpression(node.expression); } - /** - * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source - */ - function skipSourceTrivia(source: SourceMapSource, pos: number): number { - return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); + function emitCommaList(node: CommaListExpression) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements, /*parenthesizerRule*/ undefined); } /** - * Emits a mapping. - * - * If the position is synthetic (undefined or a negative value), no mapping will be - * created. - * - * @param pos The position. + * Emits any prologue directives at the start of a Statement list, returning the + * number of prologue directives written to the output. */ - function emitPos(pos: number) { - if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { - return; + function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Set, recordBundleFileSection?: true): number { + let needsToSetSourceFile = !!sourceFile; + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + if (isPrologueDirective(statement)) { + const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; + if (shouldEmitPrologueDirective) { + if (needsToSetSourceFile) { + needsToSetSourceFile = false; + setSourceFile(sourceFile); + } + writeLine(); + const pos = writer.getTextPos(); + emit(statement); + if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); + if (seenPrologueDirectives) { + seenPrologueDirectives.add(statement.expression.text); + } + } + } + else { + // return index of the first non prologue directive + return i; + } } - const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); - sourceMapGenerator!.addMapping( - writer.getLine(), - writer.getColumn(), - sourceMapSourceIndex, - sourceLine, - sourceCharacter, - /*nameIndex*/ undefined); + return statements.length; } - function emitSourcePos(source: SourceMapSource, pos: number) { - if (source !== sourceMapSource) { - const savedSourceMapSource = sourceMapSource; - const savedSourceMapSourceIndex = sourceMapSourceIndex; - setSourceMapSource(source); - emitPos(pos); - resetSourceMapSource(savedSourceMapSource, savedSourceMapSourceIndex); + function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Set) { + for (const prologue of prologues) { + if (!seenPrologueDirectives.has(prologue.data)) { + writeLine(); + const pos = writer.getTextPos(); + emit(prologue); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); + if (seenPrologueDirectives) { + seenPrologueDirectives.add(prologue.data); + } + } + } + } + + function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { + if (isSourceFile(sourceFileOrBundle)) { + emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); } else { - emitPos(pos); + const seenPrologueDirectives = new Set(); + for (const prepend of sourceFileOrBundle.prepends) { + emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); + } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); + } + setSourceFile(undefined); } } - /** - * Emits a token of a node with possible leading and trailing source maps. - * - * @param node The node containing the token. - * @param token The token to emit. - * @param tokenStartPos The start pos of the token. - * @param emitCallback The callback used to emit the token. - */ - function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { - if (sourceMapsDisabled || node && isInJsonFile(node)) { - return emitCallback(token, writer, tokenPos); + function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { + const seenPrologueDirectives = new Set(); + let prologues: SourceFilePrologueInfo[] | undefined; + for (let index = 0; index < bundle.sourceFiles.length; index++) { + const sourceFile = bundle.sourceFiles[index]; + let directives: SourceFilePrologueDirective[] | undefined; + let end = 0; + for (const statement of sourceFile.statements) { + if (!isPrologueDirective(statement)) break; + if (seenPrologueDirectives.has(statement.expression.text)) continue; + seenPrologueDirectives.add(statement.expression.text); + (directives || (directives = [])).push({ + pos: statement.pos, + end: statement.end, + expression: { + pos: statement.expression.pos, + end: statement.expression.end, + text: statement.expression.text + } + }); + end = end < statement.end ? statement.end : end; + } + if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); } + return prologues; + } - const emitNode = node && node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - const source = range && range.source || sourceMapSource; - - tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); - if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); + function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { + if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { + const shebang = getShebang(sourceFileOrBundle.text); + if (shebang) { + writeComment(shebang); + writeLine(); + return true; + } } - - tokenPos = emitCallback(token, writer, tokenPos); - - if (range) tokenPos = range.end; - if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); + else { + for (const prepend of sourceFileOrBundle.prepends) { + Debug.assertNode(prepend, isUnparsedSource); + if (emitShebangIfNeeded(prepend)) { + return true; + } + } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + // Emit only the first encountered shebang + if (emitShebangIfNeeded(sourceFile)) { + return true; + } + } } - - return tokenPos; } - function setSourceMapSource(source: SourceMapSource) { - if (sourceMapsDisabled) { - return; - } + // + // Helpers + // - sourceMapSource = source; + function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { + if (!node) return; + const savedWrite = write; + write = writer; + emit(node); + write = savedWrite; + } - if (source === mostRecentlyAddedSourceMapSource) { - // Fast path for when the new source map is the most recently added, in which case - // we use its captured index without going through the source map generator. - sourceMapSourceIndex = mostRecentlyAddedSourceMapSourceIndex; - return; + function emitModifiers(node: Node, modifiers: NodeArray | undefined) { + if (modifiers && modifiers.length) { + emitList(node, modifiers, ListFormat.Modifiers); + writeSpace(); } + } - if (isJsonSourceMapSource(source)) { - return; + function emitTypeAnnotation(node: TypeNode | undefined) { + if (node) { + writePunctuation(":"); + writeSpace(); + emit(node); } + } - sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); - if (printerOptions.inlineSources) { - sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); + function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node, parenthesizerRule?: (node: Expression) => Expression) { + if (node) { + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); + writeSpace(); + emitExpression(node, parenthesizerRule); } - - mostRecentlyAddedSourceMapSource = source; - mostRecentlyAddedSourceMapSourceIndex = sourceMapSourceIndex; } - function resetSourceMapSource(source: SourceMapSource, sourceIndex: number) { - sourceMapSource = source; - sourceMapSourceIndex = sourceIndex; + function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: T | undefined, emit: (node: T) => void) { + if (node) { + prefixWriter(prefix); + emit(node); + } } - function isJsonSourceMapSource(sourceFile: SourceMapSource) { - return fileExtensionIs(sourceFile.fileName, Extension.Json); + function emitWithLeadingSpace(node: Node | undefined) { + if (node) { + writeSpace(); + emit(node); + } } - } - - const enum PreprintPipelinePhase { - Notification, - Substitution, - Visit - } - function createPreprinter(handlers: PrintHandlers) { - const { - substituteNode = noEmitSubstitution, - onEmitNode = noEmitNotification, - isEmitNotificationEnabled - } = handlers; + function emitExpressionWithLeadingSpace(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { + if (node) { + writeSpace(); + emitExpression(node, parenthesizerRule); + } + } - let pipelineResult: Node | undefined; + function emitWithTrailingSpace(node: Node | undefined) { + if (node) { + emit(node); + writeSpace(); + } + } - // Outer visitors - // - // These visitors are invoked by inner visitors to re-enter the pipeline - // for notification and substitution. - - const visit = makeVisitor(pipelineVisitorForUnspecified); - const visitSourceFile = makeVisitor(pipelineVisitorForSourceFile, isSourceFile); - const visitIdentifierName = makeVisitor(pipelineVisitorForIdentifierName, isIdentifier); - const visitModuleName = makeVisitor(pipelineVisitorForIdentifierNameOrUnspecified, isModuleName); - const visitPropertyName = makeVisitor(pipelineVisitorForIdentifierNameOrUnspecified, isPropertyName); - const visitMemberName = makeVisitor(pipelineVisitorForIdentifierNameOrUnspecified, isMemberName); - const visitBindingName = makeVisitor(pipelineVisitorForIdentifierNameOrUnspecified, isBindingName); - const visitEntityName = makeVisitor(pipelineVisitorForIdentifierReferenceOrUnspecified, isEntityName); - const visitExpression = makeVisitor(pipelineVisitorForExpression, isExpression); - const visitForInitializer = makeVisitor(pipelineVisitorForForInitializer, isForInitializer); - const visitTypeNode = makeVisitor(pipelineVisitorForUnspecified, isTypeNode); - const visitEmbeddedStatement = makeVisitor(pipelineVisitorForEmbeddedStatement, isStatement, factory.liftToBlock); - const visitJsxAttributeValue = makeVisitor(pipelineVisitorForJsxAttributeValue, isStringLiteralOrJsxExpression); - const visitMappedTypeParameter = makeVisitor(pipelineVisitorForMappedTypeParameter, isTypeParameterDeclaration); - const visitConciseBody = makeVisitor(pipelineVisitorForConciseBody, isConciseBody); - const visitFunctionBody = makeVisitor(pipelineVisitorForUnspecified, isFunctionBody); - const visitList = makeListVisitor(pipelineVisitorForUnspecified); - const visitTypeNodeList = makeListVisitor(pipelineVisitorForUnspecified, isTypeNode); - const visitExpressionList = makeListVisitor(pipelineVisitorForExpression, isExpression); - const visitParameterList = makeListVisitor(pipelineVisitorForUnspecified, isParameter); - - function makeVisitor(outerVisitor: (node: Node) => Node | undefined, defaultTest?: (node: Node) => node is T, lift?: (nodes: readonly Node[]) => Node) { - function visit(node: T, test: (node: Node) => node is U): U; - function visit(node: T | undefined, test: (node: Node) => node is U): U | undefined; - function visit(node: T, test?: (node: Node) => node is T): T; - function visit(node: T | undefined, test?: (node: Node) => node is T): T | undefined; - function visit(node: Node | undefined, test?: (node: Node) => node is T): Node | undefined { - return visitNode(node, outerVisitor, test || defaultTest, lift); - } - return visit; - } - - function makeListVisitor(outerVisitor: (node: Node) => Node | undefined, defaultTest?: (node: Node) => node is T) { - function visitList(nodes: NodeArray, test: (node: Node) => node is U): NodeArray; - function visitList(nodes: NodeArray | undefined, test: (node: Node) => node is U): NodeArray | undefined; - function visitList(nodes: NodeArray, test?: (node: Node) => boolean): NodeArray; - function visitList(nodes: NodeArray | undefined, test?: (node: Node) => boolean): NodeArray | undefined; - function visitList(nodes: NodeArray | undefined, test: (node: Node) => boolean = defaultTest || returnTrue): NodeArray | undefined { - return visitNodes(nodes, outerVisitor, test); - } - return visitList; - } - - // Pipeline Visitors - // - // These visitors execute our existing pipeline logic for notification and substitution, - // but adapted to our visitor pattern. In some cases, we refine the `EmitHint` we pass - // to the `onEmitNode` and `substituteNode` APIs to ensure they receive the appropriate - // context. - // - // For example, the ConciseBody of an arrow function could be an Identifier, in which - // case we would want to use `EmitHint.Expression` to ensure we treat the identifier - // as an expression during substitution. - - function pipelineVisitorForSourceFile(node: SourceFile) { return pipelineVisitorWorker(EmitHint.SourceFile, node); } - function pipelineVisitorForExpression(node: Expression) { return pipelineVisitorWorker(EmitHint.Expression, node); } - function pipelineVisitorForIdentifierName(node: Identifier) { return pipelineVisitorWorker(EmitHint.IdentifierName, node); } - function pipelineVisitorForIdentifierNameOrUnspecified(node: Node) { return pipelineVisitorWorker(isIdentifier(node) ? EmitHint.IdentifierName : EmitHint.Unspecified, node); } - function pipelineVisitorForIdentifierReferenceOrUnspecified(node: Node) { return pipelineVisitorWorker(isIdentifier(node) ? EmitHint.Expression : EmitHint.Unspecified, node); } - function pipelineVisitorForForInitializer(node: ForInitializer) { return pipelineVisitorWorker(isVariableDeclarationList(node) ? EmitHint.Unspecified : EmitHint.Expression, node); } - function pipelineVisitorForMappedTypeParameter(node: TypeParameterDeclaration) { return pipelineVisitorWorker(EmitHint.MappedTypeParameter, node); } - function pipelineVisitorForEmbeddedStatement(node: Statement) { return pipelineVisitorWorker(isEmptyStatement(node) ? EmitHint.EmbeddedStatement : EmitHint.Unspecified, node); } - function pipelineVisitorForJsxAttributeValue(node: StringLiteral | JsxExpression) { return pipelineVisitorWorker(isStringLiteral(node) ? EmitHint.JsxAttributeValue : EmitHint.Unspecified, node); } - function pipelineVisitorForConciseBody(node: ConciseBody) { return pipelineVisitorWorker(isBlock(node) ? EmitHint.Unspecified : EmitHint.Expression, node); } - function pipelineVisitorForUnspecified(node: Node) { return pipelineVisitorWorker(EmitHint.Unspecified, node); } + function emitEmbeddedStatement(parent: Node, node: Statement) { + if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { + writeSpace(); + emit(node); + } + else { + writeLine(); + increaseIndent(); + if (isEmptyStatement(node)) { + pipelineEmit(EmitHint.EmbeddedStatement, node, emitEmbeddedEmptyStatement, /*parenthesizerRule*/ undefined); + } + else { + emit(node); + } + decreaseIndent(); + } + } - /** - * Adapts the emit pipeline API to work with the visitor API - */ - function pipelineVisitorWorker(hint: EmitHint, node: Node) { - resetPipelineResult(); - // Get the first supported pipeline phase for this node and evaluate it. We can skip several stack - // frames if we aren't doing emit notification, so we check for substitution and direct callbacks - // and execute those immediately. - const pipelinePhase = getPipelinePhase(PreprintPipelinePhase.Notification, node); - if (pipelinePhase === pipelineVisitDirect) { - return visitor(hint, node); - } - - if (pipelinePhase === pipelineVisitWithSubstitution) { - // The next phase after substitution is always direct visitation, so we can reduce the call stack - // depth by calling the visitor directly. - return visitor(hint, substituteNode(hint, node)); - } - - pipelinePhase(hint, node); - Debug.assertIsDefined(pipelineResult); - const result = pipelineResult; - resetPipelineResult(); - return result; + function emitDecorators(parentNode: Node, decorators: NodeArray | undefined) { + emitList(parentNode, decorators, ListFormat.Decorators); } - function resetPipelineResult() { - pipelineResult = undefined; + function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { + emitList(parentNode, typeArguments, ListFormat.TypeArguments, parenthesizer.parenthesizeMemberOfElementType); } - /** - * Gets the pipeline callback to pass to the relevant API (i.e., `substituteNode` or `onEmitNode`) - */ - function getPipelinePhase(phase: PreprintPipelinePhase, node: Node) { - switch (phase) { - case PreprintPipelinePhase.Notification: - if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) { - return pipelineVisitWithNotification; - } - // falls through - case PreprintPipelinePhase.Substitution: - if (substituteNode !== noEmitSubstitution) { - return pipelineVisitWithSubstitution; - } - // falls through - default: - return pipelineVisitDirect; + function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { + if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures + return emitTypeArguments(parentNode, parentNode.typeArguments); } + emitList(parentNode, typeParameters, ListFormat.TypeParameters); } - /** - * A callback that can be evaluated to trigger emit notification as part of the emit pipeline. - */ - function pipelineVisitWithNotification(hint: EmitHint, node: Node) { - onEmitNode(hint, node, getPipelinePhase(PreprintPipelinePhase.Substitution, node)); + function emitParameters(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.Parameters); } - /** - * A callback that can be evaluated to trigger JIT substitution as part of the emit pipeline. - */ - function pipelineVisitWithSubstitution(hint: EmitHint, node: Node) { - // Next phase is always direct visitation, so we can reduce the call stack - // depth by calling the visitor directly. - pipelineResult = visitor(hint, substituteNode(hint, node)); + function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + const parameter = singleOrUndefined(parameters); + return parameter + && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter + && isArrowFunction(parentNode) // only arrow functions may have simple arrow head + && !parentNode.type // arrow function may not have return type annotation + && !some(parentNode.decorators) // parent may not have decorators + && !some(parentNode.modifiers) // parent may not have modifiers + && !some(parentNode.typeParameters) // parent may not have type parameters + && !some(parameter.decorators) // parameter may not have decorators + && !some(parameter.modifiers) // parameter may not have modifiers + && !parameter.dotDotDotToken // parameter may not be rest + && !parameter.questionToken // parameter may not be optional + && !parameter.type // parameter may not have a type annotation + && !parameter.initializer // parameter may not have an initializer + && isIdentifier(parameter.name); // parameter name must be identifier } - /** - * A callback that can be evaluated to visit the subtree of a node. - */ - function pipelineVisitDirect(hint: EmitHint, node: Node) { - pipelineResult = visitor(hint, node); + function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + if (canEmitSimpleArrowHead(parentNode, parameters)) { + emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); + } + else { + emitParameters(parentNode, parameters); + } } - /** - * Re-enters the visitor pattern from the pipeline pattern to perform - * tree updates and trigger parenthesization rules. - */ - function visitor(hint: EmitHint, node: Node): Node { - // This should align with the assertions in `pipelineEmitWithHint`. - if (hint === EmitHint.SourceFile) return preprintSourceFile(cast(node, isSourceFile)); - if (hint === EmitHint.IdentifierName) return preprintIdentifier(cast(node, isIdentifier)); - if (hint === EmitHint.JsxAttributeValue) return cast(node, isStringLiteral); - if (hint === EmitHint.MappedTypeParameter) return preprintTypeParameterDeclaration(cast(node, isTypeParameterDeclaration)); - if (hint === EmitHint.EmbeddedStatement) return cast(node, isEmptyStatement); + function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + } - const kind = node.kind; - // No need to visit nodes without children. - if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken) || kind === SyntaxKind.ThisType) { - return node; + function writeDelimiter(format: ListFormat) { + switch (format & ListFormat.DelimitersMask) { + case ListFormat.None: + break; + case ListFormat.CommaDelimited: + writePunctuation(","); + break; + case ListFormat.BarDelimited: + writeSpace(); + writePunctuation("|"); + break; + case ListFormat.AsteriskDelimited: + writeSpace(); + writePunctuation("*"); + writeSpace(); + break; + case ListFormat.AmpersandDelimited: + writeSpace(); + writePunctuation("&"); + break; } + } - if (hint === EmitHint.Unspecified) { - if (isKeyword(node.kind)) return node; - - switch (node.kind) { - // Identifiers - case SyntaxKind.Identifier: - return preprintIdentifier(node as Identifier); + function emitList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: (node: Node) => Node, start?: number, count?: number) { + emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count); + } - // Names - case SyntaxKind.QualifiedName: - Debug.type(node); - return factory.updateQualifiedName(node, - visitEntityName(node.left), - visitIdentifierName(node.right)); + function emitExpressionList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: (node: Expression) => Expression, start?: number, count?: number) { + emitNodeList(emitExpression, parentNode, children, format, parenthesizerRule, start, count); + } - case SyntaxKind.ComputedPropertyName: - Debug.type(node); - return factory.updateComputedPropertyName(node, - visitExpression(node.expression)); + function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule: ((node: Node) => Node) | undefined, start = 0, count = children ? children.length - start : 0) { + const isUndefined = children === undefined; + if (isUndefined && format & ListFormat.OptionalIfUndefined) { + return; + } - // Signature elements - case SyntaxKind.TypeParameter: - return preprintTypeParameterDeclaration(node as TypeParameterDeclaration); + const isEmpty = children === undefined || start >= children.length || count === 0; + if (isEmpty && format & ListFormat.OptionalIfEmpty) { + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); + } + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); + } + return; + } - case SyntaxKind.Parameter: - Debug.type(node); - return factory.updateParameterDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visit(node.dotDotDotToken, isDotDotDotToken), - visitBindingName(node.name), - visit(node.questionToken, isQuestionToken), - visitTypeNode(node.type), - visitExpression(node.initializer)); + if (format & ListFormat.BracketsMask) { + writePunctuation(getOpeningBracket(format)); + if (isEmpty && children) { + emitTrailingCommentsOfPosition(children.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists + } + } - case SyntaxKind.Decorator: - Debug.type(node); - return factory.updateDecorator(node, - visitExpression(node.expression)); + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); + } - // Type members - case SyntaxKind.PropertySignature: - Debug.type(node); - return factory.updatePropertySignature(node, - visitList(node.modifiers, isModifier), - visitPropertyName(node.name), - visit(node.questionToken, isQuestionToken), - visitTypeNode(node.type)); + if (isEmpty) { + // Write a line terminator if the parent node was multi-line + if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!)))) { + writeLine(); + } + else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { + writeSpace(); + } + } + else { + Debug.type>(children); + // Write the opening line terminator or leading whitespace. + const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; + let shouldEmitInterveningComments = mayEmitInterveningComments; + const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children, format); // TODO: GH#18217 + if (leadingLineTerminatorCount) { + writeLine(leadingLineTerminatorCount); + shouldEmitInterveningComments = false; + } + else if (format & ListFormat.SpaceBetweenBraces) { + writeSpace(); + } - case SyntaxKind.PropertyDeclaration: - Debug.type(node); - return factory.updatePropertyDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitPropertyName(node.name), - visit(node.questionToken || node.exclamationToken, isQuestionOrExclamationToken), - visitTypeNode(node.type), - visitExpression(node.initializer)); + // Increase the indent, if requested. + if (format & ListFormat.Indented) { + increaseIndent(); + } - case SyntaxKind.MethodSignature: - Debug.type(node); - return factory.updateMethodSignature(node, - visitList(node.modifiers, isModifier), - visitPropertyName(node.name), - visit(node.questionToken, isQuestionToken), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type)); + // Emit each child. + let previousSibling: Node | undefined; + let previousSourceFileTextKind: ReturnType; + let shouldDecreaseIndentAfterEmit = false; + for (let i = 0; i < count; i++) { + const child = children[start + i]; - case SyntaxKind.MethodDeclaration: - Debug.type(node); - return factory.updateMethodDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visit(node.asteriskToken, isAsteriskToken), - visitPropertyName(node.name), - visit(node.questionToken, isQuestionToken), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type), - visitFunctionBody(node.body)); + // Write the delimiter if this is not the first node. + if (format & ListFormat.AsteriskDelimited) { + // always write JSDoc in the format "\n *" + writeLine(); + writeDelimiter(format); + } + else if (previousSibling) { + // i.e + // function commentedParameters( + // /* Parameter a */ + // a + // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline + // , + if (format & ListFormat.DelimitersMask && previousSibling.end !== (parentNode ? parentNode.end : -1)) { + emitLeadingCommentsOfPosition(previousSibling.end); + } + writeDelimiter(format); + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - case SyntaxKind.Constructor: - Debug.type(node); - return factory.updateConstructorDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitParameterList(node.parameters), - visitFunctionBody(node.body)); + // Write either a line terminator or whitespace to separate the elements. + const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format); + if (separatingLineTerminatorCount > 0) { + // If a synthesized node in a single-line list starts on a new + // line, we should increase the indent. + if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { + increaseIndent(); + shouldDecreaseIndentAfterEmit = true; + } - case SyntaxKind.GetAccessor: - Debug.type(node); - return factory.updateGetAccessorDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitPropertyName(node.name), - visitParameterList(node.parameters), - visitTypeNode(node.type), - visitFunctionBody(node.body)); + writeLine(separatingLineTerminatorCount); + shouldEmitInterveningComments = false; + } + else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { + writeSpace(); + } + } - case SyntaxKind.SetAccessor: - Debug.type(node); - return factory.updateSetAccessorDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitPropertyName(node.name), - visitParameterList(node.parameters), - visitFunctionBody(node.body)); + // Emit this child. + previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); + if (shouldEmitInterveningComments) { + if (emitTrailingCommentsOfPosition) { + const commentRange = getCommentRange(child); + emitTrailingCommentsOfPosition(commentRange.pos); + } + } + else { + shouldEmitInterveningComments = mayEmitInterveningComments; + } - case SyntaxKind.CallSignature: - Debug.type(node); - return factory.updateCallSignature(node, - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type)); + nextListElementPos = child.pos; + if (emit.length === 1) { + emit(child); + } + else { + emit(child, parenthesizerRule); + } - case SyntaxKind.ConstructSignature: - Debug.type(node); - return factory.updateConstructSignature(node, - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type)); + if (shouldDecreaseIndentAfterEmit) { + decreaseIndent(); + shouldDecreaseIndentAfterEmit = false; + } - case SyntaxKind.IndexSignature: - Debug.type(node); - return factory.updateIndexSignature(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitParameterList(node.parameters), - visitTypeNode(node.type)); + previousSibling = child; + } - // Types - case SyntaxKind.TypePredicate: - Debug.type(node); - return factory.updateTypePredicateNode(node, - visit(node.assertsModifier, isAssertsKeyword), - visit(node.parameterName, isIdentifierOrThisTypeNode), - visitTypeNode(node.type)); + // Write a trailing comma, if requested. + const emitFlags = previousSibling ? getEmitFlags(previousSibling) : 0; + const skipTrailingComments = commentsDisabled || !!(emitFlags & EmitFlags.NoTrailingComments); + const hasTrailingComma = children?.hasTrailingComma && (format & ListFormat.AllowTrailingComma) && (format & ListFormat.CommaDelimited); + if (hasTrailingComma) { + if (previousSibling && !skipTrailingComments) { + emitTokenWithComment(SyntaxKind.CommaToken, previousSibling.end, writePunctuation, previousSibling); + } + else { + writePunctuation(","); + } + } - case SyntaxKind.TypeReference: - Debug.type(node); - return factory.updateTypeReferenceNode(node, - visitEntityName(node.typeName), - visitTypeNodeList(node.typeArguments)); + // Emit any trailing comment of the last element in the list + // i.e + // var array = [... + // 2 + // /* end of element 2 */ + // ]; + if (previousSibling && (parentNode ? parentNode.end : -1) !== previousSibling.end && (format & ListFormat.DelimitersMask) && !skipTrailingComments) { + emitLeadingCommentsOfPosition(hasTrailingComma && children?.end ? children.end : previousSibling.end); + } - case SyntaxKind.FunctionType: - Debug.type(node); - return factory.updateFunctionTypeNode(node, - visitList(node.typeParameters, isTypeParameterDeclaration), - visitNodes(node.parameters, pipelineVisitorForUnspecified, isParameterDeclaration), - visitTypeNode(node.type)); + // Decrease the indent, if requested. + if (format & ListFormat.Indented) { + decreaseIndent(); + } - case SyntaxKind.ConstructorType: - Debug.type(node); - return factory.updateConstructorTypeNode(node, - visitNodes(node.modifiers, pipelineVisitorForUnspecified, isModifier), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type)); + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - case SyntaxKind.TypeQuery: - Debug.type(node); - return factory.updateTypeQueryNode(node, - visitEntityName(node.exprName)); + // Write the closing line terminator or closing whitespace. + const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children, format); + if (closingLineTerminatorCount) { + writeLine(closingLineTerminatorCount); + } + else if (format & (ListFormat.SpaceAfterList | ListFormat.SpaceBetweenBraces)) { + writeSpace(); + } + } - case SyntaxKind.TypeLiteral: - Debug.type(node); - return factory.updateTypeLiteralNode(node, - visitList(node.members, isTypeElement)); + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); + } - case SyntaxKind.ArrayType: - Debug.type(node); - return factory.updateArrayTypeNode(node, - visitTypeNode(node.elementType)); + if (format & ListFormat.BracketsMask) { + if (isEmpty && children) { + emitLeadingCommentsOfPosition(children.end); // Emit leading comments within empty lists + } + writePunctuation(getClosingBracket(format)); + } + } - case SyntaxKind.TupleType: - Debug.type(node); - return factory.updateTupleTypeNode(node, - visitTypeNodeList(node.elements)); + // Writers - case SyntaxKind.OptionalType: - Debug.type(node); - return factory.updateOptionalTypeNode(node, - visitTypeNode(node.type)); + function writeLiteral(s: string) { + writer.writeLiteral(s); + } - case SyntaxKind.RestType: - Debug.type(node); - return factory.updateRestTypeNode(node, - visitTypeNode(node.type)); + function writeStringLiteral(s: string) { + writer.writeStringLiteral(s); + } - case SyntaxKind.UnionType: - Debug.type(node); - return factory.updateUnionTypeNode(node, - visitTypeNodeList(node.types)); + function writeBase(s: string) { + writer.write(s); + } - case SyntaxKind.IntersectionType: - Debug.type(node); - return factory.updateIntersectionTypeNode(node, - visitTypeNodeList(node.types)); + function writeSymbol(s: string, sym: Symbol) { + writer.writeSymbol(s, sym); + } - case SyntaxKind.ConditionalType: - Debug.type(node); - return factory.updateConditionalTypeNode(node, - visitTypeNode(node.checkType), - visitTypeNode(node.extendsType), - visitTypeNode(node.trueType), - visitTypeNode(node.falseType)); + function writePunctuation(s: string) { + writer.writePunctuation(s); + } - case SyntaxKind.InferType: - Debug.type(node); - return factory.updateInferTypeNode(node, - visit(node.typeParameter, isTypeParameterDeclaration)); + function writeTrailingSemicolon() { + writer.writeTrailingSemicolon(";"); + } - case SyntaxKind.ImportType: - Debug.type(node); - return factory.updateImportTypeNode(node, - visitTypeNode(node.argument), - visitEntityName(node.qualifier), - visitTypeNodeList(node.typeArguments), - node.isTypeOf - ); + function writeKeyword(s: string) { + writer.writeKeyword(s); + } - case SyntaxKind.NamedTupleMember: - Debug.type(node); - return factory.updateNamedTupleMember(node, - visit(node.dotDotDotToken, isDotDotDotToken), - visitIdentifierName(node.name), - visit(node.questionToken, isQuestionToken), - visitTypeNode(node.type), - ); + function writeOperator(s: string) { + writer.writeOperator(s); + } - case SyntaxKind.ParenthesizedType: - Debug.type(node); - return factory.updateParenthesizedType(node, - visitTypeNode(node.type)); + function writeParameter(s: string) { + writer.writeParameter(s); + } - case SyntaxKind.ExpressionWithTypeArguments: - Debug.type(node); - return factory.updateExpressionWithTypeArguments(node, - visitExpression(node.expression), - visitTypeNodeList(node.typeArguments)); + function writeComment(s: string) { + writer.writeComment(s); + } - case SyntaxKind.TypeOperator: - Debug.type(node); - return factory.updateTypeOperatorNode(node, - visitTypeNode(node.type)); + function writeSpace() { + writer.writeSpace(" "); + } - case SyntaxKind.IndexedAccessType: - Debug.type(node); - return factory.updateIndexedAccessTypeNode(node, - visitTypeNode(node.objectType), - visitTypeNode(node.indexType)); + function writeProperty(s: string) { + writer.writeProperty(s); + } - case SyntaxKind.MappedType: - Debug.type(node); - return factory.updateMappedTypeNode(node, - visit(node.readonlyToken, isReadonlyKeywordOrPlusOrMinusToken), - visitMappedTypeParameter(node.typeParameter), - visitTypeNode(node.nameType), - visit(node.questionToken, isQuestionOrPlusOrMinusToken), - visitTypeNode(node.type)); + function writeLine(count = 1) { + for (let i = 0; i < count; i++) { + writer.writeLine(i > 0); + } + } - case SyntaxKind.LiteralType: - Debug.type(node); - return factory.updateLiteralTypeNode(node, - visitExpression(node.literal, isLiteralTypeLikeExpression)); + function increaseIndent() { + writer.increaseIndent(); + } - case SyntaxKind.TemplateLiteralType: - Debug.type(node); - return factory.updateTemplateLiteralType(node, - visit(node.head, isTemplateHead), - visitList(node.templateSpans, isTemplateLiteralTypeSpan)); + function decreaseIndent() { + writer.decreaseIndent(); + } - case SyntaxKind.TemplateLiteralTypeSpan: - Debug.type(node); - return factory.updateTemplateLiteralTypeSpan(node, - visitTypeNode(node.type), - visit(node.literal, isTemplateMiddleOrTemplateTail)); + function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { + return !sourceMapsDisabled + ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) + : writeTokenText(token, writer, pos); + } - // Binding patterns - case SyntaxKind.ObjectBindingPattern: - Debug.type(node); - return factory.updateObjectBindingPattern(node, - visitList(node.elements, isBindingElement)); + function writeTokenNode(node: Node, writer: (s: string) => void) { + if (onBeforeEmitToken) { + onBeforeEmitToken(node); + } + writer(tokenToString(node.kind)!); + if (onAfterEmitToken) { + onAfterEmitToken(node); + } + } - case SyntaxKind.ArrayBindingPattern: - Debug.type(node); - return factory.updateArrayBindingPattern(node, - visitList(node.elements, isArrayBindingElement)); + function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { + const tokenString = tokenToString(token)!; + writer(tokenString); + return pos! < 0 ? pos! : pos! + tokenString.length; + } - case SyntaxKind.BindingElement: - Debug.type(node); - return factory.updateBindingElement(node, - visit(node.dotDotDotToken, isDotDotDotToken), - visitPropertyName(node.propertyName), - visitBindingName(node.name), - visitExpression(node.initializer)); + function writeLineOrSpace(parentNode: Node, prevChildNode: Node, nextChildNode: Node) { + if (getEmitFlags(parentNode) & EmitFlags.SingleLine) { + writeSpace(); + } + else if (preserveSourceNewlines) { + const lines = getLinesBetweenNodes(parentNode, prevChildNode, nextChildNode); + if (lines) { + writeLine(lines); + } + else { + writeSpace(); + } + } + else { + writeLine(); + } + } - // Misc - case SyntaxKind.TemplateSpan: - Debug.type(node); - return factory.updateTemplateSpan(node, - visitExpression(node.expression), - visit(node.literal, isTemplateMiddleOrTemplateTail)); + function writeLines(text: string): void { + const lines = text.split(/\r\n?|\n/g); + const indentation = guessIndentation(lines); + for (const lineText of lines) { + const line = indentation ? lineText.slice(indentation) : lineText; + if (line.length) { + writeLine(); + write(line); + } + } + } - // Element - case SyntaxKind.Block: - Debug.type(node); - return factory.updateBlock(node, - visitList(node.statements, isStatement)); + function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) { + if (lineCount) { + increaseIndent(); + writeLine(lineCount); + } + else if (writeSpaceIfNotIndenting) { + writeSpace(); + } + } - case SyntaxKind.VariableStatement: - Debug.type(node); - return factory.updateVariableStatement(node, - visitList(node.modifiers, isModifier), - visit(node.declarationList, isVariableDeclarationList)); + // Helper function to decrease the indent if we previously indented. Allows multiple + // previous indent values to be considered at a time. This also allows caller to just + // call this once, passing in all their appropriate indent values, instead of needing + // to call this helper function multiple times. + function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) { + if (value1) { + decreaseIndent(); + } + if (value2) { + decreaseIndent(); + } + } - case SyntaxKind.ExpressionStatement: - Debug.type(node); - return factory.updateExpressionStatement(node, - visitExpression(node.expression)); + function getLeadingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (format & ListFormat.PreferNewLine) { + return 1; + } - case SyntaxKind.IfStatement: - Debug.type(node); - return factory.updateIfStatement(node, - visitExpression(node.expression), - visitEmbeddedStatement(node.thenStatement), - visitEmbeddedStatement(node.elseStatement)); + const firstChild = children[0]; + if (firstChild === undefined) { + return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; + } + if (firstChild.pos === nextListElementPos) { + // If this child starts at the beginning of a list item in a parent list, its leading + // line terminators have already been written as the separating line terminators of the + // parent list. Example: + // + // class Foo { + // constructor() {} + // public foo() {} + // } + // + // The outer list is the list of class members, with one line terminator between the + // constructor and the method. The constructor is written, the separating line terminator + // is written, and then we start emitting the method. Its modifiers ([public]) constitute an inner + // list, so we look for its leading line terminators. If we didn't know that we had already + // written a newline as part of the parent list, it would appear that we need to write a + // leading newline to start the modifiers. + return 0; + } + if (firstChild.kind === SyntaxKind.JsxText) { + // JsxText will be written with its leading whitespace, so don't add more manually. + return 0; + } + if (parentNode && + !positionIsSynthesized(parentNode.pos) && + !nodeIsSynthesized(firstChild) && + (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) + ) { + if (preserveSourceNewlines) { + return getEffectiveLines( + includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter( + firstChild.pos, + parentNode.pos, + currentSourceFile!, + includeComments)); + } + return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!) ? 0 : 1; + } + if (synthesizedNodeStartsOnNewLine(firstChild, format)) { + return 1; + } + } + return format & ListFormat.MultiLine ? 1 : 0; + } - case SyntaxKind.DoStatement: - Debug.type(node); - return factory.updateDoStatement(node, - visitEmbeddedStatement(node.statement), - visitExpression(node.expression)); + function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (previousNode === undefined || nextNode === undefined) { + return 0; + } + if (nextNode.kind === SyntaxKind.JsxText) { + // JsxText will be written with its leading whitespace, so don't add more manually. + return 0; + } + else if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { + return getEffectiveLines( + includeComments => getLinesBetweenRangeEndAndRangeStart( + previousNode, + nextNode, + currentSourceFile!, + includeComments)); + } + else if (!preserveSourceNewlines && !nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { + return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!) ? 0 : 1; + } + else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) { + return 1; + } + } + else if (getStartsOnNewLine(nextNode)) { + return 1; + } + return format & ListFormat.MultiLine ? 1 : 0; + } - case SyntaxKind.WhileStatement: - Debug.type(node); - return factory.updateWhileStatement(node, - visitExpression(node.expression), - visitEmbeddedStatement(node.statement)); + function getClosingLineTerminatorCount(parentNode: Node | undefined, children: readonly Node[], format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (format & ListFormat.PreferNewLine) { + return 1; + } - case SyntaxKind.ForStatement: - Debug.type(node); - return factory.updateForStatement(node, - visitForInitializer(node.initializer), - visitExpression(node.condition), - visitExpression(node.incrementor), - visitEmbeddedStatement(node.statement)); + const lastChild = lastOrUndefined(children); + if (lastChild === undefined) { + return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; + } + if (parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { + if (preserveSourceNewlines) { + const end = isNodeArray(children) && !positionIsSynthesized(children.end) ? children.end : lastChild.end; + return getEffectiveLines( + includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter( + end, + parentNode.end, + currentSourceFile!, + includeComments)); + } + return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!) ? 0 : 1; + } + if (synthesizedNodeStartsOnNewLine(lastChild, format)) { + return 1; + } + } + if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) { + return 1; + } + return 0; + } - case SyntaxKind.ForInStatement: - Debug.type(node); - return factory.updateForInStatement(node, - visitForInitializer(node.initializer), - visitExpression(node.expression), - visitEmbeddedStatement(node.statement)); + function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) { + // If 'preserveSourceNewlines' is disabled, we should never call this function + // because it could be more expensive than alternative approximations. + Debug.assert(!!preserveSourceNewlines); + // We start by measuring the line difference from a position to its adjacent comments, + // so that this is counted as a one-line difference, not two: + // + // node1; + // // NODE2 COMMENT + // node2; + const lines = getLineDifference(/*includeComments*/ true); + if (lines === 0) { + // However, if the line difference considering comments was 0, we might have this: + // + // node1; // NODE2 COMMENT + // node2; + // + // in which case we should be ignoring node2's comment, so this too is counted as + // a one-line difference, not zero. + return getLineDifference(/*includeComments*/ false); + } + return lines; + } - case SyntaxKind.ForOfStatement: - Debug.type(node); - return factory.updateForOfStatement(node, - visit(node.awaitModifier, isAwaitKeyword), - visitForInitializer(node.initializer), - visitExpression(node.expression), - visitEmbeddedStatement(node.statement)); + function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { + const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, [node], ListFormat.None); + if (leadingNewlines) { + writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false); + } + return !!leadingNewlines; + } - case SyntaxKind.ContinueStatement: - Debug.type(node); - return factory.updateContinueStatement(node, - visitIdentifierName(node.label)); + function writeLineSeparatorsAfter(node: Node, parent: Node) { + const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, [node], ListFormat.None); + if (trailingNewlines) { + writeLine(trailingNewlines); + } + } - case SyntaxKind.BreakStatement: - Debug.type(node); - return factory.updateBreakStatement(node, - visitIdentifierName(node.label)); + function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { + if (nodeIsSynthesized(node)) { + const startsOnNewLine = getStartsOnNewLine(node); + if (startsOnNewLine === undefined) { + return (format & ListFormat.PreferNewLine) !== 0; + } - case SyntaxKind.ReturnStatement: - Debug.type(node); - return factory.updateReturnStatement(node, - visitExpression(node.expression)); + return startsOnNewLine; + } - case SyntaxKind.WithStatement: - Debug.type(node); - return factory.updateWithStatement(node, - visitExpression(node.expression), - visitEmbeddedStatement(node.statement)); + return (format & ListFormat.PreferNewLine) !== 0; + } - case SyntaxKind.SwitchStatement: - Debug.type(node); - return factory.updateSwitchStatement(node, - visitExpression(node.expression), - visit(node.caseBlock, isCaseBlock)); + function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number { + if (getEmitFlags(parent) & EmitFlags.NoIndentation) { + return 0; + } - case SyntaxKind.LabeledStatement: - Debug.type(node); - return factory.updateLabeledStatement(node, - visitIdentifierName(node.label), - visitEmbeddedStatement(node.statement)); + parent = skipSynthesizedParentheses(parent); + node1 = skipSynthesizedParentheses(node1); + node2 = skipSynthesizedParentheses(node2); - case SyntaxKind.ThrowStatement: - Debug.type(node); - return factory.updateThrowStatement(node, - visitExpression(node.expression)); + // Always use a newline for synthesized code if the synthesizer desires it. + if (getStartsOnNewLine(node2)) { + return 1; + } - case SyntaxKind.TryStatement: - Debug.type(node); - return factory.updateTryStatement(node, - visit(node.tryBlock, isBlock), - visit(node.catchClause, isCatchClause), - visit(node.finallyBlock, isBlock)); + if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { + if (preserveSourceNewlines) { + return getEffectiveLines( + includeComments => getLinesBetweenRangeEndAndRangeStart( + node1, + node2, + currentSourceFile!, + includeComments)); + } + return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!) ? 0 : 1; + } - // Declarations - case SyntaxKind.VariableDeclaration: - Debug.type(node); - return factory.updateVariableDeclaration(node, - visitBindingName(node.name), - visit(node.exclamationToken, isExclamationToken), - visitTypeNode(node.type), - visitExpression(node.initializer)); + return 0; + } - case SyntaxKind.VariableDeclarationList: - Debug.type(node); - return factory.updateVariableDeclarationList(node, - visitList(node.declarations, isVariableDeclaration)); + function isEmptyBlock(block: BlockLike) { + return block.statements.length === 0 + && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile!); + } - case SyntaxKind.FunctionDeclaration: - Debug.type(node); - return factory.updateFunctionDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visit(node.asteriskToken, isAsteriskToken), - visitIdentifierName(node.name), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type), - visitFunctionBody(node.body)); + function skipSynthesizedParentheses(node: Node) { + while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { + node = (node).expression; + } - case SyntaxKind.ClassDeclaration: - Debug.type(node); - return factory.updateClassDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitIdentifierName(node.name), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitList(node.heritageClauses, isHeritageClause), - visitList(node.members, isClassElement)); + return node; + } - case SyntaxKind.InterfaceDeclaration: - Debug.type(node); - return factory.updateInterfaceDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitIdentifierName(node.name), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitList(node.heritageClauses, isHeritageClause), - visitList(node.members, isTypeElement)); + function getTextOfNode(node: Node, includeTrivia?: boolean): string { + if (isGeneratedIdentifier(node)) { + return generateName(node); + } + else if ((isIdentifier(node) || isPrivateIdentifier(node)) && (nodeIsSynthesized(node) || !node.parent || !currentSourceFile || (node.parent && currentSourceFile && getSourceFileOfNode(node) !== getOriginalNode(currentSourceFile)))) { + return idText(node); + } + else if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { + return getTextOfNode((node).textSourceNode!, includeTrivia); + } + else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { + return node.text; + } - case SyntaxKind.TypeAliasDeclaration: - Debug.type(node); - return factory.updateTypeAliasDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitIdentifierName(node.name), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitTypeNode(node.type)); + return getSourceTextOfNodeFromSourceFile(currentSourceFile!, node, includeTrivia); + } - case SyntaxKind.EnumDeclaration: - Debug.type(node); - return factory.updateEnumDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitIdentifierName(node.name), - visitList(node.members, isEnumMember)); + function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { + if (node.kind === SyntaxKind.StringLiteral && (node).textSourceNode) { + const textSourceNode = (node).textSourceNode!; + if (isIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) { + const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode); + return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` : + neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` : + `"${escapeNonAsciiString(text)}"`; + } + else { + return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); + } + } - case SyntaxKind.ModuleDeclaration: - Debug.type(node); - return factory.updateModuleDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitModuleName(node.name), - visit(node.body, isModuleBody)); + const flags = (neverAsciiEscape ? GetLiteralTextFlags.NeverAsciiEscape : 0) + | (jsxAttributeEscape ? GetLiteralTextFlags.JsxAttributeEscape : 0) + | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) + | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); - case SyntaxKind.ModuleBlock: - Debug.type(node); - return factory.updateModuleBlock(node, - visitList(node.statements, isStatement)); + return getLiteralText(node, currentSourceFile!, flags); + } - case SyntaxKind.CaseBlock: - Debug.type(node); - return factory.updateCaseBlock(node, - visitList(node.clauses, isCaseOrDefaultClause)); + /** + * Push a new name generation scope. + */ + function pushNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; + } + tempFlagsStack.push(tempFlags); + tempFlags = 0; + reservedNamesStack.push(reservedNames); + } - case SyntaxKind.NamespaceExportDeclaration: - Debug.type(node); - return factory.updateNamespaceExportDeclaration(node, - visitIdentifierName(node.name)); + /** + * Pop the current name generation scope. + */ + function popNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; + } + tempFlags = tempFlagsStack.pop()!; + reservedNames = reservedNamesStack.pop()!; + } - case SyntaxKind.ImportEqualsDeclaration: - Debug.type(node); - return factory.updateImportEqualsDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - node.isTypeOnly, - visitIdentifierName(node.name), - visit(node.moduleReference, isModuleReference)); + function reserveNameInNestedScopes(name: string) { + if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { + reservedNames = new Set(); + } + reservedNames.add(name); + } - case SyntaxKind.ImportDeclaration: - Debug.type(node); - return factory.updateImportDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visit(node.importClause, isImportClause), - visitExpression(node.moduleSpecifier)); + function generateNames(node: Node | undefined) { + if (!node) return; + switch (node.kind) { + case SyntaxKind.Block: + forEach((node).statements, generateNames); + break; + case SyntaxKind.LabeledStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + generateNames((node).statement); + break; + case SyntaxKind.IfStatement: + generateNames((node).thenStatement); + generateNames((node).elseStatement); + break; + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + generateNames((node).initializer); + generateNames((node).statement); + break; + case SyntaxKind.SwitchStatement: + generateNames((node).caseBlock); + break; + case SyntaxKind.CaseBlock: + forEach((node).clauses, generateNames); + break; + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + forEach((node).statements, generateNames); + break; + case SyntaxKind.TryStatement: + generateNames((node).tryBlock); + generateNames((node).catchClause); + generateNames((node).finallyBlock); + break; + case SyntaxKind.CatchClause: + generateNames((node).variableDeclaration); + generateNames((node).block); + break; + case SyntaxKind.VariableStatement: + generateNames((node).declarationList); + break; + case SyntaxKind.VariableDeclarationList: + forEach((node).declarations, generateNames); + break; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + generateNameIfNeeded((node).name); + break; + case SyntaxKind.FunctionDeclaration: + generateNameIfNeeded((node).name); + if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + forEach((node).parameters, generateNames); + generateNames((node).body); + } + break; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + forEach((node).elements, generateNames); + break; + case SyntaxKind.ImportDeclaration: + generateNames((node).importClause); + break; + case SyntaxKind.ImportClause: + generateNameIfNeeded((node).name); + generateNames((node).namedBindings); + break; + case SyntaxKind.NamespaceImport: + generateNameIfNeeded((node).name); + break; + case SyntaxKind.NamespaceExport: + generateNameIfNeeded((node).name); + break; + case SyntaxKind.NamedImports: + forEach((node).elements, generateNames); + break; + case SyntaxKind.ImportSpecifier: + generateNameIfNeeded((node).propertyName || (node).name); + break; + } + } - case SyntaxKind.ImportClause: - Debug.type(node); - return factory.updateImportClause(node, - node.isTypeOnly, - visitIdentifierName(node.name), - visit(node.namedBindings, isNamedImportBindings)); + function generateMemberNames(node: Node | undefined) { + if (!node) return; + switch (node.kind) { + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + generateNameIfNeeded((node).name); + break; + } + } - case SyntaxKind.NamespaceImport: - Debug.type(node); - return factory.updateNamespaceImport(node, - visitIdentifierName(node.name)); + function generateNameIfNeeded(name: DeclarationName | undefined) { + if (name) { + if (isGeneratedIdentifier(name)) { + generateName(name); + } + else if (isBindingPattern(name)) { + generateNames(name); + } + } + } - case SyntaxKind.NamespaceExport: - Debug.type(node); - return factory.updateNamespaceExport(node, - visitIdentifierName(node.name)); + /** + * Generate the text for a generated identifier. + */ + function generateName(name: GeneratedIdentifier) { + if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { + // Node names generate unique names based on their original node + // and are cached based on that node's id. + return generateNameCached(getNodeForGeneratedName(name), name.autoGenerateFlags); + } + else { + // Auto, Loop, and Unique names are cached based on their unique + // autoGenerateId. + const autoGenerateId = name.autoGenerateId!; + return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); + } + } - case SyntaxKind.NamedImports: - Debug.type(node); - return factory.updateNamedImports(node, - visitList(node.elements, isImportSpecifier)); + function generateNameCached(node: Node, flags?: GeneratedIdentifierFlags) { + const nodeId = getNodeId(node); + return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, flags)); + } - case SyntaxKind.ImportSpecifier: - Debug.type(node); - return factory.updateImportSpecifier(node, - visitIdentifierName(node.propertyName), - visitIdentifierName(node.name)); + /** + * Returns a value indicating whether a name is unique globally, within the current file, + * or within the NameGenerator. + */ + function isUniqueName(name: string): boolean { + return isFileLevelUniqueName(name) + && !generatedNames.has(name) + && !(reservedNames && reservedNames.has(name)); + } - case SyntaxKind.ExportAssignment: - Debug.type(node); - return factory.updateExportAssignment(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitExpression(node.expression)); + /** + * Returns a value indicating whether a name is unique globally or within the current file. + */ + function isFileLevelUniqueName(name: string) { + return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; + } - case SyntaxKind.ExportDeclaration: - Debug.type(node); - return factory.updateExportDeclaration(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - node.isTypeOnly, - visit(node.exportClause, isNamedExportBindings), - visitExpression(node.moduleSpecifier)); + /** + * Returns a value indicating whether a name is unique within a container. + */ + function isUniqueLocalName(name: string, container: Node): boolean { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { + if (node.locals) { + const local = node.locals.get(escapeLeadingUnderscores(name)); + // We conservatively include alias symbols to cover cases where they're emitted as locals + if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { + return false; + } + } + } + return true; + } - case SyntaxKind.NamedExports: - Debug.type(node); - return factory.updateNamedExports(node, - visitList(node.elements, isExportSpecifier)); + /** + * Return the next available name in the pattern _a ... _z, _0, _1, ... + * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. + * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. + */ + function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { + if (flags && !(tempFlags & flags)) { + const name = flags === TempFlags._i ? "_i" : "_n"; + if (isUniqueName(name)) { + tempFlags |= flags; + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } + return name; + } + } + while (true) { + const count = tempFlags & TempFlags.CountMask; + tempFlags++; + // Skip over 'i' and 'n' + if (count !== 8 && count !== 13) { + const name = count < 26 + ? "_" + String.fromCharCode(CharacterCodes.a + count) + : "_" + (count - 26); + if (isUniqueName(name)) { + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } + return name; + } + } + } + } - case SyntaxKind.ExportSpecifier: - Debug.type(node); - return factory.updateExportSpecifier(node, - visitIdentifierName(node.propertyName), - visitIdentifierName(node.name)); + /** + * Generate a name that is unique within the current file and doesn't conflict with any names + * in global scope. The name is formed by adding an '_n' suffix to the specified base name, + * where n is a positive integer. Note that names generated by makeTempVariableName and + * makeUniqueName are guaranteed to never conflict. + * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' + */ + function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic?: boolean, scoped?: boolean): string { + if (optimistic) { + if (checkFn(baseName)) { + if (scoped) { + reserveNameInNestedScopes(baseName); + } + else { + generatedNames.add(baseName); + } + return baseName; + } + } + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + let i = 1; + while (true) { + const generatedName = baseName + i; + if (checkFn(generatedName)) { + if (scoped) { + reserveNameInNestedScopes(generatedName); + } + else { + generatedNames.add(generatedName); + } + return generatedName; + } + i++; + } + } - case SyntaxKind.MissingDeclaration: - return node; + function makeFileLevelOptimisticUniqueName(name: string) { + return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true); + } - // Module references - case SyntaxKind.ExternalModuleReference: - Debug.type(node); - return factory.updateExternalModuleReference(node, - visitExpression(node.expression)); + /** + * Generates a unique name for a ModuleDeclaration or EnumDeclaration. + */ + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + const name = getTextOfNode(node.name); + // Use module/enum name itself if it is unique, otherwise make a unique variation + return isUniqueLocalName(name, node) ? name : makeUniqueName(name); + } - // JSX (non-expression) - case SyntaxKind.JsxOpeningElement: - Debug.type(node); - return factory.updateJsxOpeningElement(node, - visitExpression(node.tagName, isJsxTagNameExpression), - visitList(node.typeArguments, isTypeNode), - visit(node.attributes, isJsxAttributes)); + /** + * Generates a unique name for an ImportDeclaration or ExportDeclaration. + */ + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + const expr = getExternalModuleName(node)!; // TODO: GH#18217 + const baseName = isStringLiteral(expr) ? + makeIdentifierFromModuleName(expr.text) : "module"; + return makeUniqueName(baseName); + } - case SyntaxKind.JsxClosingElement: - Debug.type(node); - return factory.updateJsxClosingElement(node, - visitExpression(node.tagName, isJsxTagNameExpression)); + /** + * Generates a unique name for a default export. + */ + function generateNameForExportDefault() { + return makeUniqueName("default"); + } - case SyntaxKind.JsxAttribute: - Debug.type(node); - return factory.updateJsxAttribute(node, - visitIdentifierName(node.name), - visitJsxAttributeValue(node.initializer)); + /** + * Generates a unique name for a class expression. + */ + function generateNameForClassExpression() { + return makeUniqueName("class"); + } - case SyntaxKind.JsxAttributes: - Debug.type(node); - return factory.updateJsxAttributes(node, - visitList(node.properties, isJsxAttributeLike)); + function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration) { + if (isIdentifier(node.name)) { + return generateNameCached(node.name); + } + return makeTempVariableName(TempFlags.Auto); + } - case SyntaxKind.JsxSpreadAttribute: - Debug.type(node); - return factory.updateJsxSpreadAttribute(node, - visitExpression(node.expression)); + /** + * Generates a unique name from a node. + */ + function generateNameForNode(node: Node, flags?: GeneratedIdentifierFlags): string { + switch (node.kind) { + case SyntaxKind.Identifier: + return makeUniqueName( + getTextOfNode(node), + isUniqueName, + !!(flags! & GeneratedIdentifierFlags.Optimistic), + !!(flags! & GeneratedIdentifierFlags.ReservedInNestedScopes) + ); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + return generateNameForModuleOrEnum(node); + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return generateNameForImportOrExportDeclaration(node); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExportAssignment: + return generateNameForExportDefault(); + case SyntaxKind.ClassExpression: + return generateNameForClassExpression(); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return generateNameForMethodOrAccessor(node); + case SyntaxKind.ComputedPropertyName: + return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); + default: + return makeTempVariableName(TempFlags.Auto); + } + } - case SyntaxKind.JsxExpression: - Debug.type(node); - return factory.updateJsxExpression(node, - visitExpression(node.expression)); + /** + * Generates a unique identifier for a node. + */ + function makeName(name: GeneratedIdentifier) { + switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + case GeneratedIdentifierFlags.Auto: + return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Loop: + return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Unique: + return makeUniqueName( + idText(name), + (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, + !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), + !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes) + ); + } - // Clauses - case SyntaxKind.CaseClause: - Debug.type(node); - return factory.updateCaseClause(node, - visitExpression(node.expression), - visitList(node.statements, isStatement)); + return Debug.fail("Unsupported GeneratedIdentifierKind."); + } - case SyntaxKind.DefaultClause: - Debug.type(node); - return factory.updateDefaultClause(node, - visitList(node.statements, isStatement)); + /** + * Gets the node from which a name should be generated. + */ + function getNodeForGeneratedName(name: GeneratedIdentifier) { + const autoGenerateId = name.autoGenerateId; + let node = name as Node; + let original = node.original; + while (original) { + node = original; - case SyntaxKind.HeritageClause: - Debug.type(node); - return factory.updateHeritageClause(node, - visitList(node.types, isExpressionWithTypeArguments)); + // if "node" is a different generated name (having a different + // "autoGenerateId"), use it and stop traversing. + if (isIdentifier(node) + && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) + && node.autoGenerateId !== autoGenerateId) { + break; + } - case SyntaxKind.CatchClause: - Debug.type(node); - return factory.updateCatchClause(node, - visit(node.variableDeclaration, isVariableDeclaration), - visit(node.block, isBlock)); + original = node.original; + } - // Property assignments - case SyntaxKind.PropertyAssignment: - Debug.type(node); - return factory.updatePropertyAssignment(node, - visitPropertyName(node.name), - visitExpression(node.initializer)); + // otherwise, return the original node for the source; + return node; + } - case SyntaxKind.ShorthandPropertyAssignment: - Debug.type(node); - return factory.updateShorthandPropertyAssignment(node, - visitIdentifierName(node.name, isIdentifier), - visitExpression(node.objectAssignmentInitializer)); + // Comments - case SyntaxKind.SpreadAssignment: - Debug.type(node); - return factory.updateSpreadAssignment(node, - visitExpression(node.expression)); + function emitLeadingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number) { + enterComment(); + hasWrittenComment = false; - // Enum - case SyntaxKind.EnumMember: - Debug.type(node); - return factory.updateEnumMember(node, - visitPropertyName(node.name), - visitExpression(node.initializer)); + // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. + // It is expensive to walk entire tree just to set one kind of node to have no comments. + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - // JSDoc nodes (only used in codefixes currently) - case SyntaxKind.JSDocTypeExpression: - case SyntaxKind.JSDocNameReference: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocOptionalType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocVariadicType: - case SyntaxKind.JSDocNamepathType: - case SyntaxKind.JSDocComment: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JSDocSignature: - case SyntaxKind.JSDocTag: - case SyntaxKind.JSDocAugmentsTag: - case SyntaxKind.JSDocImplementsTag: - case SyntaxKind.JSDocAuthorTag: - case SyntaxKind.JSDocDeprecatedTag: - case SyntaxKind.JSDocClassTag: - case SyntaxKind.JSDocPublicTag: - case SyntaxKind.JSDocPrivateTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocReadonlyTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocTemplateTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocSeeTag: - return node; + // Save current container state on the stack. + if ((pos > 0 || end > 0) && pos !== end) { + // Emit leading comments if the position is not synthesized and the node + // has not opted out from emitting leading comments. + if (!skipLeadingComments) { + emitLeadingComments(pos, /*isEmittedNode*/ node.kind !== SyntaxKind.NotEmittedStatement); + } - // Transformation nodes (ignored) + if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { + // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. + containerPos = pos; } - if (isExpression(node)) { - // If this was an expression that was originally in an `Unspecified` hint, - // re-trigger substitution using the correct hint. - hint = EmitHint.Expression; - if (substituteNode !== noEmitSubstitution) { - node = substituteNode(hint, node); + if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { + // As above. + containerEnd = end; + + // To avoid invalid comment emit in a down-level binding pattern, we + // keep track of the last declaration list container's end + if (node.kind === SyntaxKind.VariableDeclarationList) { + declarationListContainerEnd = end; } } - else if (isSourceFile(node)) { - return preprintSourceFile(node); + } + forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); + exitComment(); + } + + function emitTrailingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { + enterComment(); + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); + if ((pos > 0 || end > 0) && pos !== end) { + // Restore previous container state. + containerPos = savedContainerPos; + containerEnd = savedContainerEnd; + declarationListContainerEnd = savedDeclarationListContainerEnd; + + // Emit trailing comments if the position is not synthesized and the node + // has not opted out from emitting leading comments and is an emitted node. + if (!skipTrailingComments && node.kind !== SyntaxKind.NotEmittedStatement) { + emitTrailingComments(end); } } + exitComment(); + } - if (hint === EmitHint.Expression) { - switch (node.kind) { - // Identifiers - case SyntaxKind.Identifier: - return preprintIdentifier(node as Identifier); + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.hasLeadingNewline || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); + } + } - // Expression - case SyntaxKind.ArrayLiteralExpression: - Debug.type(node); - return factory.updateArrayLiteralExpression(node, - visitExpressionList(node.elements)); + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine) { + writer.writeLine(); + } + } - case SyntaxKind.ObjectLiteralExpression: - Debug.type(node); - return factory.updateObjectLiteralExpression(node, - visitList(node.properties, isObjectLiteralElementLike)); + function writeSynthesizedComment(comment: SynthesizedComment) { + const text = formatSynthesizedComment(comment); + const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; + writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); + } - case SyntaxKind.PropertyAccessExpression: - if (node.flags & NodeFlags.OptionalChain) { - Debug.type(node); - return factory.updatePropertyAccessChain(node, - visitExpression(node.expression), - visit(node.questionDotToken, isQuestionDotToken), - visitMemberName(node.name)); - } - Debug.type(node); - return factory.updatePropertyAccessExpression(node, - visitExpression(node.expression), - visitMemberName(node.name)); + function formatSynthesizedComment(comment: SynthesizedComment) { + return comment.kind === SyntaxKind.MultiLineCommentTrivia + ? `/*${comment.text}*/` + : `//${comment.text}`; + } - case SyntaxKind.ElementAccessExpression: - if (node.flags & NodeFlags.OptionalChain) { - Debug.type(node); - return factory.updateElementAccessChain(node, - visitExpression(node.expression), - visit(node.questionDotToken, isQuestionDotToken), - visitExpression(node.argumentExpression)); - } - Debug.type(node); - return factory.updateElementAccessExpression(node, - visitExpression(node.expression), - visitExpression(node.argumentExpression)); + function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { + enterComment(); + const { pos, end } = detachedRange; + const emitFlags = getEmitFlags(node); + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); + } - case SyntaxKind.CallExpression: - if (node.flags & NodeFlags.OptionalChain) { - Debug.type(node); - return factory.updateCallChain(node, - visitExpression(node.expression), - visit(node.questionDotToken, isQuestionDotToken), - visitTypeNodeList(node.typeArguments), - visitExpressionList(node.arguments)); - } - Debug.type(node); - return factory.updateCallExpression(node, - visitExpression(node.expression), - visitTypeNodeList(node.typeArguments), - visitExpressionList(node.arguments)); + exitComment(); + if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { + commentsDisabled = true; + emitCallback(node); + commentsDisabled = false; + } + else { + emitCallback(node); + } - case SyntaxKind.NewExpression: - Debug.type(node); - return factory.updateNewExpression(node, - visitExpression(node.expression), - visitTypeNodeList(node.typeArguments), - visitExpressionList(node.arguments)); + enterComment(); + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { + writer.writeLine(); + } + } + exitComment(); - case SyntaxKind.TaggedTemplateExpression: - Debug.type(node); - return factory.updateTaggedTemplateExpression(node, - visitExpression(node.tag), - visitTypeNodeList(node.typeArguments), - visitExpression(node.template, isTemplateLiteral)); + } - case SyntaxKind.TypeAssertionExpression: - Debug.type(node); - return factory.updateTypeAssertion(node, - visitTypeNode(node.type), - visitExpression(node.expression)); + function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) { + if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) { + return false; + } - case SyntaxKind.ParenthesizedExpression: - Debug.type(node); - return factory.updateParenthesizedExpression(node, - visitExpression(node.expression)); + if (nextNode.pos < previousNode.end) { + return false; + } - case SyntaxKind.FunctionExpression: - Debug.type(node); - return factory.updateFunctionExpression(node, - visitList(node.modifiers, isModifier), - visit(node.asteriskToken, isAsteriskToken), - visitIdentifierName(node.name), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type), - visitFunctionBody(node.body)); + previousNode = getOriginalNode(previousNode); + nextNode = getOriginalNode(nextNode); + const parent = previousNode.parent; + if (!parent || parent !== nextNode.parent) { + return false; + } - case SyntaxKind.ArrowFunction: - Debug.type(node); - return factory.updateArrowFunction(node, - visitList(node.modifiers, isModifier), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitParameterList(node.parameters), - visitTypeNode(node.type), - visit(node.equalsGreaterThanToken, isEqualsGreaterThanToken), - visitConciseBody(node.body)); + const parentNodeArray = getContainingNodeArray(previousNode); + const prevNodeIndex = parentNodeArray?.indexOf(previousNode); + return prevNodeIndex !== undefined && prevNodeIndex > -1 && parentNodeArray!.indexOf(nextNode) === prevNodeIndex + 1; + } - case SyntaxKind.DeleteExpression: - Debug.type(node); - return factory.updateDeleteExpression(node, - visitExpression(node.expression)); + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + hasWrittenComment = false; - case SyntaxKind.TypeOfExpression: - Debug.type(node); - return factory.updateTypeOfExpression(node, - visitExpression(node.expression)); + if (isEmittedNode) { + if (pos === 0 && currentSourceFile?.isDeclarationFile) { + forEachLeadingCommentToEmit(pos, emitNonTripleSlashLeadingComment); + } + else { + forEachLeadingCommentToEmit(pos, emitLeadingComment); + } + } + else if (pos === 0) { + // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, + // unless it is a triple slash comment at the top of the file. + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted + forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + } + } - case SyntaxKind.VoidExpression: - Debug.type(node); - return factory.updateVoidExpression(node, - visitExpression(node.expression)); + function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + } - case SyntaxKind.AwaitExpression: - Debug.type(node); - return factory.updateAwaitExpression(node, - visitExpression(node.expression)); + function emitNonTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + } - case SyntaxKind.PrefixUnaryExpression: - Debug.type(node); - return factory.updatePrefixUnaryExpression(node, - visitExpression(node.operand)); + function shouldWriteComment(text: string, pos: number) { + if (printerOptions.onlyPrintJsDocStyle) { + return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); + } + return true; + } - case SyntaxKind.PostfixUnaryExpression: - Debug.type(node); - return factory.updatePostfixUnaryExpression(node, - visitExpression(node.operand)); + function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + if (!hasWrittenComment) { + emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); + hasWrittenComment = true; + } - case SyntaxKind.BinaryExpression: - return preprintBinaryExpression(node as BinaryExpression); + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - case SyntaxKind.ConditionalExpression: - Debug.type(node); - return factory.updateConditionalExpression(node, - visitExpression(node.condition), - visit(node.questionToken, isQuestionToken), - visitExpression(node.whenTrue), - visit(node.colonToken, isColonToken), - visitExpression(node.whenFalse)); + if (hasTrailingNewLine) { + writer.writeLine(); + } + else if (kind === SyntaxKind.MultiLineCommentTrivia) { + writer.writeSpace(" "); + } + } - case SyntaxKind.TemplateExpression: - Debug.type(node); - return factory.updateTemplateExpression(node, - visit(node.head, isTemplateHead), - visitList(node.templateSpans, isTemplateSpan)); + function emitLeadingCommentsOfPosition(pos: number) { + if (commentsDisabled || pos === -1) { + return; + } - case SyntaxKind.YieldExpression: - Debug.type(node); - return factory.updateYieldExpression(node, - visit(node.asteriskToken, isAsteriskToken), - visitExpression(node.expression)); + emitLeadingComments(pos, /*isEmittedNode*/ true); + } - case SyntaxKind.SpreadElement: - Debug.type(node); - return factory.updateSpreadElement(node, - visitExpression(node.expression)); + function emitTrailingComments(pos: number) { + forEachTrailingCommentToEmit(pos, emitTrailingComment); + } - case SyntaxKind.ClassExpression: - Debug.type(node); - return factory.updateClassExpression(node, - visitList(node.decorators, isDecorator), - visitList(node.modifiers, isModifier), - visitIdentifierName(node.name), - visitList(node.typeParameters, isTypeParameterDeclaration), - visitList(node.heritageClauses, isHeritageClause), - visitList(node.members, isClassElement)); + function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } - case SyntaxKind.AsExpression: - Debug.type(node); - return factory.updateAsExpression(node, - visitExpression(node.expression), - visitTypeNode(node.type)); + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - case SyntaxKind.NonNullExpression: - if (node.flags & NodeFlags.OptionalChain) { - Debug.type(node); - return factory.updateNonNullChain(node, - visitExpression(node.expression)); - } - Debug.type(node); - return factory.updateNonNullExpression(node, - visitExpression(node.expression)); + if (hasTrailingNewLine) { + writer.writeLine(); + } + } - case SyntaxKind.MetaProperty: - Debug.type(node); - return factory.updateMetaProperty(node, - visitIdentifierName(node.name)); + function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) { + if (commentsDisabled) { + return; + } + enterComment(); + forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition); + exitComment(); + } + function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - // JSX (expression only) - case SyntaxKind.JsxElement: - Debug.type(node); - return factory.updateJsxElement(node, - visit(node.openingElement, isJsxOpeningElement), - visitList(node.children, isJsxChild), - visit(node.closingElement, isJsxClosingElement)); + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - case SyntaxKind.JsxSelfClosingElement: - Debug.type(node); - return factory.updateJsxSelfClosingElement(node, - visitExpression(node.tagName, isJsxTagNameExpression), - visitList(node.typeArguments, isTypeNode), - visit(node.attributes, isJsxAttributes)); + if (kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); // still write a newline for single-line comments, so closing tokens aren't written on the same line + } + } - case SyntaxKind.JsxFragment: - Debug.type(node); - return factory.updateJsxFragment(node, - visit(node.openingFragment, isJsxOpeningFragment), - visitList(node.children, isJsxChild), - visit(node.closingFragment, isJsxClosingFragment)); + function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - // Transformation nodes - case SyntaxKind.PartiallyEmittedExpression: - Debug.type(node); - return factory.updatePartiallyEmittedExpression(node, - visitExpression(node.expression)); + emitPos(commentPos); + writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - case SyntaxKind.CommaListExpression: - Debug.type(node); - return factory.updateCommaListExpression(node, - visitExpressionList(node.elements, isExpression)); - } + if (hasTrailingNewLine) { + writer.writeLine(); } - - if (Debug.shouldAssert(AssertionLevel.Normal)) { - // Any other node should not have children or this list isn't up to date. - Debug.assertMissingNode(forEachChild(node, identity), `Expected ${Debug.formatSyntaxKind(node.kind)} to contain no children.`); + else { + writer.writeSpace(" "); } - - // No need to visit nodes with no children. - return node; } - function preprintSourceFile(node: SourceFile) { - return factory.updateSourceFile(node, - visitList(node.statements, isStatement)); + function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { + if (hasDetachedComments(pos)) { + forEachLeadingCommentWithoutDetachedComments(cb); + } + else { + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); + } + } } - function preprintIdentifier(node: Identifier) { - return factory.updateIdentifier(node, - visitList(node.typeArguments, isTypeNodeOrTypeParameterDeclaration)); + function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { + // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { + forEachTrailingCommentRange(currentSourceFile.text, end, cb); + } } - function preprintTypeParameterDeclaration(node: TypeParameterDeclaration) { - return factory.updateTypeParameterDeclaration(node, - visitIdentifierName(node.name), - visitTypeNode(node.constraint), - visitTypeNode(node.default)); + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; } - function createPreprintBinaryExpression() { - interface WorkArea { - stackIndex: number; - leftStack: Expression[]; - operatorStack: BinaryOperatorToken[]; - rightStack: Expression[]; + function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // get the leading comments from detachedPos + const pos = last(detachedCommentsInfo!).detachedCommentEndPos; + if (detachedCommentsInfo!.length - 1) { + detachedCommentsInfo!.pop(); + } + else { + detachedCommentsInfo = undefined; } - return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); + forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); + } - function onEnter(node: BinaryExpression, state: WorkArea | undefined) { - if (state) { - state.stackIndex++; - state.leftStack[state.stackIndex] = node.left; - state.operatorStack[state.stackIndex] = node.operatorToken; - state.rightStack[state.stackIndex] = node.right; + function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { + const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); } else { - state = { - stackIndex: 0, - leftStack: [node.left], - operatorStack: [node.operatorToken], - rightStack: [node.right], - }; + detachedCommentsInfo = [currentDetachedCommentInfo]; } - return state; } + } - function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { - return maybeVisitExpression(left, state, "left"); + function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { + if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + emitPos(commentPos); + writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + } + + /** + * Determine if the given comment is a triple-slash + * + * @return true if the comment is a triple-slash comment else false + */ + function isTripleSlashComment(commentPos: number, commentEnd: number) { + return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); + } + + // Source Maps + + function getParsedSourceMap(node: UnparsedSource) { + if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { + node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; } + return node.parsedSourceMap || undefined; + } + + /** + * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source + */ + function skipSourceTrivia(source: SourceMapSource, pos: number): number { + return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); + } - function onOperator(operator: BinaryOperatorToken, state: WorkArea, _node: BinaryExpression) { - state.operatorStack[state.stackIndex] = visit(operator, isBinaryOperatorToken); + /** + * Emits a mapping. + * + * If the position is synthetic (undefined or a negative value), no mapping will be + * created. + * + * @param pos The position. + */ + function emitPos(pos: number) { + if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { + return; } - function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { - return maybeVisitExpression(right, state, "right"); + const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); + sourceMapGenerator!.addMapping( + writer.getLine(), + writer.getColumn(), + sourceMapSourceIndex, + sourceLine, + sourceCharacter, + /*nameIndex*/ undefined); + } + + function emitSourcePos(source: SourceMapSource, pos: number) { + if (source !== sourceMapSource) { + const savedSourceMapSource = sourceMapSource; + const savedSourceMapSourceIndex = sourceMapSourceIndex; + setSourceMapSource(source); + emitPos(pos); + resetSourceMapSource(savedSourceMapSource, savedSourceMapSourceIndex); + } + else { + emitPos(pos); } + } - function onExit(node: BinaryExpression, state: WorkArea) { - const left = state.leftStack[state.stackIndex]; - const operator = state.operatorStack[state.stackIndex]; - const right = state.rightStack[state.stackIndex]; - if (state.stackIndex > 0) { - state.stackIndex--; - } - return factory.updateBinaryExpression(node, left, operator, right); + /** + * Emits a token of a node with possible leading and trailing source maps. + * + * @param node The node containing the token. + * @param token The token to emit. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. + */ + function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { + if (sourceMapsDisabled || node && isInJsonFile(node)) { + return emitCallback(token, writer, tokenPos); } - function foldState(state: WorkArea, result: BinaryExpression, side: "left" | "right") { - (side === "left" ? state.leftStack : state.rightStack)[state.stackIndex] = result; - return state; + const emitNode = node && node.emitNode; + const emitFlags = emitNode && emitNode.flags || EmitFlags.None; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; + const source = range && range.source || sourceMapSource; + + tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); + if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); } - function maybeVisitExpression(node: Expression, state: WorkArea, side: "left" | "right") { - // Get the first supported pipeline phase for this node. We can skip several stack - // frames if we aren't doing emit notification, so we check for substitution and - // direct callbacks and execute those immediately. - let pipelinePhase = getPipelinePhase(PreprintPipelinePhase.Notification, node); - if (pipelinePhase === pipelineVisitWithSubstitution) { - // The next phase after substitution is always direct visitation, so we can reduce the call stack - // depth by proceding to the direct visitor. - node = cast(substituteNode(EmitHint.Expression, node), isExpression); - pipelinePhase = pipelineVisitDirect; - } - if (pipelinePhase === pipelineVisitDirect && isBinaryExpression(node)) { - // If we are visiting directly and the next node is a BinaryExpression, we can - // add it to the stack and continue the trampoline. - return node; - } - else { - // Visit the expression and store the result on whichever side we are currently visiting. - (side === "left" ? state.leftStack : state.rightStack)[state.stackIndex] = visitExpression(node, isExpression); - } + tokenPos = emitCallback(token, writer, tokenPos); + + if (range) tokenPos = range.end; + if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); } + + return tokenPos; } - const preprintBinaryExpression = createPreprintBinaryExpression(); + function setSourceMapSource(source: SourceMapSource) { + if (sourceMapsDisabled) { + return; + } + + sourceMapSource = source; - function preprint(hint: EmitHint, node: Node) { - // If we're not performing substitution or notification, we have no work to do here. - if (substituteNode === noEmitSubstitution && - onEmitNode === noEmitNotification) { - return node; + if (source === mostRecentlyAddedSourceMapSource) { + // Fast path for when the new source map is the most recently added, in which case + // we use its captured index without going through the source map generator. + sourceMapSourceIndex = mostRecentlyAddedSourceMapSourceIndex; + return; } - switch (hint) { - case EmitHint.SourceFile: return visitSourceFile(cast(node, isSourceFile)); - case EmitHint.Expression: return visitExpression(cast(node, isExpression)); - case EmitHint.IdentifierName: return visitIdentifierName(cast(node, isIdentifier)); - case EmitHint.MappedTypeParameter: return visitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); - case EmitHint.EmbeddedStatement: return visitEmbeddedStatement(cast(node, isStatement)); - case EmitHint.JsxAttributeValue: return visitJsxAttributeValue(cast(node, isStringLiteralOrJsxExpression)); - default: return visit(node); + + if (isJsonSourceMapSource(source)) { + return; } + + sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); + if (printerOptions.inlineSources) { + sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); + } + + mostRecentlyAddedSourceMapSource = source; + mostRecentlyAddedSourceMapSourceIndex = sourceMapSourceIndex; } - return preprint; + function resetSourceMapSource(source: SourceMapSource, sourceIndex: number) { + sourceMapSource = source; + sourceMapSourceIndex = sourceIndex; + } + + function isJsonSourceMapSource(sourceFile: SourceMapSource) { + return fileExtensionIs(sourceFile.fileName, Extension.Json); + } } function createBracketsMap() { diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 7138f8d4c9f49..750d0d2c9ec02 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -2139,7 +2139,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, name, - initializer + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) ); node.propertyName = asName(propertyName); node.dotDotDotToken = dotDotDotToken; diff --git a/src/compiler/factory/parenthesizerRules.ts b/src/compiler/factory/parenthesizerRules.ts index 069df2d60c9ae..5b5d33b19f0de 100644 --- a/src/compiler/factory/parenthesizerRules.ts +++ b/src/compiler/factory/parenthesizerRules.ts @@ -210,7 +210,7 @@ namespace ts { return parenthesizeBinaryOperand(binaryOperator, leftSide, /*isLeftSideOfBinary*/ true); } - function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression, rightSide: Expression): Expression { + function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression | undefined, rightSide: Expression): Expression { return parenthesizeBinaryOperand(binaryOperator, rightSide, /*isLeftSideOfBinary*/ false, leftSide); } @@ -352,6 +352,8 @@ namespace ts { return expression; } + function parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression; + function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody; function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody { if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { // TODO(rbuckton): Verifiy whether `setTextRange` is needed. diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 015394dcc607b..732205c00a34b 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -562,31 +562,31 @@ namespace ts { } export const nullTransformationContext: TransformationContext = { - get factory() { return factory; }, - enableEmitNotification: noop, - enableSubstitution: noop, - endLexicalEnvironment: returnUndefined, + factory, getCompilerOptions: () => ({}), - getEmitHost: notImplemented, getEmitResolver: notImplemented, + getEmitHost: notImplemented, getEmitHelperFactory: notImplemented, + startLexicalEnvironment: noop, + resumeLexicalEnvironment: noop, + suspendLexicalEnvironment: noop, + endLexicalEnvironment: returnUndefined, setLexicalEnvironmentFlags: noop, getLexicalEnvironmentFlags: () => 0, - hoistFunctionDeclaration: noop, hoistVariableDeclaration: noop, + hoistFunctionDeclaration: noop, addInitializationStatement: noop, - isEmitNotificationEnabled: notImplemented, - isSubstitutionEnabled: notImplemented, - onEmitNode: noop, - onSubstituteNode: notImplemented, - readEmitHelpers: notImplemented, - requestEmitHelper: noop, - resumeLexicalEnvironment: noop, - startLexicalEnvironment: noop, - suspendLexicalEnvironment: noop, - addDiagnostic: noop, startBlockScope: noop, endBlockScope: returnUndefined, - addBlockScopedVariable: noop + addBlockScopedVariable: noop, + requestEmitHelper: noop, + readEmitHelpers: notImplemented, + enableSubstitution: noop, + enableEmitNotification: noop, + isSubstitutionEnabled: notImplemented, + isEmitNotificationEnabled: notImplemented, + onSubstituteNode: noEmitSubstitution, + onEmitNode: noEmitNotification, + addDiagnostic: noop, }; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 63b20f49139a6..701ddfc0511d1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6798,6 +6798,7 @@ namespace ts { parenthesizeExpressionsOfCommaDelimitedList(elements: readonly Expression[]): NodeArray; parenthesizeExpressionForDisallowedComma(expression: Expression): Expression; parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression; + parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression; parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody; parenthesizeMemberOfConditionalType(member: TypeNode): TypeNode; parenthesizeMemberOfElementType(member: TypeNode): TypeNode; diff --git a/tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.js b/tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.js index da09d825b0f95..177f997297b44 100644 --- a/tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.js +++ b/tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.js @@ -161,4 +161,6 @@ var _brokenTree = (0, renderer_1.dom)(component_1.MySFC, { x: 1, y: 2 }, (0, renderer_1.dom)(component_1.MyClass, { x: 3, y: 4 }), (0, renderer_1.dom)(component_1.MyClass, { x: 5, y: 6 })); // Should fail, nondom isn't allowed as children of dom -var _brokenTree2 = (0, renderer_1.dom)(DOMSFC, { x: 1, y: 2 }, component_1.tree, component_1.tree); +var _brokenTree2 = (0, renderer_1.dom)(DOMSFC, { x: 1, y: 2 }, + component_1.tree, + component_1.tree); diff --git a/tests/baselines/reference/jsxCheckJsxNoTypeArgumentsAllowed.js b/tests/baselines/reference/jsxCheckJsxNoTypeArgumentsAllowed.js index 8eb7b03f2b7ac..a1a7692ca7655 100644 --- a/tests/baselines/reference/jsxCheckJsxNoTypeArgumentsAllowed.js +++ b/tests/baselines/reference/jsxCheckJsxNoTypeArgumentsAllowed.js @@ -23,5 +23,5 @@ let x = a={10} b="hi" />; // error, no type arguments in js exports.__esModule = true; var component_1 = require("./component"); var React = require("react"); -var x = (, a={10} b="hi" />; // error, no type arguments in js -); +var x = , a={10} b="hi" />; // error, no type arguments in js +;