Skip to content

Report JS diagnostics at missing nodes without skipping trivia - #65332

Open
Jemmy (JemmyIndex) wants to merge 1 commit into
microsoft:mainfrom
JemmyIndex:fix/js-error-empty-span-65078
Open

Jemmy (JemmyIndex) wants to merge 1 commit into
microsoft:mainfrom
JemmyIndex:fix/js-error-empty-span-65078

Conversation

@JemmyIndex

@JemmyIndex Jemmy (JemmyIndex) commented Nov 5, 2026 •

Copy link
Copy Markdown

Fixes #65078.

The bug

In a .js file, var R: ; reported TS8010 at column 8 and then crashed:

a.js:1:8 - error TS8010: Type annotations can only be used in TypeScript files.

1 var R: ;
         panic: strings: negative Repeat count

jsErrorAtRange built its span as
NewTextRange(SkipTrivia(sourceText, loc.Pos()), loc.End()). When the type annotation is
missing its location is empty (pos == end == 6), and skipping the whitespace before ;
moved the start to 7, so the span had length -1. --pretty false showed the resulting
wrong column, and the pretty writer passed the negative length to strings.Repeat.

TypeScript 6.0.3 reports TS8010 at column 7 with an empty span, because the classic
compiler creates the diagnostic through createDiagnosticForNodeInSourceFile →
getErrorSpanForNode, which skips trivia only when the node is present:

const isMissing = nodeIsMissing(errorNode);
const pos = isMissing || isJsxText(node) ? errorNode.pos : skipTrivia(sourceFile.text, errorNode.pos);

The fix

jsErrorAtRange was doing the span computation itself instead of using the parser's
existing getErrorSpanForNode, which already implements the missing-node rule. Add
jsErrorAtNode and use it for the 21 diagnostics that are anchored at a node; the three
diagnostics anchored at a node list (type parameters, parameter modifiers, type
arguments) keep the range-based helper, because createDiagnosticForNodeArray also skips
trivia unconditionally for lists.

Nothing changes for present nodes: getErrorSpanForNode still skips trivia for them.

func (p *Parser) jsErrorAtNode(node *ast.Node, message *diagnostics.Message, args ...any) {
	p.jsDiagnostics = append(p.jsDiagnostics, ast.NewDiagnostic(nil, getErrorSpanForNode(p.sourceText, node), message, args...))
}

Verification

  • var R: ; in a .js file now reports TS8010 at 1:7 in both modes and --pretty
    renders the empty underline instead of panicking:
a.js:1:7 - error TS8010: Type annotations can only be used in TypeScript files.

1 var R: ;
        ~

a.js:1:8 - error TS1110: Type expected.
  • Other JS-only diagnostics keep their spans: var a: number → TS8010 at the type,
    class C { x: string }, function f(p: string) {}, interface I {} → TS8006 at the name.
  • go test ./tsc/internal/parser/ passes, including the new
    TestJSDiagnosticsForMissingNodesDoNotSkipTrivia regression test.

AI assistance

This PR was written with AI assistance, and the change is deliberately scoped to how the parser computes the span of JS-only diagnostics that are anchored at a node. I reproduced the crash and verified the fix manually: --pretty no longer panics, TS8010 on var R: ; is now reported at 1:7 with an empty span like TypeScript 6.0.3, and var a: number, class C { x: string }, function f(p: string) {} and interface I {} keep their existing spans. go test ./tsc/internal/parser/ passes locally, including the new regression test. I understand the implementation and am happy to answer questions about it.

`jsErrorAtRange` unconditionally skipped leading trivia when creating the
diagnostic span. For a missing node the location is empty (pos == end), so
skipping the following whitespace moved the start past the end: the TS8010 on
`var R: ;` in a .js file was reported at column 8 instead of 7, and `--pretty`
crashed in strings.Repeat with a negative count because the span length was -1.

Route node-anchored JS diagnostics through getErrorSpanForNode, which only
skips trivia for present nodes. This matches the classic compiler, where
createDiagnosticForNodeInSourceFile goes through getErrorSpanForNode and skips
trivia only when the node is not missing.

The three call sites that pass a node list (type parameters, parameters'
modifiers, type arguments) keep the range helper, mirroring
createDiagnosticForNodeArray, which skips trivia unconditionally.

Fixes microsoft#65078
Copilot AI balanced review requested due to automatic review settings November 5, 2026 09:23
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Nov 5, 2026
@typescript-automation typescript-automation Bot added the For Backlog Bug PRs that fix a backlog bug label Nov 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the node-aware JavaScript diagnostic span changes.

No open issues 🎉

What changed in this PR

Fixes #65078 by using node-aware spans for JavaScript diagnostics, preventing missing nodes from producing inverted ranges and crashing pretty diagnostic output.

Changes:

  • Adds jsErrorAtNode and migrates node-based diagnostics to the existing span helper.
  • Preserves range-based handling for node-list diagnostics.
  • Adds regression tests for missing type annotations and leading trivia before present annotations.
File Description
tsc/​internal/​parser/​parser.go Uses node-aware diagnostic spans to handle missing nodes correctly.
tsc/​internal/​parser/​parser_test.go Verifies diagnostic positions for missing and present type annotations.

Review effort: Balanced

// missing (empty) node do not skip trivia: skipping moved the start past the
// end, producing an inverted span that reported the wrong column and crashed
// the --pretty writer with a negative strings.Repeat count.
func TestJSDiagnosticsForMissingNodesDoNotSkipTrivia(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a unit test and not a compiler test or tsctest?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Backlog Bug PRs that fix a backlog bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

panic: strings: negative Repeat count with --pretty; TS8010 span for an empty type annotation in JS ends before it starts

3 participants