Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions tsc/internal/diagnosticwriter/diagnosticwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ func FormatDiagnosticWithColorAndContext(output io.Writer, diagnostic Diagnostic
}

func writeCodeSnippet(writer io.Writer, sourceFile FileLike, start int, length int, squiggleColor string, indent string, formatOpts *FormattingOptions) {
// Inverted diagnostic spans (end < start) must not reach strings.Repeat with a
// negative count. Treat them as empty markers at `start` (see #65078).
if length < 0 {
length = 0
}
firstLine, firstLineChar := scanner.GetECMALineAndUTF16CharacterOfPosition(sourceFile, start)
lastLine, lastLineChar := scanner.GetECMALineAndUTF16CharacterOfPosition(sourceFile, start+length)
if length == 0 {
Expand Down
20 changes: 16 additions & 4 deletions tsc/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6472,7 +6472,19 @@ func (p *Parser) inAwaitContext() bool {
}

func (p *Parser) skipRangeTrivia(textRange core.TextRange) core.TextRange {
return core.NewTextRange(scanner.SkipTrivia(p.sourceText, textRange.Pos()), textRange.End())
// Missing nodes use an empty range (pos == end). Skipping trivia from that
// position can move start past end and invert the span, which then panics
// in --pretty (strings.Repeat with a negative count). Match Strada's
// getErrorSpanForNode: do not skip trivia for missing locations.
// See https://github.com/microsoft/TypeScript/issues/65078
if textRange.Pos() == textRange.End() {
return textRange
}
start := scanner.SkipTrivia(p.sourceText, textRange.Pos())
if start > textRange.End() {
return textRange
}
return core.NewTextRange(start, textRange.End())
}

func isReservedWord(token ast.Kind) bool {
Expand Down Expand Up @@ -6742,7 +6754,7 @@ 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...))
p.jsDiagnostics = append(p.jsDiagnostics, ast.NewDiagnostic(nil, p.skipRangeTrivia(loc), message, args...))
}

func (p *Parser) checkJSDecoratorSyntax(node *ast.Node) {
Expand Down Expand Up @@ -6782,12 +6794,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)
Expand Down
26 changes: 26 additions & 0 deletions tsc/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// An empty (missing) type annotation in a JS file must keep an empty diagnostic
// span at the missing location. Skipping trivia used to invert the 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)
}