Skip to content

Commit dc21070

Browse files
authored
Expose bind diags in LSP, show dead/deprecated code (#955)
1 parent 8202302 commit dc21070

File tree

4 files changed

+91
-17
lines changed

4 files changed

+91
-17
lines changed

internal/ast/diagnostic.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type Diagnostic struct {
1919
message string
2020
messageChain []*Diagnostic
2121
relatedInformation []*Diagnostic
22+
reportsUnnecessary bool
23+
reportsDeprecated bool
2224
}
2325

2426
func (d *Diagnostic) File() *SourceFile { return d.file }
@@ -31,6 +33,8 @@ func (d *Diagnostic) Category() diagnostics.Category { return d.category }
3133
func (d *Diagnostic) Message() string { return d.message }
3234
func (d *Diagnostic) MessageChain() []*Diagnostic { return d.messageChain }
3335
func (d *Diagnostic) RelatedInformation() []*Diagnostic { return d.relatedInformation }
36+
func (d *Diagnostic) ReportsUnnecessary() bool { return d.reportsUnnecessary }
37+
func (d *Diagnostic) ReportsDeprecated() bool { return d.reportsDeprecated }
3438

3539
func (d *Diagnostic) SetFile(file *SourceFile) { d.file = file }
3640
func (d *Diagnostic) SetLocation(loc core.TextRange) { d.loc = loc }
@@ -67,11 +71,13 @@ func (d *Diagnostic) Clone() *Diagnostic {
6771

6872
func NewDiagnostic(file *SourceFile, loc core.TextRange, message *diagnostics.Message, args ...any) *Diagnostic {
6973
return &Diagnostic{
70-
file: file,
71-
loc: loc,
72-
code: message.Code(),
73-
category: message.Category(),
74-
message: message.Format(args...),
74+
file: file,
75+
loc: loc,
76+
code: message.Code(),
77+
category: message.Category(),
78+
message: message.Format(args...),
79+
reportsUnnecessary: message.ReportsUnnecessary(),
80+
reportsDeprecated: message.ReportsDeprecated(),
7581
}
7682
}
7783

internal/checker/checker.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13153,21 +13153,29 @@ func (c *Checker) getCannotFindNameDiagnosticForName(node *ast.Node) *diagnostic
1315313153
}
1315413154

1315513155
func (c *Checker) GetDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
13156+
return c.getDiagnostics(ctx, sourceFile, &c.diagnostics)
13157+
}
13158+
13159+
func (c *Checker) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
13160+
return c.getDiagnostics(ctx, sourceFile, &c.suggestionDiagnostics)
13161+
}
13162+
13163+
func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collection *ast.DiagnosticsCollection) []*ast.Diagnostic {
1315613164
c.checkNotCanceled()
1315713165
if sourceFile != nil {
1315813166
c.CheckSourceFile(ctx, sourceFile)
1315913167
if c.wasCanceled {
1316013168
return nil
1316113169
}
13162-
return c.diagnostics.GetDiagnosticsForFile(sourceFile.FileName())
13170+
return collection.GetDiagnosticsForFile(sourceFile.FileName())
1316313171
}
1316413172
for _, file := range c.files {
1316513173
c.CheckSourceFile(ctx, file)
1316613174
if c.wasCanceled {
1316713175
return nil
1316813176
}
1316913177
}
13170-
return c.diagnostics.GetDiagnostics()
13178+
return collection.GetDiagnostics()
1317113179
}
1317213180

