diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index c670d64ad21c2..5b4b738db33be 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -6742,7 +6742,14 @@ func (p *Parser) parseResolutionMode(mode string, pos int, end int) (resolutionK } func (p *Parser) jsErrorAtRange(loc core.TextRange, message *diagnostics.Message, args ...any) { - p.jsDiagnostics = append(p.jsDiagnostics, ast.NewDiagnostic(nil, core.NewTextRange(scanner.SkipTrivia(p.sourceText, loc.Pos()), loc.End()), message, args...)) + // Match Strada getErrorSpanForNode: do not skip trivia for missing (empty) + // locations. Skipping the following whitespace would move start past end + // and invert the diagnostic span (see #65078). + rangeLoc := loc + if loc.Pos() != loc.End() { + rangeLoc = p.skipRangeTrivia(loc) + } + p.jsDiagnostics = append(p.jsDiagnostics, ast.NewDiagnostic(nil, rangeLoc, message, args...)) } func (p *Parser) checkJSDecoratorSyntax(node *ast.Node) { @@ -6782,12 +6789,12 @@ func (p *Parser) checkJSDecoratorSyntax(node *ast.Node) { if trailingDecoratorIndex >= 0 { diag := ast.NewDiagnostic( nil, - core.NewTextRange(scanner.SkipTrivia(p.sourceText, modifiers[trailingDecoratorIndex].Loc.Pos()), modifiers[trailingDecoratorIndex].Loc.End()), + p.skipRangeTrivia(modifiers[trailingDecoratorIndex].Loc), diagnostics.Decorators_may_not_appear_after_export_or_export_default_if_they_also_appear_before_export, ) diag.AddRelatedInfo(ast.NewDiagnostic( nil, - core.NewTextRange(scanner.SkipTrivia(p.sourceText, modifiers[decoratorIndex].Loc.Pos()), modifiers[decoratorIndex].Loc.End()), + p.skipRangeTrivia(modifiers[decoratorIndex].Loc), diagnostics.Decorator_used_before_export_here, )) p.jsDiagnostics = append(p.jsDiagnostics, diag) diff --git a/tsc/internal/parser/parser_test.go b/tsc/internal/parser/parser_test.go index ffa96713eb444..492cd5892f562 100644 --- a/tsc/internal/parser/parser_test.go +++ b/tsc/internal/parser/parser_test.go @@ -571,3 +571,29 @@ namespace N { assert.Equal(t, positionMap.UTF8ToUTF16(afterBoxDrawingCharacter), afterBoxDrawingCharacter-2) assert.Equal(t, positionMap.UTF8ToUTF16(len(sourceText)), len(sourceText)-2) } + +// https://github.com/microsoft/TypeScript/issues/65078 +// jsErrorAtRange must not skip trivia for a missing (empty) type annotation; +// doing so inverted the TS8010 span (start past end), which crashed --pretty +// and reported the wrong column. +func TestJSErrorAtMissingTypeAnnotationKeepsEmptySpan(t *testing.T) { + t.Parallel() + sourceText := "var R: ;" + opts := ast.SourceFileParseOptions{ + FileName: "/a.js", + PathKey: "/a.js", + } + file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS) + + var found *ast.Diagnostic + for _, d := range file.JSDiagnostics() { + if d.Code() == diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files.Code() { + found = d + break + } + } + assert.Assert(t, found != nil, "expected TS8010") + // "var R:" ends at index 6; the missing type occupies [6,6]. + assert.Equal(t, found.Pos(), 6) + assert.Equal(t, found.Len(), 0) +}