1import { 2 AccessExpression, append, appendIfUnique, Debug, EmitFlags, EmitHelper, EmitNode, getParseTreeNode, 3 getSourceFileOfNode, isParseTreeNode, Node, orderedRemoveItem, SnippetElement, some, SourceFile, SourceMapRange, 4 SyntaxKind, SynthesizedComment, TextRange, TypeNode 5} from "../_namespaces/ts"; 6 7/** 8 * Associates a node with the current transformation, initializing 9 * various transient transformation properties. 10 * @internal 11 */ 12export function getOrCreateEmitNode(node: Node): EmitNode { 13 if (!node.emitNode) { 14 if (isParseTreeNode(node)) { 15 // To avoid holding onto transformation artifacts, we keep track of any 16 // parse tree node we are annotating. This allows us to clean them up after 17 // all transformations have completed. 18 if (node.kind === SyntaxKind.SourceFile) { 19 return node.emitNode = { annotatedNodes: [node] } as EmitNode; 20 } 21 22 const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file."); 23 getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); 24 } 25 26 node.emitNode = {} as EmitNode; 27 } 28 else { 29 Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); 30 } 31 return node.emitNode; 32} 33 34/** 35 * Clears any `EmitNode` entries from parse-tree nodes. 36 * @param sourceFile A source file. 37 */ 38export function disposeEmitNodes(sourceFile: SourceFile | undefined) { 39 // During transformation we may need to annotate a parse tree node with transient 40 // transformation properties. As parse tree nodes live longer than transformation 41 // nodes, we need to make sure we reclaim any memory allocated for custom ranges 42 // from these nodes to ensure we do not hold onto entire subtrees just for position 43 // information. We also need to reset these nodes to a pre-transformation state 44 // for incremental parsing scenarios so that we do not impact later emit. 45 const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes; 46 if (annotatedNodes) { 47 for (const node of annotatedNodes) { 48 node.emitNode = undefined; 49 } 50 } 51} 52 53/** 54 * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. 55 * @internal 56 */ 57export function removeAllComments<T extends Node>(node: T): T { 58 const emitNode = getOrCreateEmitNode(node); 59 emitNode.flags |= EmitFlags.NoComments; 60 emitNode.leadingComments = undefined; 61 emitNode.trailingComments = undefined; 62 return node; 63} 64 65/** 66 * Sets flags that control emit behavior of a node. 67 */ 68export function setEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags) { 69 getOrCreateEmitNode(node).flags = emitFlags; 70 return node; 71} 72 73/** 74 * Sets flags that control emit behavior of a node. 75 * @internal 76 */ 77export function addEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags) { 78 const emitNode = getOrCreateEmitNode(node); 79 emitNode.flags = emitNode.flags | emitFlags; 80 return node; 81} 82 83/** 84 * Gets a custom text range to use when emitting source maps. 85 */ 86export function getSourceMapRange(node: Node): SourceMapRange { 87 return node.emitNode?.sourceMapRange ?? node; 88} 89 90/** 91 * Sets a custom text range to use when emitting source maps. 92 */ 93export function setSourceMapRange<T extends Node>(node: T, range: SourceMapRange | undefined) { 94 getOrCreateEmitNode(node).sourceMapRange = range; 95 return node; 96} 97 98/** 99 * Gets the TextRange to use for source maps for a token of a node. 100 */ 101export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { 102 return node.emitNode?.tokenSourceMapRanges?.[token]; 103} 104 105/** 106 * Sets the TextRange to use for source maps for a token of a node. 107 */ 108export function setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { 109 const emitNode = getOrCreateEmitNode(node); 110 const tokenSourceMapRanges = emitNode.tokenSourceMapRanges ?? (emitNode.tokenSourceMapRanges = []); 111 tokenSourceMapRanges[token] = range; 112 return node; 113} 114 115/** 116 * Gets a custom text range to use when emitting comments. 117 * 118 * @internal 119 */ 120export function getStartsOnNewLine(node: Node) { 121 return node.emitNode?.startsOnNewLine; 122} 123 124/** 125 * Sets a custom text range to use when emitting comments. 126 * 127 * @internal 128 */ 129export function setStartsOnNewLine<T extends Node>(node: T, newLine: boolean) { 130 getOrCreateEmitNode(node).startsOnNewLine = newLine; 131 return node; 132} 133 134/** 135 * Gets a custom text range to use when emitting comments. 136 */ 137export function getCommentRange(node: Node): TextRange { 138 return node.emitNode?.commentRange ?? node; 139} 140 141/** 142 * Sets a custom text range to use when emitting comments. 143 */ 144export function setCommentRange<T extends Node>(node: T, range: TextRange) { 145 getOrCreateEmitNode(node).commentRange = range; 146 return node; 147} 148 149export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { 150 return node.emitNode?.leadingComments; 151} 152 153export function setSyntheticLeadingComments<T extends Node>(node: T, comments: SynthesizedComment[] | undefined) { 154 getOrCreateEmitNode(node).leadingComments = comments; 155 return node; 156} 157 158export function addSyntheticLeadingComment<T extends Node>(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { 159 return setSyntheticLeadingComments(node, append<SynthesizedComment>(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); 160} 161 162export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { 163 return node.emitNode?.trailingComments; 164} 165 166export function setSyntheticTrailingComments<T extends Node>(node: T, comments: SynthesizedComment[] | undefined) { 167 getOrCreateEmitNode(node).trailingComments = comments; 168 return node; 169} 170 171export function addSyntheticTrailingComment<T extends Node>(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { 172 return setSyntheticTrailingComments(node, append<SynthesizedComment>(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); 173} 174 175export function moveSyntheticComments<T extends Node>(node: T, original: Node): T { 176 setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); 177 setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); 178 const emit = getOrCreateEmitNode(original); 179 emit.leadingComments = undefined; 180 emit.trailingComments = undefined; 181 return node; 182} 183 184/** 185 * Gets the constant value to emit for an expression representing an enum. 186 */ 187export function getConstantValue(node: AccessExpression): string | number | undefined { 188 return node.emitNode?.constantValue; 189} 190 191/** 192 * Sets the constant value to emit for an expression. 193 */ 194export function setConstantValue(node: AccessExpression, value: string | number): AccessExpression { 195 const emitNode = getOrCreateEmitNode(node); 196 emitNode.constantValue = value; 197 return node; 198} 199 200/** 201 * Adds an EmitHelper to a node. 202 */ 203export function addEmitHelper<T extends Node>(node: T, helper: EmitHelper): T { 204 const emitNode = getOrCreateEmitNode(node); 205 emitNode.helpers = append(emitNode.helpers, helper); 206 return node; 207} 208 209/** 210 * Add EmitHelpers to a node. 211 */ 212export function addEmitHelpers<T extends Node>(node: T, helpers: EmitHelper[] | undefined): T { 213 if (some(helpers)) { 214 const emitNode = getOrCreateEmitNode(node); 215 for (const helper of helpers) { 216 emitNode.helpers = appendIfUnique(emitNode.helpers, helper); 217 } 218 } 219 return node; 220} 221 222/** 223 * Removes an EmitHelper from a node. 224 */ 225export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { 226 const helpers = node.emitNode?.helpers; 227 if (helpers) { 228 return orderedRemoveItem(helpers, helper); 229 } 230 return false; 231} 232 233/** 234 * Gets the EmitHelpers of a node. 235 */ 236export function getEmitHelpers(node: Node): EmitHelper[] | undefined { 237 return node.emitNode?.helpers; 238} 239 240/** 241 * Moves matching emit helpers from a source node to a target node. 242 */ 243export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { 244 const sourceEmitNode = source.emitNode; 245 const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; 246 if (!some(sourceEmitHelpers)) return; 247 248 const targetEmitNode = getOrCreateEmitNode(target); 249 let helpersRemoved = 0; 250 for (let i = 0; i < sourceEmitHelpers.length; i++) { 251 const helper = sourceEmitHelpers[i]; 252 if (predicate(helper)) { 253 helpersRemoved++; 254 targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); 255 } 256 else if (helpersRemoved > 0) { 257 sourceEmitHelpers[i - helpersRemoved] = helper; 258 } 259 } 260 261 if (helpersRemoved > 0) { 262 sourceEmitHelpers.length -= helpersRemoved; 263 } 264} 265 266/** 267 * Gets the SnippetElement of a node. 268 * 269 * @internal 270 */ 271export function getSnippetElement(node: Node): SnippetElement | undefined { 272 return node.emitNode?.snippetElement; 273} 274 275/** 276 * Sets the SnippetElement of a node. 277 * 278 * @internal 279 */ 280export function setSnippetElement<T extends Node>(node: T, snippet: SnippetElement): T { 281 const emitNode = getOrCreateEmitNode(node); 282 emitNode.snippetElement = snippet; 283 return node; 284} 285 286/** @internal */ 287export function ignoreSourceNewlines<T extends Node>(node: T): T { 288 getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; 289 return node; 290} 291 292/** @internal */ 293export function setTypeNode<T extends Node>(node: T, type: TypeNode): T { 294 const emitNode = getOrCreateEmitNode(node); 295 emitNode.typeNode = type; 296 return node; 297} 298 299/** @internal */ 300export function getTypeNode<T extends Node>(node: T): TypeNode | undefined { 301 return node.emitNode?.typeNode; 302} 303