From 127e3100b0852516b7103e50d8ac3f1c61c0a7bd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 21:40:57 +0000
Subject: [PATCH 1/5] Initial plan
From 89d6c676d77d4dd142d27fe5a3dac3a075ca8e90 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 21:53:54 +0000
Subject: [PATCH 2/5] Add test case to reproduce JSX comment duplication issue
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
---
src/compiler/emitter.ts | 8 +++----
.../jsxCommentDuplication(jsx=preserve).js | 11 ++++++++++
...sxCommentDuplication(jsx=preserve).symbols | 11 ++++++++++
.../jsxCommentDuplication(jsx=preserve).types | 20 +++++++++++++++++
...mmentDuplication(jsx=react-jsx).errors.txt | 9 ++++++++
.../jsxCommentDuplication(jsx=react-jsx).js | 14 ++++++++++++
...xCommentDuplication(jsx=react-jsx).symbols | 11 ++++++++++
...jsxCommentDuplication(jsx=react-jsx).types | 22 +++++++++++++++++++
.../cases/compiler/jsxCommentDuplication.tsx | 4 ++++
9 files changed, 106 insertions(+), 4 deletions(-)
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).errors.txt
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).js
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).symbols
create mode 100644 tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).types
create mode 100644 tests/cases/compiler/jsxCommentDuplication.tsx
diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index c60fd92787275..071ecb382aed6 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -4785,10 +4785,10 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}
- // Emit this child.
- if (shouldEmitInterveningComments) {
- const commentRange = getCommentRange(child);
- emitTrailingCommentsOfPosition(commentRange.pos);
+ // Emit this child.
+ if (shouldEmitInterveningComments) {
+ const commentRange = getCommentRange(child);
+ emitTrailingCommentsOfPosition(commentRange.pos);
}
else {
shouldEmitInterveningComments = mayEmitInterveningComments;
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js
new file mode 100644
index 0000000000000..476d4c989b883
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js
@@ -0,0 +1,11 @@
+//// [tests/cases/compiler/jsxCommentDuplication.tsx] ////
+
+//// [jsxCommentDuplication.tsx]
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+const jsx =
/*pre*/{x}/*post*/
;
+
+//// [jsxCommentDuplication.jsx]
+// Simple test case to reproduce JSX comment duplication
+var x = 42;
+var jsx = /*pre*//*pre*/{x} /*post*//*post*/
;
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols
new file mode 100644
index 0000000000000..bb4f837dbfc9e
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols
@@ -0,0 +1,11 @@
+//// [tests/cases/compiler/jsxCommentDuplication.tsx] ////
+
+=== jsxCommentDuplication.tsx ===
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+>x : Symbol(x, Decl(jsxCommentDuplication.tsx, 1, 5))
+
+const jsx = /*pre*/{x}/*post*/
;
+>jsx : Symbol(jsx, Decl(jsxCommentDuplication.tsx, 2, 5))
+>x : Symbol(x, Decl(jsxCommentDuplication.tsx, 1, 5))
+
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types
new file mode 100644
index 0000000000000..2c9cdbd68a0f4
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types
@@ -0,0 +1,20 @@
+//// [tests/cases/compiler/jsxCommentDuplication.tsx] ////
+
+=== jsxCommentDuplication.tsx ===
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+>x : 42
+> : ^^
+>42 : 42
+> : ^^
+
+const jsx = /*pre*/{x}/*post*/
;
+>jsx : error
+>/*pre*/{x}/*post*/
: error
+>div : any
+> : ^^^
+>x : 42
+> : ^^
+>div : any
+> : ^^^
+
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).errors.txt b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).errors.txt
new file mode 100644
index 0000000000000..aefdad9e893ce
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).errors.txt
@@ -0,0 +1,9 @@
+jsxCommentDuplication.tsx(3,13): error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
+
+
+==== jsxCommentDuplication.tsx (1 errors) ====
+ // Simple test case to reproduce JSX comment duplication
+ const x = 42;
+ const jsx = /*pre*/{x}/*post*/
;
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+!!! error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
\ No newline at end of file
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).js b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).js
new file mode 100644
index 0000000000000..c663c49b539ec
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).js
@@ -0,0 +1,14 @@
+//// [tests/cases/compiler/jsxCommentDuplication.tsx] ////
+
+//// [jsxCommentDuplication.tsx]
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+const jsx = /*pre*/{x}/*post*/
;
+
+//// [jsxCommentDuplication.js]
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var jsx_runtime_1 = require("react/jsx-runtime");
+// Simple test case to reproduce JSX comment duplication
+var x = 42;
+var jsx = (0, jsx_runtime_1.jsxs)("div", { children: ["/*pre*/", x, "/*post*/"] });
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).symbols b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).symbols
new file mode 100644
index 0000000000000..bb4f837dbfc9e
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).symbols
@@ -0,0 +1,11 @@
+//// [tests/cases/compiler/jsxCommentDuplication.tsx] ////
+
+=== jsxCommentDuplication.tsx ===
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+>x : Symbol(x, Decl(jsxCommentDuplication.tsx, 1, 5))
+
+const jsx = /*pre*/{x}/*post*/
;
+>jsx : Symbol(jsx, Decl(jsxCommentDuplication.tsx, 2, 5))
+>x : Symbol(x, Decl(jsxCommentDuplication.tsx, 1, 5))
+
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).types b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).types
new file mode 100644
index 0000000000000..0ee7b9eb8dcfa
--- /dev/null
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).types
@@ -0,0 +1,22 @@
+//// [tests/cases/compiler/jsxCommentDuplication.tsx] ////
+
+=== jsxCommentDuplication.tsx ===
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+>x : 42
+> : ^^
+>42 : 42
+> : ^^
+
+const jsx = /*pre*/{x}/*post*/
;
+>jsx : any
+> : ^^^
+>/*pre*/{x}/*post*/
: any
+> : ^^^
+>div : any
+> : ^^^
+>x : 42
+> : ^^
+>div : any
+> : ^^^
+
diff --git a/tests/cases/compiler/jsxCommentDuplication.tsx b/tests/cases/compiler/jsxCommentDuplication.tsx
new file mode 100644
index 0000000000000..4ff65ddd631d6
--- /dev/null
+++ b/tests/cases/compiler/jsxCommentDuplication.tsx
@@ -0,0 +1,4 @@
+// @jsx: preserve,react-jsx
+// Simple test case to reproduce JSX comment duplication
+const x = 42;
+const jsx = /*pre*/{x}/*post*/
;
\ No newline at end of file
From 854b89ecde88515572e3f407b40505e76bc7a6c1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 22:04:25 +0000
Subject: [PATCH 3/5] Investigate JSX comment duplication - identify issue is
not in main list emission logic
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
---
src/compiler/emitter.ts | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index 071ecb382aed6..e195634d75ecd 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -3192,9 +3192,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
pos = writeTokenText(token, writer, pos);
}
- if (isSimilarNode && contextNode.end !== pos) {
- const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression;
- emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext);
+ if (isSimilarNode && contextNode.end !== pos) {
+ const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression;
+ emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext);
}
return pos;
}
@@ -4713,9 +4713,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
*
* NOTE: You probably don't want to call this directly and should be using `emitList` or `emitExpressionList` instead.
*/
- function emitNodeListItems(emit: EmitFunction, parentNode: Node | undefined, children: readonly Child[], format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start: number, count: number, hasTrailingComma: boolean, childrenTextRange: TextRange | undefined) {
- // Write the opening line terminator or leading whitespace.
- const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0;
+ function emitNodeListItems(emit: EmitFunction, parentNode: Node | undefined, children: readonly Child[], format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start: number, count: number, hasTrailingComma: boolean, childrenTextRange: TextRange | undefined) {
+ // Write the opening line terminator or leading whitespace.
+ const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0;
let shouldEmitInterveningComments = mayEmitInterveningComments;
const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children[start], format);
@@ -4772,9 +4772,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
shouldDecreaseIndentAfterEmit = true;
}
- if (shouldEmitInterveningComments && format & ListFormat.DelimitersMask && !positionIsSynthesized(child.pos)) {
- const commentRange = getCommentRange(child);
- emitTrailingCommentsOfPosition(commentRange.pos, /*prefixSpace*/ !!(format & ListFormat.SpaceBetweenSiblings), /*forceNoNewline*/ true);
+ if (shouldEmitInterveningComments && format & ListFormat.DelimitersMask && !positionIsSynthesized(child.pos)) {
+ const commentRange = getCommentRange(child);
+ emitTrailingCommentsOfPosition(commentRange.pos, /*prefixSpace*/ !!(format & ListFormat.SpaceBetweenSiblings), /*forceNoNewline*/ true);
}
writeLine(separatingLineTerminatorCount);
@@ -6051,13 +6051,13 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}
- function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) {
- if (commentsDisabled) {
- return;
- }
- enterComment();
- forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition);
- exitComment();
+ 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) {
From f6ff1596d64b01fb0aeb0325ca359f63b41c2345 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 22:11:56 +0000
Subject: [PATCH 4/5] Fix JSX comment duplication by filtering comments within
JSX text
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
---
debug-ast.js | 28 ++++++++
src/compiler/emitter.ts | 67 +++++++++++++------
.../jsxCommentDuplication(jsx=preserve).js | 2 +-
3 files changed, 77 insertions(+), 20 deletions(-)
create mode 100644 debug-ast.js
diff --git a/debug-ast.js b/debug-ast.js
new file mode 100644
index 0000000000000..3dd31b071514a
--- /dev/null
+++ b/debug-ast.js
@@ -0,0 +1,28 @@
+const ts = require("./built/local/typescript.js");
+
+const sourceCode = `const jsx = /*a*/{x}/*b*/
;`;
+const sourceFile = ts.createSourceFile("test.tsx", sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
+
+function printNode(node, depth = 0) {
+ const indent = " ".repeat(depth);
+ const kindName = ts.SyntaxKind[node.kind];
+ const posInfo = `(${node.pos}-${node.end})`;
+ let extraInfo = "";
+
+ if (node.text !== undefined) {
+ extraInfo += ` text:"${node.text}"`;
+ }
+
+ console.log(`${indent}${kindName} ${posInfo}${extraInfo}`);
+
+ // Print the actual source text for this node range
+ if (node.pos >= 0 && node.end > node.pos) {
+ const nodeText = sourceCode.substring(node.pos, node.end);
+ console.log(`${indent} source: "${nodeText}"`);
+ }
+
+ ts.forEachChild(node, child => printNode(child, depth + 1));
+}
+
+console.log("AST for:", sourceCode);
+printNode(sourceFile);
\ No newline at end of file
diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index e195634d75ecd..210a6d47728b2 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -3939,20 +3939,20 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
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();
- }
- }
+ 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();
+ }
+ }
}
function emitJsxNamespacedName(node: JsxNamespacedName) {
@@ -6101,11 +6101,40 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}
- 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 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))) {
+ // Create a wrapper callback that filters out comments that are part of JSX text content
+ const filteredCb = (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => {
+ // Check if this comment is actually JSX text content
+ if (isCommentWithinJsxText(commentPos, commentEnd)) {
+ return; // Skip this comment emission
+ }
+ cb(commentPos, commentEnd, kind, hasTrailingNewLine);
+ };
+
+ forEachTrailingCommentRange(currentSourceFile.text, end, filteredCb);
+ }
+ }
+
+ function isCommentWithinJsxText(commentPos: number, commentEnd: number): boolean {
+ if (!currentSourceFile) return false;
+
+ // Find JSX text nodes that might contain this comment
+ function checkNode(node: Node): boolean {
+ if (node.kind === SyntaxKind.JsxText) {
+ const jsxText = node as JsxText;
+ // Check if the comment range is within the JSX text node range
+ if (commentPos >= jsxText.pos && commentEnd <= jsxText.end) {
+ return true;
+ }
+ }
+
+ // Recursively check children
+ return !!forEachChild(node, checkNode);
+ }
+
+ return checkNode(currentSourceFile);
}
function hasDetachedComments(pos: number) {
diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js
index 476d4c989b883..e6e31288aa05d 100644
--- a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js
+++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js
@@ -8,4 +8,4 @@ const jsx = /*pre*/{x}/*post*/
;
//// [jsxCommentDuplication.jsx]
// Simple test case to reproduce JSX comment duplication
var x = 42;
-var jsx = /*pre*//*pre*/{x} /*post*//*post*/
;
+var jsx = /*pre*/{x}/*post*/
;
From 98d98b9935eed840eb34fa888687a7bd7174c434 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 9 Jul 2025 22:18:31 +0000
Subject: [PATCH 5/5] Clean up debug files and format code
---
debug-ast.js | 28 ---------
src/compiler/emitter.ts | 136 ++++++++++++++++++++--------------------
2 files changed, 68 insertions(+), 96 deletions(-)
delete mode 100644 debug-ast.js
diff --git a/debug-ast.js b/debug-ast.js
deleted file mode 100644
index 3dd31b071514a..0000000000000
--- a/debug-ast.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const ts = require("./built/local/typescript.js");
-
-const sourceCode = `const jsx = /*a*/{x}/*b*/
;`;
-const sourceFile = ts.createSourceFile("test.tsx", sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
-
-function printNode(node, depth = 0) {
- const indent = " ".repeat(depth);
- const kindName = ts.SyntaxKind[node.kind];
- const posInfo = `(${node.pos}-${node.end})`;
- let extraInfo = "";
-
- if (node.text !== undefined) {
- extraInfo += ` text:"${node.text}"`;
- }
-
- console.log(`${indent}${kindName} ${posInfo}${extraInfo}`);
-
- // Print the actual source text for this node range
- if (node.pos >= 0 && node.end > node.pos) {
- const nodeText = sourceCode.substring(node.pos, node.end);
- console.log(`${indent} source: "${nodeText}"`);
- }
-
- ts.forEachChild(node, child => printNode(child, depth + 1));
-}
-
-console.log("AST for:", sourceCode);
-printNode(sourceFile);
\ No newline at end of file
diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index 210a6d47728b2..c907b2453090d 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -3192,9 +3192,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
pos = writeTokenText(token, writer, pos);
}
- if (isSimilarNode && contextNode.end !== pos) {
- const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression;
- emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext);
+ if (isSimilarNode && contextNode.end !== pos) {
+ const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression;
+ emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext);
}
return pos;
}
@@ -3939,20 +3939,20 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
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();
- }
- }
+ 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();
+ }
+ }
}
function emitJsxNamespacedName(node: JsxNamespacedName) {
@@ -4713,9 +4713,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
*
* NOTE: You probably don't want to call this directly and should be using `emitList` or `emitExpressionList` instead.
*/
- function emitNodeListItems(emit: EmitFunction, parentNode: Node | undefined, children: readonly Child[], format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start: number, count: number, hasTrailingComma: boolean, childrenTextRange: TextRange | undefined) {
- // Write the opening line terminator or leading whitespace.
- const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0;
+ function emitNodeListItems(emit: EmitFunction, parentNode: Node | undefined, children: readonly Child[], format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start: number, count: number, hasTrailingComma: boolean, childrenTextRange: TextRange | undefined) {
+ // Write the opening line terminator or leading whitespace.
+ const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0;
let shouldEmitInterveningComments = mayEmitInterveningComments;
const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children[start], format);
@@ -4772,9 +4772,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
shouldDecreaseIndentAfterEmit = true;
}
- if (shouldEmitInterveningComments && format & ListFormat.DelimitersMask && !positionIsSynthesized(child.pos)) {
- const commentRange = getCommentRange(child);
- emitTrailingCommentsOfPosition(commentRange.pos, /*prefixSpace*/ !!(format & ListFormat.SpaceBetweenSiblings), /*forceNoNewline*/ true);
+ if (shouldEmitInterveningComments && format & ListFormat.DelimitersMask && !positionIsSynthesized(child.pos)) {
+ const commentRange = getCommentRange(child);
+ emitTrailingCommentsOfPosition(commentRange.pos, /*prefixSpace*/ !!(format & ListFormat.SpaceBetweenSiblings), /*forceNoNewline*/ true);
}
writeLine(separatingLineTerminatorCount);
@@ -4785,10 +4785,10 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}
- // Emit this child.
- if (shouldEmitInterveningComments) {
- const commentRange = getCommentRange(child);
- emitTrailingCommentsOfPosition(commentRange.pos);
+ // Emit this child.
+ if (shouldEmitInterveningComments) {
+ const commentRange = getCommentRange(child);
+ emitTrailingCommentsOfPosition(commentRange.pos);
}
else {
shouldEmitInterveningComments = mayEmitInterveningComments;
@@ -6051,13 +6051,13 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}
- function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) {
- if (commentsDisabled) {
- return;
- }
- enterComment();
- forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition);
- exitComment();
+ 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) {
@@ -6101,40 +6101,40 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}
- 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))) {
- // Create a wrapper callback that filters out comments that are part of JSX text content
- const filteredCb = (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => {
- // Check if this comment is actually JSX text content
- if (isCommentWithinJsxText(commentPos, commentEnd)) {
- return; // Skip this comment emission
- }
- cb(commentPos, commentEnd, kind, hasTrailingNewLine);
- };
-
- forEachTrailingCommentRange(currentSourceFile.text, end, filteredCb);
- }
- }
-
- function isCommentWithinJsxText(commentPos: number, commentEnd: number): boolean {
- if (!currentSourceFile) return false;
-
- // Find JSX text nodes that might contain this comment
- function checkNode(node: Node): boolean {
- if (node.kind === SyntaxKind.JsxText) {
- const jsxText = node as JsxText;
- // Check if the comment range is within the JSX text node range
- if (commentPos >= jsxText.pos && commentEnd <= jsxText.end) {
- return true;
- }
- }
-
- // Recursively check children
- return !!forEachChild(node, checkNode);
- }
-
- return checkNode(currentSourceFile);
+ 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))) {
+ // Create a wrapper callback that filters out comments that are part of JSX text content
+ const filteredCb = (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => {
+ // Check if this comment is actually JSX text content
+ if (isCommentWithinJsxText(commentPos, commentEnd)) {
+ return; // Skip this comment emission
+ }
+ cb(commentPos, commentEnd, kind, hasTrailingNewLine);
+ };
+
+ forEachTrailingCommentRange(currentSourceFile.text, end, filteredCb);
+ }
+ }
+
+ function isCommentWithinJsxText(commentPos: number, commentEnd: number): boolean {
+ if (!currentSourceFile) return false;
+
+ // Find JSX text nodes that might contain this comment
+ function checkNode(node: Node): boolean {
+ if (node.kind === SyntaxKind.JsxText) {
+ const jsxText = node as JsxText;
+ // Check if the comment range is within the JSX text node range
+ if (commentPos >= jsxText.pos && commentEnd <= jsxText.end) {
+ return true;
+ }
+ }
+
+ // Recursively check children
+ return !!forEachChild(node, checkNode);
+ }
+
+ return checkNode(currentSourceFile);
}
function hasDetachedComments(pos: number) {