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