1317313181
func (c *Checker) GetDiagnosticsWithoutCheck(sourceFile *ast.SourceFile) []*ast.Diagnostic {

internal/compiler/program.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ func (p *Program) GetSemanticDiagnostics(ctx context.Context, sourceFile *ast.So
348348
return p.getDiagnosticsHelper(ctx, sourceFile, true /*ensureBound*/, true /*ensureChecked*/, p.getSemanticDiagnosticsForFile)
349349
}
350350

351+
func (p *Program) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
352+
return p.getDiagnosticsHelper(ctx, sourceFile, true /*ensureBound*/, true /*ensureChecked*/, p.getSuggestionDiagnosticsForFile)
353+
}
354+
351355
func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic {
352356
var globalDiagnostics []*ast.Diagnostic
353357
checkers, done := p.checkerPool.GetAllCheckers(ctx)
@@ -452,6 +456,39 @@ func (p *Program) getSemanticDiagnosticsForFile(ctx context.Context, sourceFile
452456
return filtered
453457
}
454458

459+
func (p *Program) getSuggestionDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
460+
if checker.SkipTypeChecking(sourceFile, p.compilerOptions) {
461+
return nil
462+
}
463+
464+
var fileChecker *checker.Checker
465+
var done func()
466+
if sourceFile != nil {
467+
fileChecker, done = p.checkerPool.GetCheckerForFile(ctx, sourceFile)
468+
defer done()
469+
}
470+
471+
diags := slices.Clip(sourceFile.BindSuggestionDiagnostics)
472+
473+
checkers, closeCheckers := p.checkerPool.GetAllCheckers(ctx)
474+
defer closeCheckers()
475+
476+
// Ask for diags from all checkers; checking one file may add diagnostics to other files.
477+
// These are deduplicated later.
478+
for _, checker := range checkers {
479+
if sourceFile == nil || checker == fileChecker {
480+
diags = append(diags, checker.GetSuggestionDiagnostics(ctx, sourceFile)...)
481+
} else {
482+
// !!! is there any case where suggestion diagnostics are produced in other checkers?
483+
}
484+
}
485+
if ctx.Err() != nil {
486+
return nil
487+
}
488+
489+
return diags
490+
}
491+
455492
func isCommentOrBlankLine(text string, pos int) bool {
456493
for pos < len(text) && (text[pos] == ' ' || text[pos] == '\t') {
457494
pos++

internal/ls/diagnostics.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ func (l *LanguageService) GetDocumentDiagnostics(ctx context.Context, documentUR
1313
syntaxDiagnostics := program.GetSyntacticDiagnostics(ctx, file)
1414
var lspDiagnostics []*lsproto.Diagnostic
1515
if len(syntaxDiagnostics) != 0 {
16-
lspDiagnostics = make([]*lsproto.Diagnostic, len(syntaxDiagnostics))
17-
for i, diag := range syntaxDiagnostics {
18-
lspDiagnostics[i] = toLSPDiagnostic(diag, l.converters)
16+
lspDiagnostics = make([]*lsproto.Diagnostic, 0, len(syntaxDiagnostics))
17+
for _, diag := range syntaxDiagnostics {
18+
lspDiagnostics = append(lspDiagnostics, toLSPDiagnostic(diag, l.converters))
1919
}
2020
} else {
21-
checker, done := program.GetTypeCheckerForFile(ctx, file)
22-
defer done()
23-
semanticDiagnostics := checker.GetDiagnostics(ctx, file)
24-
lspDiagnostics = make([]*lsproto.Diagnostic, len(semanticDiagnostics))
25-
for i, diag := range semanticDiagnostics {
26-
lspDiagnostics[i] = toLSPDiagnostic(diag, l.converters)
21+
diagnostics := program.GetSemanticDiagnostics(ctx, file)
22+
suggestionDiagnostics := program.GetSuggestionDiagnostics(ctx, file)
23+
24+
lspDiagnostics = make([]*lsproto.Diagnostic, 0, len(diagnostics)+len(suggestionDiagnostics))
25+
for _, diag := range diagnostics {
26+
lspDiagnostics = append(lspDiagnostics, toLSPDiagnostic(diag, l.converters))
27+
}
28+
for _, diag := range suggestionDiagnostics {
29+
// !!! user preference for suggestion diagnostics; keep only unnecessary/dprecated?
30+
lspDiagnostics = append(lspDiagnostics, toLSPDiagnostic(diag, l.converters))
2731
}
2832
}
2933
return &lsproto.DocumentDiagnosticReport{
@@ -60,6 +64,17 @@ func toLSPDiagnostic(diagnostic *ast.Diagnostic, converters *Converters) *lsprot
6064
})
6165
}
6266

67+
var tags []lsproto.DiagnosticTag
68+
if diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated() {
69+
tags = make([]lsproto.DiagnosticTag, 0, 2)
70+
if diagnostic.ReportsUnnecessary() {
71+
tags = append(tags, lsproto.DiagnosticTagUnnecessary)
72+
}
73+
if diagnostic.ReportsDeprecated() {
74+
tags = append(tags, lsproto.DiagnosticTagDeprecated)
75+
}
76+
}
77+
6378
return &lsproto.Diagnostic{
6479
Range: converters.ToLSPRange(diagnostic.File(), diagnostic.Loc()),
6580
Code: &lsproto.IntegerOrString{
@@ -68,6 +83,14 @@ func toLSPDiagnostic(diagnostic *ast.Diagnostic, converters *Converters) *lsprot
6883
Severity: &severity,
6984
Message: diagnostic.Message(),
7085
Source: ptrTo("ts"),
71-
RelatedInformation: &relatedInformation,
86+
RelatedInformation: ptrToSliceIfNonEmpty(relatedInformation),
87+
Tags: ptrToSliceIfNonEmpty(tags),
88+
}
89+
}
90+
91+
func ptrToSliceIfNonEmpty[T any](s []T) *[]T {
92+
if len(s) == 0 {
93+
return nil
7294
}
95+
return &s
7396
}

0 commit comments

Comments
 (0)