diff --git a/.github/copilot-questions.md b/.github/copilot-questions.md index 250d4bf2d776d..d9c9c74a0afa8 100644 --- a/.github/copilot-questions.md +++ b/.github/copilot-questions.md @@ -1,4 +1,5 @@ -Questions I have that I think the developers of this project can help me with: - * How does control flow analysis represent a circular graph? I checked the documentation server for "cfa" and "control flow" - * How do I tell if a symbol is in the global scope? I checked the documentation server for topics referencing "symbol" and "global" - * What is an `EscapedName`, exactly? +Questions I have that I think the developers of this project can help me with: + * How does control flow analysis represent a circular graph? I checked the documentation server for "cfa" and "control flow" + * How do I tell if a symbol is in the global scope? I checked the documentation server for topics referencing "symbol" and "global" + * What is an `EscapedName`, exactly? + * How does TypeScript distinguish between comments inside JSX text vs regular TypeScript comments? I searched for "JSX text comments emitter forEachTrailingCommentRange JSX text ranges" - The issue was that comment iteration doesn't understand JSX context and treats JSX text content as regular comments. diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c60fd92787275..e6c55e11e45d8 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1253,6 +1253,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri var detachedCommentsInfo: { nodePos: number; detachedCommentEndPos: number; }[] | undefined; var hasWrittenComment = false; var commentsDisabled = !!printerOptions.removeComments; + var jsxTextPositionCache: Map | undefined; var lastSubstitution: Node | undefined; var currentParenthesizerRule: ParenthesizerRule | undefined; var { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); @@ -1387,6 +1388,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri currentSourceFile = sourceFile; currentLineMap = undefined; detachedCommentsInfo = undefined; + jsxTextPositionCache = undefined; // Clear cache for new source file + if (sourceFile) { setSourceMapSource(sourceFile); } @@ -1402,6 +1405,40 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri sourceMapsDisabled = !writer || !sourceMapGenerator; } + function isPositionInJsxText(pos: number): boolean { + if (!currentSourceFile) return false; + + // Use cache if available + if (!jsxTextPositionCache) { + jsxTextPositionCache = new Map(); + } + + if (jsxTextPositionCache.has(pos)) { + return jsxTextPositionCache.get(pos)!; + } + + // Walk the AST to find if this position is within a JSX text node + let found = false; + + function visitNode(node: Node): void { + if (found) return; + + if (node.kind === SyntaxKind.JsxText && pos >= node.pos && pos < node.end) { + found = true; + return; + } + + // Only continue traversing if this node might contain the position + if (pos >= node.pos && pos < node.end) { + forEachChild(node, visitNode); + } + } + + visitNode(currentSourceFile); + jsxTextPositionCache.set(pos, found); + return found; + } + function reset() { nodeIdToGeneratedName = []; nodeIdToGeneratedPrivateName = []; @@ -6096,7 +6133,12 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri forEachLeadingCommentWithoutDetachedComments(cb); } else { - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); + forEachLeadingCommentRange(currentSourceFile.text, pos, (commentPos, commentEnd, kind, hasTrailingNewLine, rangePos) => { + // Skip comments that are within JSX text nodes + if (!isPositionInJsxText(commentPos)) { + cb(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + }, /*state*/ pos); } } } @@ -6104,7 +6146,12 @@ 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); + forEachTrailingCommentRange(currentSourceFile.text, end, (commentPos, commentEnd, kind, hasTrailingNewLine) => { + // Skip comments that are within JSX text nodes + if (!isPositionInJsxText(commentPos)) { + cb(commentPos, commentEnd, kind, hasTrailingNewLine); + } + }); } } @@ -6123,7 +6170,12 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri detachedCommentsInfo = undefined; } - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); + forEachLeadingCommentRange(currentSourceFile.text, pos, (commentPos, commentEnd, kind, hasTrailingNewLine, rangePos) => { + // Skip comments that are within JSX text nodes + if (!isPositionInJsxText(commentPos)) { + cb(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + }, /*state*/ pos); } function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js new file mode 100644 index 0000000000000..15426054ad301 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).js @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +//// [jsxCommentDuplication.tsx] +function App() {} +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; + +//// [jsxCommentDuplication.jsx] +function App() { } +var jsx = /* no */{/* 1 */123 /* 2 */}/* no */; diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols new file mode 100644 index 0000000000000..328bd5542845d --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : Symbol(jsx, Decl(jsxCommentDuplication.tsx, 1, 5)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types new file mode 100644 index 0000000000000..5a4fbfe5078c4 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=preserve).types @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : () => void +> : ^^^^^^^^^^ + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : error +>/* no */{/* 1 */ 123 /* 2 */}/* no */ : error +>App : () => void +> : ^^^^^^^^^^ +>123 : 123 +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ + diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react).errors.txt b/tests/baselines/reference/jsxCommentDuplication(jsx=react).errors.txt new file mode 100644 index 0000000000000..5902bb8f12f9e --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react).errors.txt @@ -0,0 +1,8 @@ +jsxCommentDuplication.tsx(2,14): error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. + + +==== jsxCommentDuplication.tsx (1 errors) ==== + function App() {} + const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; + ~~~ +!!! error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. \ No newline at end of file diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react).js b/tests/baselines/reference/jsxCommentDuplication(jsx=react).js new file mode 100644 index 0000000000000..37cba9d576718 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react).js @@ -0,0 +1,12 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +//// [jsxCommentDuplication.tsx] +function App() {} +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; + +//// [jsxCommentDuplication.js] +function App() { } +var jsx = React.createElement(App, null, + "/* no */", /* 1 */ + 123 /* 2 */, + "/* no */"); diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react).symbols b/tests/baselines/reference/jsxCommentDuplication(jsx=react).symbols new file mode 100644 index 0000000000000..328bd5542845d --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react).symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : Symbol(jsx, Decl(jsxCommentDuplication.tsx, 1, 5)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + diff --git a/tests/baselines/reference/jsxCommentDuplication(jsx=react).types b/tests/baselines/reference/jsxCommentDuplication(jsx=react).types new file mode 100644 index 0000000000000..d0333134fdd1c --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react).types @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : () => void +> : ^^^^^^^^^^ + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : any +> : ^^^ +>/* no */{/* 1 */ 123 /* 2 */}/* no */ : any +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ +>123 : 123 +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ + 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..a821a45616d2d --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).errors.txt @@ -0,0 +1,8 @@ +jsxCommentDuplication.tsx(2,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) ==== + function App() {} + const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! 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..b28abc2eb3933 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).js @@ -0,0 +1,12 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +//// [jsxCommentDuplication.tsx] +function App() {} +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; + +//// [jsxCommentDuplication.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var jsx_runtime_1 = require("react/jsx-runtime"); +function App() { } +var jsx = (0, jsx_runtime_1.jsxs)(App, { children: ["/* no */", /* 1 */ 123 /* 2 */, "/* no */"] }); 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..328bd5542845d --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : Symbol(jsx, Decl(jsxCommentDuplication.tsx, 1, 5)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + 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..d0333134fdd1c --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication(jsx=react-jsx).types @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : () => void +> : ^^^^^^^^^^ + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : any +> : ^^^ +>/* no */{/* 1 */ 123 /* 2 */}/* no */ : any +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ +>123 : 123 +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ + diff --git a/tests/baselines/reference/jsxCommentDuplication.js b/tests/baselines/reference/jsxCommentDuplication.js new file mode 100644 index 0000000000000..15426054ad301 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication.js @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +//// [jsxCommentDuplication.tsx] +function App() {} +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; + +//// [jsxCommentDuplication.jsx] +function App() { } +var jsx = /* no */{/* 1 */123 /* 2 */}/* no */; diff --git a/tests/baselines/reference/jsxCommentDuplication.symbols b/tests/baselines/reference/jsxCommentDuplication.symbols new file mode 100644 index 0000000000000..328bd5542845d --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication.symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : Symbol(jsx, Decl(jsxCommentDuplication.tsx, 1, 5)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) +>App : Symbol(App, Decl(jsxCommentDuplication.tsx, 0, 0)) + diff --git a/tests/baselines/reference/jsxCommentDuplication.types b/tests/baselines/reference/jsxCommentDuplication.types new file mode 100644 index 0000000000000..5a4fbfe5078c4 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplication.types @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/jsxCommentDuplication.tsx] //// + +=== jsxCommentDuplication.tsx === +function App() {} +>App : () => void +> : ^^^^^^^^^^ + +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; +>jsx : error +>/* no */{/* 1 */ 123 /* 2 */}/* no */ : error +>App : () => void +> : ^^^^^^^^^^ +>123 : 123 +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ + diff --git a/tests/baselines/reference/jsxCommentDuplicationDebug.js b/tests/baselines/reference/jsxCommentDuplicationDebug.js new file mode 100644 index 0000000000000..cd1898a0501c6 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplicationDebug.js @@ -0,0 +1,9 @@ +//// [tests/cases/compiler/jsxCommentDuplicationDebug.tsx] //// + +//// [jsxCommentDuplicationDebug.tsx] +function App() {} +const jsx = /* before */{/* 1 */ 123 /* 2 */}/* after */; + +//// [jsxCommentDuplicationDebug.jsx] +function App() { } +var jsx = /* before */{123}/* after */; diff --git a/tests/baselines/reference/jsxCommentDuplicationDebug.symbols b/tests/baselines/reference/jsxCommentDuplicationDebug.symbols new file mode 100644 index 0000000000000..a5801dee31379 --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplicationDebug.symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsxCommentDuplicationDebug.tsx] //// + +=== jsxCommentDuplicationDebug.tsx === +function App() {} +>App : Symbol(App, Decl(jsxCommentDuplicationDebug.tsx, 0, 0)) + +const jsx = /* before */{/* 1 */ 123 /* 2 */}/* after */; +>jsx : Symbol(jsx, Decl(jsxCommentDuplicationDebug.tsx, 1, 5)) +>App : Symbol(App, Decl(jsxCommentDuplicationDebug.tsx, 0, 0)) +>App : Symbol(App, Decl(jsxCommentDuplicationDebug.tsx, 0, 0)) + diff --git a/tests/baselines/reference/jsxCommentDuplicationDebug.types b/tests/baselines/reference/jsxCommentDuplicationDebug.types new file mode 100644 index 0000000000000..84f6d3b529b0f --- /dev/null +++ b/tests/baselines/reference/jsxCommentDuplicationDebug.types @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/jsxCommentDuplicationDebug.tsx] //// + +=== jsxCommentDuplicationDebug.tsx === +function App() {} +>App : () => void +> : ^^^^^^^^^^ + +const jsx = /* before */{/* 1 */ 123 /* 2 */}/* after */; +>jsx : error +>/* before */{/* 1 */ 123 /* 2 */}/* after */ : error +>App : () => void +> : ^^^^^^^^^^ +>123 : 123 +> : ^^^ +>App : () => void +> : ^^^^^^^^^^ + diff --git a/tests/baselines/reference/tsxAttributeResolution14.js b/tests/baselines/reference/tsxAttributeResolution14.js index 26633239d3918..1d20896a73dd7 100644 --- a/tests/baselines/reference/tsxAttributeResolution14.js +++ b/tests/baselines/reference/tsxAttributeResolution14.js @@ -35,11 +35,8 @@ function VerticalNavMenuItem(prop) { } function VerticalNav() { return (
- // error - // error - // ok - // ok - // error - // error + // error + // ok + // error
); } diff --git a/tests/cases/compiler/jsxCommentDuplication.tsx b/tests/cases/compiler/jsxCommentDuplication.tsx new file mode 100644 index 0000000000000..a46de1b2ac76b --- /dev/null +++ b/tests/cases/compiler/jsxCommentDuplication.tsx @@ -0,0 +1,3 @@ +// @jsx: preserve,react,react-jsx +function App() {} +const jsx = /* no */{/* 1 */ 123 /* 2 */}/* no */; \ No newline at end of file