diff --git a/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go index 0c3f87b0f4..3cbe0a83c5 100644 --- a/internal/lsp/cache/analysis.go +++ b/internal/lsp/cache/analysis.go @@ -18,6 +18,7 @@ import ( "golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/lsp/debug/tag" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/memoize" errors "golang.org/x/xerrors" @@ -347,7 +348,7 @@ func runAnalysis(ctx context.Context, snapshot *snapshot, analyzer *analysis.Ana } for _, diag := range diagnostics { - srcErr, err := sourceDiagnostic(ctx, snapshot, pkg, diag) + srcDiags, err := sourceDiagnostics(ctx, snapshot, pkg, protocol.SeverityWarning, diag) if err != nil { event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(pkg.ID())) continue @@ -356,7 +357,7 @@ func runAnalysis(ctx context.Context, snapshot *snapshot, analyzer *analysis.Ana data.err = ctx.Err() return data } - data.diagnostics = append(data.diagnostics, srcErr) + data.diagnostics = append(data.diagnostics, srcDiags...) } return data } diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go index 08428252d0..24b2dc4fb3 100644 --- a/internal/lsp/cache/check.go +++ b/internal/lsp/cache/check.go @@ -20,6 +20,7 @@ import ( "golang.org/x/tools/go/packages" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/lsp/debug/tag" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/memoize" "golang.org/x/tools/internal/span" @@ -377,12 +378,12 @@ func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source // Try to attach errors messages to the file as much as possible. var found bool for _, e := range rawErrors { - srcErr, err := sourceDiagnostic(ctx, snapshot, pkg, e) + srcDiags, err := sourceDiagnostics(ctx, snapshot, pkg, protocol.SeverityError, e) if err != nil { continue } found = true - pkg.diagnostics = append(pkg.diagnostics, srcErr) + pkg.diagnostics = append(pkg.diagnostics, srcDiags...) } if found { return pkg, nil @@ -439,12 +440,13 @@ func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source if mode == source.ParseFull { expandErrors(rawErrors) for _, e := range rawErrors { - srcErr, err := sourceDiagnostic(ctx, snapshot, pkg, e) + srcDiags, err := sourceDiagnostics(ctx, snapshot, pkg, protocol.SeverityError, e) if err != nil { event.Error(ctx, "unable to compute error positions", err, tag.Package.Of(pkg.ID())) continue } - pkg.diagnostics = append(pkg.diagnostics, srcErr) + pkg.diagnostics = append(pkg.diagnostics, srcDiags...) + if err, ok := e.(extendedError); ok { pkg.typeErrors = append(pkg.typeErrors, err.primary) } diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go index c14d4c2a28..529a420bfc 100644 --- a/internal/lsp/cache/errors.go +++ b/internal/lsp/cache/errors.go @@ -26,23 +26,23 @@ import ( errors "golang.org/x/xerrors" ) -func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Diagnostic, error) { +func sourceDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, severity protocol.DiagnosticSeverity, e interface{}) ([]*source.Diagnostic, error) { fset := snapshot.view.session.cache.fset var ( - spn span.Span - err error - msg, category string - code typesinternal.ErrorCode - kind source.ErrorKind - fixes []source.SuggestedFix - related []source.RelatedInformation + spn span.Span + err error + msg string + code typesinternal.ErrorCode + diagSrc source.DiagnosticSource + fixes []source.SuggestedFix + related []source.RelatedInformation ) switch e := e.(type) { case packages.Error: - kind = toSourceErrorKind(e.Kind) + diagSrc = toDiagnosticSource(e.Kind) var ok bool if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok { - kind = source.TypeError + diagSrc = source.TypeError break } if e.Pos == "" { @@ -50,18 +50,23 @@ func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e inter // We may not have been able to parse a valid span. if _, err := spanToRange(snapshot, pkg, spn); err != nil { - return &source.Diagnostic{ - URI: spn.URI(), - Message: msg, - Kind: kind, - }, nil + var diags []*source.Diagnostic + for _, cgf := range pkg.compiledGoFiles { + diags = append(diags, &source.Diagnostic{ + URI: cgf.URI, + Severity: severity, + Source: diagSrc, + Message: msg, + }) + } + return diags, nil } } else { spn = span.Parse(e.Pos) } case *scanner.Error: msg = e.Msg - kind = source.ParseError + diagSrc = source.ParseError spn, err = scannerErrorRange(snapshot, pkg, e.Pos) if err != nil { if ctx.Err() != nil { @@ -77,7 +82,7 @@ func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e inter return nil, errors.Errorf("no errors in %v", e) } msg = e[0].Msg - kind = source.ParseError + diagSrc = source.ParseError spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos) if err != nil { if ctx.Err() != nil { @@ -88,7 +93,7 @@ func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e inter } case types.Error: msg = e.Msg - kind = source.TypeError + diagSrc = source.TypeError if !e.Pos.IsValid() { return nil, fmt.Errorf("invalid position for type error %v", e) } @@ -99,7 +104,7 @@ func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e inter case extendedError: perr := e.primary msg = perr.Msg - kind = source.TypeError + diagSrc = source.TypeError if !perr.Pos.IsValid() { return nil, fmt.Errorf("invalid position for type error %v", e) } @@ -128,8 +133,7 @@ func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e inter return nil, err } msg = e.Message - kind = source.Analysis - category = e.Category + diagSrc = source.AnalyzerErrorKind(e.Category) fixes, err = suggestedAnalysisFixes(snapshot, pkg, e) if err != nil { return nil, err @@ -148,17 +152,17 @@ func sourceDiagnostic(ctx context.Context, snapshot *snapshot, pkg *pkg, e inter sd := &source.Diagnostic{ URI: spn.URI(), Range: rng, + Severity: severity, + Source: diagSrc, Message: msg, - Kind: kind, - Category: category, - SuggestedFixes: fixes, Related: related, + SuggestedFixes: fixes, } if code != 0 { sd.Code = code.String() sd.CodeHref = typesCodeHref(snapshot, code) } - return sd, nil + return []*source.Diagnostic{sd}, nil } func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string { @@ -212,7 +216,7 @@ func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) return out, nil } -func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind { +func toDiagnosticSource(kind packages.ErrorKind) source.DiagnosticSource { switch kind { case packages.ListError: return source.ListError diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go index 07016141f6..31a44d221b 100644 --- a/internal/lsp/cache/load.go +++ b/internal/lsp/cache/load.go @@ -273,10 +273,11 @@ func (s *snapshot) applyCriticalErrorToFiles(ctx context.Context, msg string, fi } } srcDiags = append(srcDiags, &source.Diagnostic{ - URI: fh.URI(), - Range: rng, - Kind: source.ListError, - Message: msg, + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: source.ListError, + Message: msg, }) } return srcDiags diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go index 27d44967c8..5b58887a6d 100644 --- a/internal/lsp/cache/mod.go +++ b/internal/lsp/cache/mod.go @@ -22,11 +22,6 @@ import ( "golang.org/x/tools/internal/span" ) -const ( - SyntaxError = "syntax" - GoCommandError = "go command" -) - type parseModHandle struct { handle *memoize.Handle } @@ -315,10 +310,11 @@ outer: msg = fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version) } return &source.Diagnostic{ - Message: msg, - Kind: source.ListError, - Range: rng, - URI: fh.URI(), + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Message: msg, + Source: source.ListError, SuggestedFixes: []source.SuggestedFix{{ Title: fmt.Sprintf("Download %v@%v", innermost.Path, innermost.Version), Command: &protocol.Command{ @@ -329,11 +325,16 @@ outer: }}, } } + diagSource := source.ListError + if fh != nil { + diagSource = source.ParseError + } return &source.Diagnostic{ - Message: goCmdError, - Range: rng, - URI: fh.URI(), - Kind: source.ListError, + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: diagSource, + Message: goCmdError, } } @@ -377,14 +378,15 @@ func extractErrorWithPosition(ctx context.Context, goCmdError string, src source if err != nil { return nil } - category := GoCommandError + diagSource := source.ListError if fh != nil { - category = SyntaxError + diagSource = source.ParseError } return &source.Diagnostic{ - Category: category, - Message: msg, - Range: rng, URI: spn.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: diagSource, + Message: msg, } } diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go index 85aa7cc984..c98d4fd086 100644 --- a/internal/lsp/cache/mod_tidy.go +++ b/internal/lsp/cache/mod_tidy.go @@ -180,9 +180,10 @@ func (s *snapshot) parseModError(ctx context.Context, fh source.FileHandle, errT switch { case isInconsistentVendor: return &source.Diagnostic{ - URI: fh.URI(), - Range: rng, - Kind: source.ListError, + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: source.ListError, Message: `Inconsistent vendoring detected. Please re-run "go mod vendor". See https://github.com/golang/go/issues/39164 for more detail on this issue.`, SuggestedFixes: []source.SuggestedFix{{ @@ -197,10 +198,11 @@ See https://github.com/golang/go/issues/39164 for more detail on this issue.`, case isGoSumUpdates: return &source.Diagnostic{ - URI: fh.URI(), - Range: rng, - Kind: source.ListError, - Message: `go.sum is out of sync with go.mod. Please update it or run "go mod tidy".`, + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: source.ListError, + Message: `go.sum is out of sync with go.mod. Please update it or run "go mod tidy".`, SuggestedFixes: []source.SuggestedFix{ { Title: source.CommandTidy.Title, @@ -388,10 +390,11 @@ func unusedDiagnostic(m *protocol.ColumnMapper, req *modfile.Require, onlyDiagno return nil, err } return &source.Diagnostic{ - Category: source.GoModTidy, - Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path), - Range: rng, URI: m.URI, + Range: rng, + Severity: protocol.SeverityWarning, + Source: source.ModTidyError, + Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path), SuggestedFixes: []source.SuggestedFix{{ Title: fmt.Sprintf("Remove dependency: %s", req.Mod.Path), Command: &protocol.Command{ @@ -431,10 +434,11 @@ func directnessDiagnostic(m *protocol.ColumnMapper, req *modfile.Require, comput return nil, err } return &source.Diagnostic{ - Message: fmt.Sprintf("%s should be %s", req.Mod.Path, direction), - Range: rng, URI: m.URI, - Category: source.GoModTidy, + Range: rng, + Severity: protocol.SeverityWarning, + Source: source.ModTidyError, + Message: fmt.Sprintf("%s should be %s", req.Mod.Path, direction), SuggestedFixes: []source.SuggestedFix{{ Title: fmt.Sprintf("Change %s to %s", req.Mod.Path, direction), Edits: map[span.URI][]protocol.TextEdit{ @@ -462,9 +466,9 @@ func missingModuleDiagnostic(snapshot source.Snapshot, pm *source.ParsedModule, return &source.Diagnostic{ URI: pm.Mapper.URI, Range: rng, + Severity: protocol.SeverityError, + Source: source.ModTidyError, Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path), - Category: source.GoModTidy, - Kind: source.ModTidyError, SuggestedFixes: []source.SuggestedFix{{ Title: fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path), Command: &protocol.Command{ @@ -527,11 +531,11 @@ func missingModuleForImport(snapshot source.Snapshot, m *protocol.ColumnMapper, return nil, err } return &source.Diagnostic{ - Category: source.GoModTidy, URI: m.URI, Range: rng, + Severity: protocol.SeverityError, + Source: source.ModTidyError, Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path), - Kind: source.ModTidyError, SuggestedFixes: fixes, }, nil } diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 47d5758752..f8baf45672 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -27,6 +27,7 @@ import ( "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/gocommand" "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/memoize" "golang.org/x/tools/internal/span" @@ -543,8 +544,8 @@ func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) { addError := func(uri span.URI, err error) { modDiagnostics = append(modDiagnostics, &source.Diagnostic{ URI: uri, - Category: "compiler", - Kind: source.ListError, + Severity: protocol.SeverityError, + Source: source.ListError, Message: err.Error(), }) } diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index a2cdbfe2e5..cd78c5abf3 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -316,7 +316,7 @@ func findDiagnostic(ctx context.Context, snapshot source.Snapshot, pkgID string, if protocol.CompareRange(err.Range, diag.Range) != 0 { continue } - if err.Category != analyzer.Analyzer.Name { + if string(err.Source) != analyzer.Analyzer.Name { continue } // The error matches. @@ -431,9 +431,9 @@ func convenienceFixes(ctx context.Context, snapshot source.Snapshot, pkg source. func diagnosticToCommandCodeAction(ctx context.Context, snapshot source.Snapshot, sd *source.Diagnostic, pd *protocol.Diagnostic, kind protocol.CodeActionKind) (*protocol.CodeAction, error) { // The fix depends on the category of the analyzer. The diagnostic may be // nil, so use the error's category. - analyzer := diagnosticToAnalyzer(snapshot, sd.Category, sd.Message) + analyzer := diagnosticToAnalyzer(snapshot, string(sd.Source), sd.Message) if analyzer == nil { - return nil, fmt.Errorf("no convenience analyzer for category %s", sd.Category) + return nil, fmt.Errorf("no convenience analyzer for source %s", sd.Source) } if analyzer.Command == nil { return nil, fmt.Errorf("no command for convenience analyzer %s", analyzer.Analyzer.Name) @@ -565,7 +565,7 @@ func moduleQuickFixes(ctx context.Context, snapshot source.Snapshot, fh source.V } func sameDiagnostic(pd protocol.Diagnostic, sd *source.Diagnostic) bool { - return pd.Message == sd.Message && protocol.CompareRange(pd.Range, sd.Range) == 0 && pd.Source == sd.Category + return pd.Message == sd.Message && protocol.CompareRange(pd.Range, sd.Range) == 0 && pd.Source == string(sd.Source) } func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) { diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 12a3378877..20af61fd0f 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -351,7 +351,9 @@ func (s *Server) showCriticalErrorStatus(ctx context.Context, snapshot source.Sn var errMsg string if err != nil { event.Error(ctx, "errors loading workspace", err.MainError, tag.Snapshot.Of(snapshot.ID()), tag.Directory.Of(snapshot.View().Folder())) - s.storeErrorDiagnostics(ctx, snapshot, modSource, err.DiagList) + for _, d := range err.DiagList { + s.storeDiagnostics(snapshot, d.URI, modSource, []*source.Diagnostic{d}) + } errMsg = strings.Replace(err.MainError.Error(), "\n", " ", -1) } @@ -399,28 +401,14 @@ func (s *Server) checkForOrphanedFile(ctx context.Context, snapshot source.Snaps // file and show a more specific error message. For now, put the diagnostic // on the package declaration. return &source.Diagnostic{ - Range: rng, + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityWarning, + Source: source.ListError, Message: fmt.Sprintf(`No packages found for open file %s: %v. If this file contains build tags, try adding "-tags=" to your gopls "buildFlag" configuration (see (https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags-string). Otherwise, see the troubleshooting guidelines for help investigating (https://github.com/golang/tools/blob/master/gopls/doc/troubleshooting.md). `, fh.URI().Filename(), err), - Severity: protocol.SeverityWarning, - Source: "compiler", - } -} - -func (s *Server) storeErrorDiagnostics(ctx context.Context, snapshot source.Snapshot, dsource diagnosticSource, diagnostics []*source.Diagnostic) { - for _, d := range diagnostics { - diagnostic := &source.Diagnostic{ - Range: d.Range, - Message: d.Message, - Related: d.Related, - Severity: protocol.SeverityError, - Source: d.Category, - Code: d.Code, - CodeHref: d.CodeHref, - } - s.storeDiagnostics(snapshot, d.URI, dsource, []*source.Diagnostic{diagnostic}) } } @@ -523,7 +511,7 @@ func toProtocolDiagnostics(diagnostics []*source.Diagnostic) []protocol.Diagnost Message: strings.TrimSpace(diag.Message), Range: diag.Range, Severity: diag.Severity, - Source: diag.Source, + Source: string(diag.Source), Tags: diag.Tags, RelatedInformation: related, } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index fd8b4ada64..f3677282ea 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -1178,19 +1178,10 @@ func (r *runner) collectDiagnostics(view source.View) { // Always run diagnostics with analysis. r.server.diagnose(r.ctx, snapshot, true) for uri, reports := range r.server.diagnostics { - var diagnostics []*source.Diagnostic for _, report := range reports.reports { for _, d := range report.diags { - diagnostics = append(diagnostics, &source.Diagnostic{ - Range: d.Range, - Message: d.Message, - Related: d.Related, - Severity: d.Severity, - Source: d.Source, - Tags: d.Tags, - }) + r.diagnostics[uri] = append(r.diagnostics[uri], d) } - r.diagnostics[uri] = diagnostics } } } diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go index 9716aa7175..0d068e6e2d 100644 --- a/internal/lsp/mod/diagnostics.go +++ b/internal/lsp/mod/diagnostics.go @@ -27,25 +27,12 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.Vers return nil, err } reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{} - errors, err := DiagnosticsForMod(ctx, snapshot, fh) + diagnostics, err := DiagnosticsForMod(ctx, snapshot, fh) if err != nil { return nil, err } - for _, e := range errors { - d := &source.Diagnostic{ - Message: e.Message, - Range: e.Range, - Source: e.Category, - } - switch { - case e.Category == "syntax", e.Kind == source.ListError: - d.Severity = protocol.SeverityError - case e.Kind == source.UpgradeNotification: - d.Severity = protocol.SeverityInformation - default: - d.Severity = protocol.SeverityWarning - } - fh, err := snapshot.GetVersionedFile(ctx, e.URI) + for _, d := range diagnostics { + fh, err := snapshot.GetVersionedFile(ctx, d.URI) if err != nil { return nil, err } @@ -83,10 +70,11 @@ func DiagnosticsForMod(ctx context.Context, snapshot source.Snapshot, fh source. return nil, err } diagnostics = append(diagnostics, &source.Diagnostic{ - URI: fh.URI(), - Range: rng, - Kind: source.UpgradeNotification, - Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path), + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityInformation, + Source: source.UpgradeNotification, + Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path), SuggestedFixes: []source.SuggestedFix{{ Title: fmt.Sprintf("Upgrade to %v", ver), Command: &protocol.Command{ diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go index b426a04da1..f2b382dea6 100644 --- a/internal/lsp/source/diagnostics.go +++ b/internal/lsp/source/diagnostics.go @@ -34,22 +34,6 @@ func GetTypeCheckDiagnostics(ctx context.Context, snapshot Snapshot, pkg Package if onlyIgnoredFiles { return TypeCheckDiagnostics{} } - - // Prepare any additional reports for the errors in this package. - for _, e := range pkg.GetDiagnostics() { - // We only need to handle lower-level errors. - if e.Kind != ListError { - continue - } - // If no file is associated with the error, pick an open file from the package. - if e.URI.Filename() == "" { - for _, pgf := range pkg.CompiledGoFiles() { - if snapshot.IsOpen(pgf.URI) { - e.URI = pgf.URI - } - } - } - } return typeCheckDiagnostics(ctx, snapshot, pkg) } @@ -61,54 +45,60 @@ func Analyze(ctx context.Context, snapshot Snapshot, pkg Package, typeCheckResul } // If we don't have any list or parse errors, run analyses. analyzers := pickAnalyzers(snapshot, typeCheckResult.HasTypeErrors) - analysisErrors, err := snapshot.Analyze(ctx, pkg.ID(), analyzers...) + analysisDiagnostics, err := snapshot.Analyze(ctx, pkg.ID(), analyzers...) if err != nil { return nil, err } + analysisDiagnostics = cloneDiagnostics(analysisDiagnostics) reports := emptyDiagnostics(pkg) // Report diagnostics and errors from root analyzers. - for _, e := range analysisErrors { + for _, diag := range analysisDiagnostics { // If the diagnostic comes from a "convenience" analyzer, it is not // meant to provide diagnostics, but rather only suggested fixes. // Skip these types of errors in diagnostics; we will use their // suggested fixes when providing code actions. - if isConvenienceAnalyzer(e.Category) { + if isConvenienceAnalyzer(string(diag.Source)) { continue } // This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code. // If we are deleting code as part of all of our suggested fixes, assume that this is dead code. // TODO(golang/go#34508): Return these codes from the diagnostics themselves. var tags []protocol.DiagnosticTag - if onlyDeletions(e.SuggestedFixes) { + if onlyDeletions(diag.SuggestedFixes) { tags = append(tags, protocol.Unnecessary) } // Type error analyzers only alter the tags for existing type errors. - if _, ok := snapshot.View().Options().TypeErrorAnalyzers[e.Category]; ok { - existingDiagnostics := typeCheckResult.Diagnostics[e.URI] - for _, d := range existingDiagnostics { - if r := protocol.CompareRange(e.Range, d.Range); r != 0 { + if _, ok := snapshot.View().Options().TypeErrorAnalyzers[string(diag.Source)]; ok { + existingDiagnostics := typeCheckResult.Diagnostics[diag.URI] + for _, existing := range existingDiagnostics { + if r := protocol.CompareRange(diag.Range, existing.Range); r != 0 { continue } - if e.Message != d.Message { + if diag.Message != existing.Message { continue } - d.Tags = append(d.Tags, tags...) + existing.Tags = append(existing.Tags, tags...) } } else { - reports[e.URI] = append(reports[e.URI], &Diagnostic{ - Range: e.Range, - Message: e.Message, - Source: e.Category, - Severity: protocol.SeverityWarning, - Tags: tags, - Related: e.Related, - }) + diag.Tags = append(diag.Tags, tags...) + reports[diag.URI] = append(reports[diag.URI], diag) } } return reports, nil } +// cloneDiagnostics makes a shallow copy of diagnostics so that Analyze +// can add tags to them without affecting the cached diagnostics. +func cloneDiagnostics(diags []*Diagnostic) []*Diagnostic { + result := []*Diagnostic{} + for _, d := range diags { + clone := *d + result = append(result, &clone) + } + return result +} + func pickAnalyzers(snapshot Snapshot, hadTypeErrors bool) []*analysis.Analyzer { // Always run convenience analyzers. categories := []map[string]Analyzer{snapshot.View().Options().ConvenienceAnalyzers} @@ -166,28 +156,19 @@ func typeCheckDiagnostics(ctx context.Context, snapshot Snapshot, pkg Package) T defer done() diagSets := make(map[span.URI]*diagnosticSet) - for _, e := range pkg.GetDiagnostics() { - diag := &Diagnostic{ - Message: e.Message, - Range: e.Range, - Severity: protocol.SeverityError, - Related: e.Related, - } - set, ok := diagSets[e.URI] + for _, diag := range pkg.GetDiagnostics() { + set, ok := diagSets[diag.URI] if !ok { set = &diagnosticSet{} - diagSets[e.URI] = set + diagSets[diag.URI] = set } - switch e.Kind { + switch diag.Source { case ParseError: set.parseErrors = append(set.parseErrors, diag) - diag.Source = "syntax" case TypeError: set.typeErrors = append(set.typeErrors, diag) - diag.Source = "compiler" case ListError: set.listErrors = append(set.listErrors, diag) - diag.Source = "go list" } } typecheck := TypeCheckDiagnostics{ @@ -208,7 +189,7 @@ func typeCheckDiagnostics(ctx context.Context, snapshot Snapshot, pkg Package) T case len(set.typeErrors) > 0: typecheck.HasTypeErrors = true } - typecheck.Diagnostics[uri] = diags + typecheck.Diagnostics[uri] = cloneDiagnostics(diags) } return typecheck } diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go index 776cbb09c2..f2a0e676e9 100644 --- a/internal/lsp/source/gc_annotations.go +++ b/internal/lsp/source/gc_annotations.go @@ -141,10 +141,11 @@ func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnosti }) } diagnostic := &Diagnostic{ + URI: uri, Range: zeroIndexedRange(d.Range), Message: msg, Severity: d.Severity, - Source: d.Source, + Source: OptimizationDetailsError, // d.Source is always "go compiler" as of 1.16, use our own Tags: d.Tags, Related: related, } diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index bd4875ac07..90b25e199f 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -571,9 +571,9 @@ type Diagnostic struct { Code string CodeHref string - Source string - Kind ErrorKind - Category string // only used by analysis errors so far + // Source is a human-readable description of the source of the error. + // Diagnostics generated by an analysis.Analyzer set it to Analyzer.Name. + Source DiagnosticSource Message string @@ -585,21 +585,22 @@ type Diagnostic struct { SuggestedFixes []SuggestedFix } -// GoModTidy is the source for a diagnostic computed by running `go mod tidy`. -const GoModTidy = "go mod tidy" - -type ErrorKind int +type DiagnosticSource string const ( - UnknownError = ErrorKind(iota) - ListError - ParseError - TypeError - ModTidyError - Analysis - UpgradeNotification + UnknownError DiagnosticSource = "" + ListError DiagnosticSource = "go list" + ParseError DiagnosticSource = "syntax" + TypeError DiagnosticSource = "compiler" + ModTidyError DiagnosticSource = "go mod tidy" + OptimizationDetailsError DiagnosticSource = "optimizer details" + UpgradeNotification DiagnosticSource = "upgrade available" ) +func AnalyzerErrorKind(name string) DiagnosticSource { + return DiagnosticSource(name) +} + var ( PackagesLoadError = errors.New("packages.Load error") ) diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 1680814db0..c99086ad3a 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -1004,7 +1004,7 @@ func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity want := &source.Diagnostic{ Range: rng, Severity: severity, - Source: msgSource, + Source: source.DiagnosticSource(msgSource), Message: msg, } data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)