2018-11-05 12:48:08 -07:00
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
2018-10-19 14:03:29 -06:00
package lsp
import (
2018-12-05 15:00:36 -07:00
"context"
2019-05-17 11:45:50 -06:00
"strings"
2020-01-31 15:27:08 -07:00
"sync"
2018-11-13 09:13:53 -07:00
2019-12-17 14:13:33 -07:00
"golang.org/x/tools/internal/lsp/mod"
2018-10-19 14:03:29 -06:00
"golang.org/x/tools/internal/lsp/protocol"
2018-11-02 14:15:31 -06:00
"golang.org/x/tools/internal/lsp/source"
2019-07-14 21:08:10 -06:00
"golang.org/x/tools/internal/lsp/telemetry"
2019-08-13 13:07:39 -06:00
"golang.org/x/tools/internal/telemetry/log"
2019-09-17 12:36:39 -06:00
"golang.org/x/tools/internal/telemetry/trace"
2020-01-11 21:59:57 -07:00
"golang.org/x/tools/internal/xcontext"
2018-10-19 14:03:29 -06:00
)
2020-01-31 15:27:08 -07:00
type diagnosticKey struct {
id source . FileIdentity
withAnalysis bool
}
2020-01-11 21:59:57 -07:00
func ( s * Server ) diagnoseDetached ( snapshot source . Snapshot ) {
ctx := snapshot . View ( ) . BackgroundContext ( )
ctx = xcontext . Detach ( ctx )
2019-11-20 12:26:02 -07:00
2020-01-31 15:27:08 -07:00
reports := s . diagnose ( ctx , snapshot , false )
s . publishReports ( ctx , snapshot , reports )
2019-11-15 12:47:29 -07:00
}
2020-01-11 21:59:57 -07:00
func ( s * Server ) diagnoseSnapshot ( snapshot source . Snapshot ) {
2019-11-20 12:26:02 -07:00
ctx := snapshot . View ( ) . BackgroundContext ( )
2019-09-17 09:19:11 -06:00
2020-01-31 15:27:08 -07:00
reports := s . diagnose ( ctx , snapshot , false )
s . publishReports ( ctx , snapshot , reports )
2019-11-20 12:26:02 -07:00
}
2019-03-14 15:19:01 -06:00
2020-01-11 21:59:57 -07:00
// diagnose is a helper function for running diagnostics with a given context.
// Do not call it directly.
2020-01-31 15:27:08 -07:00
func ( s * Server ) diagnose ( ctx context . Context , snapshot source . Snapshot , alwaysAnalyze bool ) map [ diagnosticKey ] [ ] source . Diagnostic {
2019-12-17 14:13:33 -07:00
ctx , done := trace . StartSpan ( ctx , "lsp:background-worker" )
defer done ( )
2020-01-31 15:27:08 -07:00
allReports := make ( map [ diagnosticKey ] [ ] source . Diagnostic )
var reportsMu sync . Mutex
var wg sync . WaitGroup
2020-01-24 08:14:25 -07:00
// Diagnose the go.mod file.
reports , missingModules , err := mod . Diagnostics ( ctx , snapshot )
if ctx . Err ( ) != nil {
2020-01-31 15:27:08 -07:00
return nil
2020-01-24 08:14:25 -07:00
}
if err != nil {
log . Error ( ctx , "diagnose: could not generate diagnostics for go.mod file" , err )
}
// Ensure that the reports returned from mod.Diagnostics are only related to the
// go.mod file for the module.
if len ( reports ) > 1 {
panic ( "unexpected reports from mod.Diagnostics" )
}
modURI , _ := snapshot . View ( ) . ModFiles ( )
2020-01-31 15:27:08 -07:00
for id , diags := range reports {
if id . URI != modURI {
2020-01-24 08:14:25 -07:00
panic ( "unexpected reports from mod.Diagnostics" )
}
2020-01-31 15:27:08 -07:00
key := diagnosticKey {
id : id ,
}
allReports [ key ] = diags
2020-01-24 08:14:25 -07:00
}
2020-01-11 21:59:57 -07:00
// Diagnose all of the packages in the workspace.
2020-01-31 15:27:08 -07:00
wsPackages , err := snapshot . WorkspacePackages ( ctx )
if ctx . Err ( ) != nil {
return nil
}
if err != nil {
log . Error ( ctx , "diagnose: no workspace packages" , err , telemetry . Snapshot . Of ( snapshot . ID ( ) ) , telemetry . Directory . Of ( snapshot . View ( ) . Folder ) )
return nil
}
for _ , ph := range wsPackages {
wg . Add ( 1 )
go func ( ph source . PackageHandle ) {
defer wg . Done ( )
// Only run analyses for packages with open files.
withAnalyses := alwaysAnalyze
for _ , fh := range ph . CompiledGoFiles ( ) {
if s . session . IsOpen ( fh . File ( ) . Identity ( ) . URI ) {
withAnalyses = true
2020-01-11 21:59:57 -07:00
}
2020-01-31 15:27:08 -07:00
}
reports , warn , err := source . Diagnostics ( ctx , snapshot , ph , missingModules , withAnalyses )
// Check if might want to warn the user about their build configuration.
if warn && ! snapshot . View ( ) . ValidBuildConfiguration ( ) {
s . client . ShowMessage ( ctx , & protocol . ShowMessageParams {
Type : protocol . Warning ,
// TODO(rstambler): We should really be able to point to a link on the website.
Message : ` You are neither in a module nor in your GOPATH. Please see https://github.com/golang/go/wiki/Modules for information on how to set up your Go project. ` ,
} )
}
if ctx . Err ( ) != nil {
return
}
if err != nil {
log . Error ( ctx , "diagnose: could not generate diagnostics for package" , err , telemetry . Snapshot . Of ( snapshot . ID ( ) ) , telemetry . Package . Of ( ph . ID ( ) ) )
return
}
reportsMu . Lock ( )
for id , diags := range reports {
key := diagnosticKey {
id : id ,
withAnalysis : withAnalyses ,
2020-01-11 21:59:57 -07:00
}
2020-01-31 15:27:08 -07:00
allReports [ key ] = diags
}
reportsMu . Unlock ( )
} ( ph )
}
wg . Wait ( )
return allReports
2019-12-17 14:13:33 -07:00
}
2020-01-31 15:27:08 -07:00
func ( s * Server ) publishReports ( ctx context . Context , snapshot source . Snapshot , reports map [ diagnosticKey ] [ ] source . Diagnostic ) {
2019-12-05 14:37:47 -07:00
// Check for context cancellation before publishing diagnostics.
if ctx . Err ( ) != nil {
return
}
2019-11-20 23:24:43 -07:00
s . deliveredMu . Lock ( )
defer s . deliveredMu . Unlock ( )
2020-01-31 15:27:08 -07:00
for key , diagnostics := range reports {
2019-12-05 14:37:47 -07:00
// Don't deliver diagnostics if the context has already been canceled.
if ctx . Err ( ) != nil {
break
}
2020-01-12 18:24:40 -07:00
2019-11-20 23:24:43 -07:00
// Pre-sort diagnostics to avoid extra work when we compare them.
source . SortDiagnostics ( diagnostics )
toSend := sentDiagnostics {
2020-01-31 15:27:08 -07:00
version : key . id . Version ,
identifier : key . id . Identifier ,
2020-01-13 18:41:03 -07:00
sorted : diagnostics ,
2020-01-31 15:27:08 -07:00
withAnalysis : key . withAnalysis ,
2020-01-13 18:41:03 -07:00
snapshotID : snapshot . ID ( ) ,
2019-11-20 23:24:43 -07:00
}
2020-01-17 11:12:00 -07:00
2020-01-10 10:29:37 -07:00
// We use the zero values if this is an unknown file.
2020-01-31 15:27:08 -07:00
delivered := s . delivered [ key . id . URI ]
2020-01-13 18:41:03 -07:00
2020-01-17 11:12:00 -07:00
// Snapshot IDs are always increasing, so we use them instead of file
// versions to create the correct order for diagnostics.
// If we've already delivered diagnostics for a future snapshot for this file,
// do not deliver them.
if delivered . snapshotID > toSend . snapshotID {
// Do not update the delivered map since it already contains newer diagnostics.
continue
}
// Check if we should reuse the cached diagnostics.
if equalDiagnostics ( delivered . sorted , diagnostics ) {
// Make sure to update the delivered map.
2020-01-31 15:27:08 -07:00
s . delivered [ key . id . URI ] = toSend
2020-01-08 11:53:50 -07:00
continue
}
2020-01-17 11:12:00 -07:00
// If we've already delivered diagnostics for this file, at this
// snapshot, with analyses, do not send diagnostics without analyses.
2020-01-10 10:29:37 -07:00
if delivered . snapshotID == toSend . snapshotID && delivered . version == toSend . version &&
delivered . withAnalysis && ! toSend . withAnalysis {
2020-01-17 11:12:00 -07:00
// Do not update the delivered map since it already contains better diagnostics.
2020-01-10 10:29:37 -07:00
continue
2019-11-20 23:24:43 -07:00
}
2020-01-17 11:12:00 -07:00
2019-12-05 14:37:47 -07:00
if err := s . client . PublishDiagnostics ( ctx , & protocol . PublishDiagnosticsParams {
2020-01-10 10:29:37 -07:00
Diagnostics : toProtocolDiagnostics ( diagnostics ) ,
2020-01-31 15:27:08 -07:00
URI : protocol . NewURI ( key . id . URI ) ,
Version : key . id . Version ,
2019-12-05 14:37:47 -07:00
} ) ; err != nil {
2020-01-26 14:05:01 -07:00
if ctx . Err ( ) == nil {
log . Error ( ctx , "publishReports: failed to deliver diagnostic" , err , telemetry . File )
}
2019-05-01 20:46:07 -06:00
continue
2019-03-14 15:19:01 -06:00
}
2019-11-20 23:24:43 -07:00
// Update the delivered map.
2020-01-31 15:27:08 -07:00
s . delivered [ key . id . URI ] = toSend
2019-11-20 23:24:43 -07:00
}
}
// equalDiagnostics returns true if the 2 lists of diagnostics are equal.
// It assumes that both a and b are already sorted.
func equalDiagnostics ( a , b [ ] source . Diagnostic ) bool {
if len ( a ) != len ( b ) {
return false
}
for i := 0 ; i < len ( a ) ; i ++ {
if source . CompareDiagnostic ( a [ i ] , b [ i ] ) != 0 {
return false
}
2019-05-01 20:46:07 -06:00
}
2019-11-20 23:24:43 -07:00
return true
2018-12-18 14:18:03 -07:00
}
2020-01-10 10:29:37 -07:00
func toProtocolDiagnostics ( diagnostics [ ] source . Diagnostic ) [ ] protocol . Diagnostic {
2018-11-12 12:15:47 -07:00
reports := [ ] protocol . Diagnostic { }
for _ , diag := range diagnostics {
2019-10-11 03:39:09 -06:00
related := make ( [ ] protocol . DiagnosticRelatedInformation , 0 , len ( diag . Related ) )
for _ , rel := range diag . Related {
related = append ( related , protocol . DiagnosticRelatedInformation {
Location : protocol . Location {
URI : protocol . NewURI ( rel . URI ) ,
Range : rel . Range ,
} ,
Message : rel . Message ,
} )
}
2019-09-24 22:46:57 -06:00
reports = append ( reports , protocol . Diagnostic {
2019-10-11 03:39:09 -06:00
Message : strings . TrimSpace ( diag . Message ) , // go list returns errors prefixed by newline
Range : diag . Range ,
Severity : diag . Severity ,
Source : diag . Source ,
Tags : diag . Tags ,
RelatedInformation : related ,
2019-09-24 22:46:57 -06:00
} )
2018-11-12 12:15:47 -07:00
}
2019-09-24 14:28:59 -06:00
return reports
2018-10-29 16:12:41 -06:00
}