From 07660c8307affd57c10735ddff252cc3521485a1 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 22 Apr 2022 11:18:53 -0700 Subject: [PATCH] Fix emit for undefined SourceFile (#48774) Co-authored-by: Ron Buckton --- src/compiler/emitter.ts | 113 +++++++++++++++++++------------------- src/compiler/utilities.ts | 4 +- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 28f0d766f72..c83713750f2 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1172,7 +1172,7 @@ namespace ts { } function getCurrentLineMap() { - return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!)); + return currentLineMap || (currentLineMap = getLineStarts(Debug.checkDefined(currentSourceFile))); } function emit(node: Node, parenthesizerRule?: (node: Node) => Node): void; @@ -1817,7 +1817,7 @@ namespace ts { 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 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); @@ -2467,7 +2467,7 @@ namespace ts { } const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - const allowTrailingComma = currentSourceFile!.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile!) ? ListFormat.AllowTrailingComma : ListFormat.None; + const allowTrailingComma = currentSourceFile && currentSourceFile.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile) ? ListFormat.AllowTrailingComma : ListFormat.None; emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); if (indentedFlag) { @@ -2882,7 +2882,7 @@ namespace ts { 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)) { + if (!currentSourceFile || !isJsonSourceFile(currentSourceFile) || nodeIsSynthesized(node.expression)) { writeTrailingSemicolon(); } } @@ -3209,7 +3209,7 @@ namespace ts { return false; } - if (!nodeIsSynthesized(body) && !rangeIsOnSingleLine(body, currentSourceFile!)) { + if (!nodeIsSynthesized(body) && currentSourceFile && !rangeIsOnSingleLine(body, currentSourceFile)) { return false; } @@ -3240,12 +3240,7 @@ namespace ts { ? emitBlockFunctionBodyOnSingleLine : emitBlockFunctionBodyWorker; - if (emitBodyWithDetachedComments) { - emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); - } - else { - emitBlockFunctionBody(body); - } + emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); decreaseIndent(); writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); @@ -3703,9 +3698,10 @@ namespace ts { statements.length === 1 && ( // treat synthesized nodes as located on the same line for emit purposes + !currentSourceFile || nodeIsSynthesized(parentNode) || nodeIsSynthesized(statements[0]) || - rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile!) + rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile) ); let format = ListFormat.CaseOrDefaultClauseStatements; @@ -3960,16 +3956,14 @@ namespace ts { 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; - } + // 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); } @@ -4371,7 +4365,7 @@ namespace ts { if (isEmpty) { // Write a line terminator if the parent node was multi-line - if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!)))) { + if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile)))) { writeLine(); } else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { @@ -4675,7 +4669,7 @@ namespace ts { const firstChild = children[0]; if (firstChild === undefined) { - return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; + return !parentNode || currentSourceFile && 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 @@ -4699,7 +4693,7 @@ namespace ts { // JsxText will be written with its leading whitespace, so don't add more manually. return 0; } - if (parentNode && + if (currentSourceFile && parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(firstChild) && (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) @@ -4712,7 +4706,7 @@ namespace ts { currentSourceFile!, includeComments)); } - return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!) ? 0 : 1; + return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile) ? 0 : 1; } if (synthesizedNodeStartsOnNewLine(firstChild, format)) { return 1; @@ -4730,7 +4724,7 @@ namespace ts { // JsxText will be written with its leading whitespace, so don't add more manually. return 0; } - else if (!nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { + else if (currentSourceFile && !nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { return getEffectiveLines( includeComments => getLinesBetweenRangeEndAndRangeStart( @@ -4745,7 +4739,7 @@ namespace ts { // expensive than checking with `preserveSourceNewlines` as above, but the goal is not to preserve the // effective source lines between two sibling nodes. else if (!preserveSourceNewlines && originalNodesHaveSameParent(previousNode, nextNode)) { - return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!) ? 0 : 1; + return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile) ? 0 : 1; } // If the two nodes are not comparable, add a line terminator based on the format that can indicate // whether new lines are preferred or not. @@ -4769,9 +4763,9 @@ namespace ts { const lastChild = lastOrUndefined(children); if (lastChild === undefined) { - return !parentNode || rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1; + return !parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile) ? 0 : 1; } - if (parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { + if (currentSourceFile && 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( @@ -4781,7 +4775,7 @@ namespace ts { currentSourceFile!, includeComments)); } - return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!) ? 0 : 1; + return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile) ? 0 : 1; } if (synthesizedNodeStartsOnNewLine(lastChild, format)) { return 1; @@ -4859,7 +4853,7 @@ namespace ts { return 1; } - if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { + if (currentSourceFile && !nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { if (preserveSourceNewlines) { return getEffectiveLines( includeComments => getLinesBetweenRangeEndAndRangeStart( @@ -4868,7 +4862,7 @@ namespace ts { currentSourceFile!, includeComments)); } - return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!) ? 0 : 1; + return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile) ? 0 : 1; } return 0; @@ -4876,7 +4870,7 @@ namespace ts { function isEmptyBlock(block: BlockLike) { return block.statements.length === 0 - && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile!); + && (!currentSourceFile || rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile)); } function skipSynthesizedParentheses(node: Node) { @@ -4887,21 +4881,27 @@ namespace ts { return node; } - function getTextOfNode(node: Node, includeTrivia?: boolean): string { + function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression, 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); + if (isStringLiteral(node) && node.textSourceNode) { + return getTextOfNode(node.textSourceNode, includeTrivia); } - else if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { - return getTextOfNode((node as StringLiteral).textSourceNode!, includeTrivia); + const sourceFile = currentSourceFile; // const needed for control flow + const canUseSourceFile = !!sourceFile && !!node.parent && !nodeIsSynthesized(node); + if (isMemberName(node)) { + if (!canUseSourceFile || getSourceFileOfNode(node) !== getOriginalNode(sourceFile)) { + return idText(node); + } } - else if (isLiteralExpression(node) && (nodeIsSynthesized(node) || !node.parent)) { - return node.text; + else { + Debug.assertNode(node, isLiteralExpression); // not strictly necessary + if (!canUseSourceFile) { + return node.text; + } } - - return getSourceTextOfNodeFromSourceFile(currentSourceFile!, node, includeTrivia); + return getSourceTextOfNodeFromSourceFile(sourceFile, node, includeTrivia); } function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { @@ -4923,7 +4923,7 @@ namespace ts { | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); - return getLiteralText(node, currentSourceFile!, flags); + return getLiteralText(node, currentSourceFile, flags); } /** @@ -5246,7 +5246,7 @@ namespace ts { switch (node.kind) { case SyntaxKind.Identifier: return makeUniqueName( - getTextOfNode(node), + getTextOfNode(node as Identifier), isUniqueName, !!(flags! & GeneratedIdentifierFlags.Optimistic), !!(flags! & GeneratedIdentifierFlags.ReservedInNestedScopes) @@ -5548,7 +5548,7 @@ namespace ts { } function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; if (!hasWrittenComment) { emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); hasWrittenComment = true; @@ -5556,7 +5556,7 @@ namespace ts { // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); emitPos(commentEnd); if (hasTrailingNewLine) { @@ -5580,14 +5580,14 @@ namespace ts { } function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ if (!writer.isAtStartOfLine()) { writer.writeSpace(" "); } emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); emitPos(commentEnd); if (hasTrailingNewLine) { @@ -5605,10 +5605,11 @@ namespace ts { } function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { + if (!currentSourceFile) return; // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); emitPos(commentEnd); if (kind === SyntaxKind.SingleLineCommentTrivia) { @@ -5617,10 +5618,11 @@ namespace ts { } function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if(!currentSourceFile) return; // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space emitPos(commentPos); - writeCommentRange(currentSourceFile!.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); emitPos(commentEnd); if (hasTrailingNewLine) { @@ -5655,6 +5657,7 @@ namespace ts { } function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + if (!currentSourceFile) return; // get the leading comments from detachedPos const pos = last(detachedCommentsInfo!).detachedCommentEndPos; if (detachedCommentsInfo!.length - 1) { @@ -5664,11 +5667,11 @@ namespace ts { detachedCommentsInfo = undefined; } - forEachLeadingCommentRange(currentSourceFile!.text, pos, cb, /*state*/ pos); + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); } function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { - const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile!.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); + const currentDetachedCommentInfo = currentSourceFile && emitDetachedComments(currentSourceFile.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); if (currentDetachedCommentInfo) { if (detachedCommentsInfo) { detachedCommentsInfo.push(currentDetachedCommentInfo); @@ -5680,7 +5683,7 @@ namespace ts { } function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { - if (!shouldWriteComment(currentSourceFile!.text, commentPos)) return; + if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; emitPos(commentPos); writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); emitPos(commentEnd); @@ -5692,7 +5695,7 @@ namespace ts { * @return true if the comment is a triple-slash comment else false */ function isTripleSlashComment(commentPos: number, commentEnd: number) { - return isRecognizedTripleSlashComment(currentSourceFile!.text, commentPos, commentEnd); + return !!currentSourceFile && isRecognizedTripleSlashComment(currentSourceFile.text, commentPos, commentEnd); } // Source Maps diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1093d9ae384..7d860fa7a61 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -656,10 +656,10 @@ namespace ts { AllowNumericSeparator = 1 << 3 } - export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile, flags: GetLiteralTextFlags) { + export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile | undefined, flags: GetLiteralTextFlags) { // If we don't need to downlevel and we can reach the original source text using // the node's parent reference, then simply get the text as it was originally written. - if (canUseOriginalText(node, flags)) { + if (sourceFile && canUseOriginalText(node, flags)) { return getSourceTextOfNodeFromSourceFile(sourceFile, node); }