Skip to content

Commit 0cb049f

Browse files
Fix JSX comment duplication by excluding JSX text ranges from comment emission
Co-authored-by: RyanCavanaugh <[email protected]>
1 parent 68b4475 commit 0cb049f

File tree

3 files changed

+37
-32
lines changed

3 files changed

+37
-32
lines changed

src/compiler/emitter.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,12 +1249,12 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
12491249
var containerPos = -1;
12501250
var containerEnd = -1;
12511251
var declarationListContainerEnd = -1;
1252-
// Track JSX elements to prevent duplicate comment emission
1253-
var currentJsxElement: JsxElement | undefined;
12541252
var currentLineMap: readonly number[] | undefined;
12551253
var detachedCommentsInfo: { nodePos: number; detachedCommentEndPos: number; }[] | undefined;
12561254
var hasWrittenComment = false;
12571255
var commentsDisabled = !!printerOptions.removeComments;
1256+
// Track JSX text ranges to prevent them from being treated as comments
1257+
var jsxTextRanges: { start: number; end: number; }[] = [];
12581258
var lastSubstitution: Node | undefined;
12591259
var currentParenthesizerRule: ParenthesizerRule<any> | undefined;
12601260
var { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment");
@@ -1389,11 +1389,22 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
13891389
currentSourceFile = sourceFile;
13901390
currentLineMap = undefined;
13911391
detachedCommentsInfo = undefined;
1392+
jsxTextRanges = []; // Clear JSX text ranges for new source file
1393+
1394+
// Pre-collect all JSX text ranges before emission starts
13921395
if (sourceFile) {
1396+
collectJsxTextRanges(sourceFile);
13931397
setSourceMapSource(sourceFile);
13941398
}
13951399
}
13961400

1401+
function collectJsxTextRanges(node: Node) {
1402+
if (node.kind === SyntaxKind.JsxText) {
1403+
jsxTextRanges.push({ start: node.pos, end: node.end });
1404+
}
1405+
forEachChild(node, collectJsxTextRanges);
1406+
}
1407+
13971408
function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) {
13981409
if (_writer && printerOptions.omitTrailingSemicolon) {
13991410
_writer = getTrailingSemicolonDeferringWriter(_writer);
@@ -3860,14 +3871,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
38603871
//
38613872

38623873
function emitJsxElement(node: JsxElement) {
3863-
const savedJsxElement = currentJsxElement;
3864-
currentJsxElement = node;
3865-
38663874
emit(node.openingElement);
38673875
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
38683876
emit(node.closingElement);
3869-
3870-
currentJsxElement = savedJsxElement;
38713877
}
38723878

38733879
function emitJsxSelfClosingElement(node: JsxSelfClosingElement) {
@@ -6103,31 +6109,27 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
61036109
forEachLeadingCommentWithoutDetachedComments(cb);
61046110
}
61056111
else {
6106-
forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos);
6112+
forEachLeadingCommentRange(currentSourceFile.text, pos, (commentPos, commentEnd, kind, hasTrailingNewLine, rangePos) => {
6113+
// Check if this comment position falls within any JSX text range
6114+
const isWithinJsxText = jsxTextRanges.some(range => commentPos >= range.start && commentEnd <= range.end);
6115+
if (!isWithinJsxText) {
6116+
cb(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos);
6117+
}
6118+
}, /*state*/ pos);
61076119
}
61086120
}
61096121
}
61106122

61116123
function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) {
61126124
// Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments
6113-
6114-
// Check if this position is within a JSX element that contains comment-only text children
6115-
// If so, skip emission as the JSX processor will handle these comments
6116-
if (currentJsxElement && end >= currentJsxElement.pos && end <= currentJsxElement.end) {
6117-
// Check if any of the JSX children are comment-only text nodes
6118-
const hasCommentOnlyText = currentJsxElement.children.some(child => {
6119-
if (child.kind === SyntaxKind.JsxText) {
6120-
return child.text.trim().startsWith("/*") && child.text.trim().endsWith("*/");
6125+
if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) {
6126+
forEachTrailingCommentRange(currentSourceFile.text, end, (commentPos, commentEnd, kind, hasTrailingNewLine) => {
6127+
// Check if this comment position falls within any JSX text range
6128+
const isWithinJsxText = jsxTextRanges.some(range => commentPos >= range.start && commentEnd <= range.end);
6129+
if (!isWithinJsxText) {
6130+
cb(commentPos, commentEnd, kind, hasTrailingNewLine);
61216131
}
6122-
return false;
61236132
});
6124-
if (hasCommentOnlyText) {
6125-
return; // Skip comment emission - will be handled by JSX processing
6126-
}
6127-
}
6128-
6129-
if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) {
6130-
forEachTrailingCommentRange(currentSourceFile.text, end, cb);
61316133
}
61326134
}
61336135

@@ -6146,7 +6148,13 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
61466148
detachedCommentsInfo = undefined;
61476149
}
61486150

6149-
forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos);
6151+
forEachLeadingCommentRange(currentSourceFile.text, pos, (commentPos, commentEnd, kind, hasTrailingNewLine, rangePos) => {
6152+
// Check if this comment position falls within any JSX text range
6153+
const isWithinJsxText = jsxTextRanges.some(range => commentPos >= range.start && commentEnd <= range.end);
6154+
if (!isWithinJsxText) {
6155+
cb(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos);
6156+
}
6157+
}, /*state*/ pos);
61506158
}
61516159

61526160
function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) {

tests/baselines/reference/jsxCommentDuplication.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ const jsx = <App>/* no */{/* 1 */ 123 /* 2 */}/* no */</App>;
66

77
//// [jsxCommentDuplication.jsx]
88
function App() { }
9-
var jsx = <App>/* no */{123}/* no */</App>;
9+
var jsx = <App>/* no */{/* 1 */123 /* 2 */}/* no */</App>;

tests/baselines/reference/tsxAttributeResolution14.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,8 @@ function VerticalNavMenuItem(prop) {
3535
}
3636
function VerticalNav() {
3737
return (<div>
38-
<VerticalNavMenuItem primaryText={2}/> // error
39-
// error
40-
<VerticalNavMenuItem justRandomProp={2} primaryText={"hello"}/> // ok
41-
// ok
42-
<VerticalNavMenuItem justRandomProp1={true} primaryText={"hello"}/> // error
43-
// error
38+
<VerticalNavMenuItem primaryText={2}/> // error
39+
<VerticalNavMenuItem justRandomProp={2} primaryText={"hello"}/> // ok
40+
<VerticalNavMenuItem justRandomProp1={true} primaryText={"hello"}/> // error
4441
</div>);
4542
}

0 commit comments

Comments
 (0)