mirror of https://github.com/golang/go.git
internal/lsp: restructure the way we report critical errors
The fragmentation of the critical error reporting is getting in the way of small fixes. This change adds a GetCriticalError function that reports critical errors, while ModTidy and WorkspacePackages no longer report critical errors. Any function that wants to process critical errors should call AwaitLoaded. Some other smaller changes are made to account for these changes. One change is that we may report multiple *source.Errors, with the assumption that duplicate diagnostics will be caught by the diagnostic caching. Also, any `go list` error message that ends with "to add it" is now considered an error that gets the "add dependency" suggested fix. Fixes golang/go#43338 Fixes golang/go#43307 Change-Id: I056b32e0a0495d9a1dcc64f9f5103649a6102645 Reviewed-on: https://go-review.googlesource.com/c/tools/+/280093 Run-TryBot: Rebecca Stambler <rstambler@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
fbbba25c5f
commit
929a8494cf
|
|
@ -937,5 +937,59 @@ require random.com v1.2.3
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSumUpdateQuickFix(t *testing.T) {
|
||||
// Error messages changed in 1.16 that changed the diagnostic positions.
|
||||
testenv.NeedsGo1Point(t, 16)
|
||||
|
||||
const mod = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
example.com v1.2.3
|
||||
)
|
||||
-- go.sum --
|
||||
-- main.go --
|
||||
package main
|
||||
|
||||
import (
|
||||
"example.com/blah"
|
||||
)
|
||||
|
||||
func main() {
|
||||
blah.Hello()
|
||||
}
|
||||
`
|
||||
withOptions(
|
||||
ProxyFiles(workspaceProxy),
|
||||
Modes(Singleton),
|
||||
).run(t, mod, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("go.mod")
|
||||
pos := env.RegexpSearch("go.mod", "example.com")
|
||||
params := &protocol.PublishDiagnosticsParams{}
|
||||
env.Await(
|
||||
OnceMet(
|
||||
env.DiagnosticAtRegexp("go.mod", "example.com"),
|
||||
ReadDiagnostics("go.mod", params),
|
||||
),
|
||||
)
|
||||
var diagnostic protocol.Diagnostic
|
||||
for _, d := range params.Diagnostics {
|
||||
if d.Range.Start.Line == float64(pos.Line) {
|
||||
diagnostic = d
|
||||
break
|
||||
}
|
||||
}
|
||||
env.ApplyQuickFixes("go.mod", []protocol.Diagnostic{diagnostic})
|
||||
const want = `example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c=
|
||||
example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
|
||||
`
|
||||
if got := env.ReadWorkspaceFile("go.sum"); got != want {
|
||||
t.Fatalf("unexpected go.sum contents:\n%s", tests.Diff(t, want, got))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,13 +128,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf
|
|||
event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
|
||||
}
|
||||
if len(pkgs) == 0 {
|
||||
if err != nil {
|
||||
// Try to extract the load error into a structured error with
|
||||
// diagnostics.
|
||||
if criticalErr := s.parseLoadError(ctx, err); criticalErr != nil {
|
||||
return criticalErr
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("no packages returned")
|
||||
}
|
||||
return errors.Errorf("%v: %w", err, source.PackagesLoadError)
|
||||
|
|
@ -182,31 +176,9 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *snapshot) parseLoadError(ctx context.Context, loadErr error) *source.CriticalError {
|
||||
if strings.Contains(loadErr.Error(), "cannot find main module") {
|
||||
return s.WorkspaceLayoutError(ctx)
|
||||
}
|
||||
criticalErr := &source.CriticalError{
|
||||
MainError: loadErr,
|
||||
}
|
||||
// Attempt to place diagnostics in the relevant go.mod files, if any.
|
||||
for _, uri := range s.ModFiles() {
|
||||
fh, err := s.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
srcErr := s.extractGoCommandError(ctx, s, fh, loadErr.Error())
|
||||
if srcErr == nil {
|
||||
continue
|
||||
}
|
||||
criticalErr.ErrorList = append(criticalErr.ErrorList, srcErr)
|
||||
}
|
||||
return criticalErr
|
||||
}
|
||||
|
||||
// workspaceLayoutErrors returns a diagnostic for every open file, as well as
|
||||
// an error message if there are no open files.
|
||||
func (s *snapshot) WorkspaceLayoutError(ctx context.Context) *source.CriticalError {
|
||||
func (s *snapshot) workspaceLayoutError(ctx context.Context) *source.CriticalError {
|
||||
if len(s.workspace.getKnownModFiles()) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func (s *snapshot) ParseMod(ctx context.Context, modFH source.FileHandle) (*sour
|
|||
// Attempt to convert the error to a standardized parse error.
|
||||
var parseErrors []*source.Error
|
||||
if err != nil {
|
||||
if parseErr, extractErr := extractErrorWithPosition(ctx, err.Error(), s); extractErr == nil {
|
||||
if parseErr := extractErrorWithPosition(ctx, err.Error(), s); parseErr != nil {
|
||||
parseErrors = []*source.Error{parseErr}
|
||||
}
|
||||
}
|
||||
|
|
@ -341,29 +341,39 @@ var moduleAtVersionRe = regexp.MustCompile(`^(?P<module>.*)@(?P<version>.*)$`)
|
|||
|
||||
// extractGoCommandError tries to parse errors that come from the go command
|
||||
// and shape them into go.mod diagnostics.
|
||||
func (s *snapshot) extractGoCommandError(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, goCmdError string) *source.Error {
|
||||
func (s *snapshot) extractGoCommandErrors(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, goCmdError string) []*source.Error {
|
||||
var srcErrs []*source.Error
|
||||
if srcErr := s.parseModError(ctx, fh, goCmdError); srcErr != nil {
|
||||
srcErrs = append(srcErrs, srcErr)
|
||||
}
|
||||
// If the error message contains a position, use that. Don't pass a file
|
||||
// handle in, as it might not be the file associated with the error.
|
||||
if srcErr, err := extractErrorWithPosition(ctx, goCmdError, s); err == nil {
|
||||
return srcErr
|
||||
if srcErr := extractErrorWithPosition(ctx, goCmdError, s); srcErr != nil {
|
||||
srcErrs = append(srcErrs, srcErr)
|
||||
} else if srcErr := s.matchErrorToModule(ctx, fh, goCmdError); srcErr != nil {
|
||||
srcErrs = append(srcErrs, srcErr)
|
||||
}
|
||||
// We try to match module versions in error messages. Some examples:
|
||||
//
|
||||
// example.com@v1.2.2: reading example.com/@v/v1.2.2.mod: no such file or directory
|
||||
// go: github.com/cockroachdb/apd/v2@v2.0.72: reading github.com/cockroachdb/apd/go.mod at revision v2.0.72: unknown revision v2.0.72
|
||||
// go: example.com@v1.2.3 requires\n\trandom.org@v1.2.3: parsing go.mod:\n\tmodule declares its path as: bob.org\n\tbut was required as: random.org
|
||||
//
|
||||
// We split on colons and whitespace, and attempt to match on something
|
||||
// that matches module@version. If we're able to find a match, we try to
|
||||
// find anything that matches it in the go.mod file.
|
||||
return srcErrs
|
||||
}
|
||||
|
||||
// matchErrorToModule attempts to match module version in error messages.
|
||||
// Some examples:
|
||||
//
|
||||
// example.com@v1.2.2: reading example.com/@v/v1.2.2.mod: no such file or directory
|
||||
// go: github.com/cockroachdb/apd/v2@v2.0.72: reading github.com/cockroachdb/apd/go.mod at revision v2.0.72: unknown revision v2.0.72
|
||||
// go: example.com@v1.2.3 requires\n\trandom.org@v1.2.3: parsing go.mod:\n\tmodule declares its path as: bob.org\n\tbut was required as: random.org
|
||||
//
|
||||
// We split on colons and whitespace, and attempt to match on something
|
||||
// that matches module@version. If we're able to find a match, we try to
|
||||
// find anything that matches it in the go.mod file.
|
||||
func (s *snapshot) matchErrorToModule(ctx context.Context, fh source.FileHandle, goCmdError string) *source.Error {
|
||||
var v module.Version
|
||||
fields := strings.FieldsFunc(goCmdError, func(r rune) bool {
|
||||
return unicode.IsSpace(r) || r == ':'
|
||||
})
|
||||
for _, s := range fields {
|
||||
s = strings.TrimSpace(s)
|
||||
match := moduleAtVersionRe.FindStringSubmatch(s)
|
||||
if match == nil || len(match) < 3 {
|
||||
for _, field := range fields {
|
||||
match := moduleAtVersionRe.FindStringSubmatch(field)
|
||||
if match == nil {
|
||||
continue
|
||||
}
|
||||
path, version := match[1], match[2]
|
||||
|
|
@ -378,7 +388,7 @@ func (s *snapshot) extractGoCommandError(ctx context.Context, snapshot source.Sn
|
|||
v.Path, v.Version = path, version
|
||||
break
|
||||
}
|
||||
pm, err := snapshot.ParseMod(ctx, fh)
|
||||
pm, err := s.ParseMod(ctx, fh)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -387,13 +397,19 @@ func (s *snapshot) extractGoCommandError(ctx context.Context, snapshot source.Sn
|
|||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if v.Path != "" && strings.Contains(goCmdError, "disabled by GOPROXY=off") {
|
||||
disabledByGOPROXY := strings.Contains(goCmdError, "disabled by GOPROXY=off")
|
||||
shouldAddDep := strings.Contains(goCmdError, "to add it")
|
||||
if v.Path != "" && (disabledByGOPROXY || shouldAddDep) {
|
||||
args, err := source.MarshalArgs(fh.URI(), false, []string{fmt.Sprintf("%v@%v", v.Path, v.Version)})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
msg := goCmdError
|
||||
if disabledByGOPROXY {
|
||||
msg = fmt.Sprintf("%v@%v has not been downloaded", v.Path, v.Version)
|
||||
}
|
||||
return &source.Error{
|
||||
Message: fmt.Sprintf("%v@%v has not been downloaded", v.Path, v.Version),
|
||||
Message: msg,
|
||||
Kind: source.ListError,
|
||||
Range: rng,
|
||||
URI: fh.URI(),
|
||||
|
|
@ -411,6 +427,7 @@ func (s *snapshot) extractGoCommandError(ctx context.Context, snapshot source.Sn
|
|||
Message: goCmdError,
|
||||
Range: rng,
|
||||
URI: fh.URI(),
|
||||
Kind: source.ListError,
|
||||
}
|
||||
}
|
||||
// Check if there are any require, exclude, or replace statements that
|
||||
|
|
@ -449,10 +466,10 @@ var errorPositionRe = regexp.MustCompile(`(?P<pos>.*:([\d]+)(:([\d]+))?): (?P<ms
|
|||
// information for the given unstructured error. If a file handle is provided,
|
||||
// the error position will be on that file. This is useful for parse errors,
|
||||
// where we already know the file with the error.
|
||||
func extractErrorWithPosition(ctx context.Context, goCmdError string, src source.FileSource) (*source.Error, error) {
|
||||
func extractErrorWithPosition(ctx context.Context, goCmdError string, src source.FileSource) *source.Error {
|
||||
matches := errorPositionRe.FindStringSubmatch(strings.TrimSpace(goCmdError))
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("error message doesn't contain a position")
|
||||
return nil
|
||||
}
|
||||
var pos, msg string
|
||||
for i, name := range errorPositionRe.SubexpNames() {
|
||||
|
|
@ -466,11 +483,11 @@ func extractErrorWithPosition(ctx context.Context, goCmdError string, src source
|
|||
spn := span.Parse(pos)
|
||||
fh, err := src.GetFile(ctx, spn.URI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil
|
||||
}
|
||||
content, err := fh.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil
|
||||
}
|
||||
m := &protocol.ColumnMapper{
|
||||
URI: spn.URI(),
|
||||
|
|
@ -479,7 +496,7 @@ func extractErrorWithPosition(ctx context.Context, goCmdError string, src source
|
|||
}
|
||||
rng, err := m.Range(spn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil
|
||||
}
|
||||
category := GoCommandError
|
||||
if fh != nil {
|
||||
|
|
@ -490,5 +507,5 @@ func extractErrorWithPosition(ctx context.Context, goCmdError string, src source
|
|||
Message: msg,
|
||||
Range: rng,
|
||||
URI: spn.URI(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,11 +71,13 @@ func (s *snapshot) ModTidy(ctx context.Context, pm *source.ParsedModule) (*sourc
|
|||
return nil, source.ErrNoModOnDisk
|
||||
}
|
||||
}
|
||||
if criticalErr := s.GetCriticalError(ctx); criticalErr != nil {
|
||||
return &source.TidiedModule{
|
||||
Errors: criticalErr.ErrorList,
|
||||
}, nil
|
||||
}
|
||||
workspacePkgs, err := s.WorkspacePackages(ctx)
|
||||
if err != nil {
|
||||
if tm, ok := s.parseModErrors(ctx, fh, err); ok {
|
||||
return tm, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
importHash, err := hashImports(ctx, workspacePkgs)
|
||||
|
|
@ -149,87 +151,77 @@ func (s *snapshot) ModTidy(ctx context.Context, pm *source.ParsedModule) (*sourc
|
|||
return mth.tidy(ctx, s)
|
||||
}
|
||||
|
||||
func (s *snapshot) parseModErrors(ctx context.Context, fh source.FileHandle, goCommandErr error) (*source.TidiedModule, bool) {
|
||||
if goCommandErr == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (s *snapshot) parseModError(ctx context.Context, fh source.FileHandle, errText string) *source.Error {
|
||||
// Match on common error messages. This is really hacky, but I'm not sure
|
||||
// of any better way. This can be removed when golang/go#39164 is resolved.
|
||||
errText := goCommandErr.Error()
|
||||
isInconsistentVendor := strings.Contains(errText, "inconsistent vendoring")
|
||||
isGoSumUpdates := strings.Contains(errText, "updates to go.sum needed") || strings.Contains(errText, "missing go.sum entry")
|
||||
|
||||
if !isInconsistentVendor && !isGoSumUpdates {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
pmf, err := s.ParseMod(ctx, fh)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
if pmf.File.Module == nil || pmf.File.Module.Syntax == nil {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
rng, err := rangeFromPositions(pmf.Mapper, pmf.File.Module.Syntax.Start, pmf.File.Module.Syntax.End)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
args, err := source.MarshalArgs(protocol.URIFromSpanURI(fh.URI()))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case isInconsistentVendor:
|
||||
return &source.TidiedModule{
|
||||
Errors: []*source.Error{{
|
||||
URI: fh.URI(),
|
||||
Range: rng,
|
||||
Kind: source.ListError,
|
||||
Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
|
||||
return &source.Error{
|
||||
URI: fh.URI(),
|
||||
Range: rng,
|
||||
Kind: 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{{
|
||||
Title: source.CommandVendor.Title,
|
||||
Command: &protocol.Command{
|
||||
Command: source.CommandVendor.ID(),
|
||||
Title: source.CommandVendor.Title,
|
||||
Arguments: args,
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}, true
|
||||
|
||||
case isGoSumUpdates:
|
||||
return &source.TidiedModule{
|
||||
Errors: []*source.Error{{
|
||||
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".`,
|
||||
SuggestedFixes: []source.SuggestedFix{
|
||||
{
|
||||
Title: source.CommandTidy.Title,
|
||||
Command: &protocol.Command{
|
||||
Command: source.CommandTidy.ID(),
|
||||
Title: source.CommandTidy.Title,
|
||||
Arguments: args,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: source.CommandUpdateGoSum.Title,
|
||||
Command: &protocol.Command{
|
||||
Command: source.CommandUpdateGoSum.ID(),
|
||||
Title: source.CommandUpdateGoSum.Title,
|
||||
Arguments: args,
|
||||
},
|
||||
},
|
||||
SuggestedFixes: []source.SuggestedFix{{
|
||||
Title: source.CommandVendor.Title,
|
||||
Command: &protocol.Command{
|
||||
Command: source.CommandVendor.ID(),
|
||||
Title: source.CommandVendor.Title,
|
||||
Arguments: args,
|
||||
},
|
||||
}},
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
case isGoSumUpdates:
|
||||
return &source.Error{
|
||||
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".`,
|
||||
SuggestedFixes: []source.SuggestedFix{
|
||||
{
|
||||
Title: source.CommandTidy.Title,
|
||||
Command: &protocol.Command{
|
||||
Command: source.CommandTidy.ID(),
|
||||
Title: source.CommandTidy.Title,
|
||||
Arguments: args,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: source.CommandUpdateGoSum.Title,
|
||||
Command: &protocol.Command{
|
||||
Command: source.CommandUpdateGoSum.ID(),
|
||||
Title: source.CommandUpdateGoSum.Title,
|
||||
Arguments: args,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashImports(ctx context.Context, wsPackages []source.Package) (string, error) {
|
||||
|
|
|
|||
|
|
@ -993,6 +993,84 @@ func (s *snapshot) isOpenLocked(uri span.URI) bool {
|
|||
}
|
||||
|
||||
func (s *snapshot) awaitLoaded(ctx context.Context) error {
|
||||
err := s.awaitLoadedAllErrors(ctx)
|
||||
|
||||
// If we still have absolutely no metadata, check if the view failed to
|
||||
// initialize and return any errors.
|
||||
// TODO(rstambler): Should we clear the error after we return it?
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if len(s.metadata) == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
|
||||
loadErr := s.awaitLoadedAllErrors(ctx)
|
||||
|
||||
// Even if packages didn't fail to load, we still may want to show
|
||||
// additional warnings.
|
||||
if loadErr == nil {
|
||||
wsPkgs, _ := s.WorkspacePackages(ctx)
|
||||
if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" {
|
||||
return &source.CriticalError{
|
||||
MainError: fmt.Errorf(msg),
|
||||
}
|
||||
}
|
||||
// Even if workspace packages were returned, there still may be an error
|
||||
// with the user's workspace layout. Workspace packages that only have the
|
||||
// ID "command-line-arguments" are usually a symptom of a bad workspace
|
||||
// configuration.
|
||||
if containsCommandLineArguments(wsPkgs) {
|
||||
return s.workspaceLayoutError(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(loadErr.Error(), "cannot find main module") {
|
||||
return s.workspaceLayoutError(ctx)
|
||||
}
|
||||
criticalErr := &source.CriticalError{
|
||||
MainError: loadErr,
|
||||
}
|
||||
// Attempt to place diagnostics in the relevant go.mod files, if any.
|
||||
for _, uri := range s.ModFiles() {
|
||||
fh, err := s.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
criticalErr.ErrorList = append(criticalErr.ErrorList, s.extractGoCommandErrors(ctx, s, fh, loadErr.Error())...)
|
||||
}
|
||||
return criticalErr
|
||||
}
|
||||
|
||||
const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
|
||||
If you are using modules, please open your editor to a directory in your module.
|
||||
If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
|
||||
|
||||
func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string {
|
||||
if snapshot.ValidBuildConfiguration() {
|
||||
return ""
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if len(pkg.MissingDependencies()) > 0 {
|
||||
return adHocPackagesWarning
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func containsCommandLineArguments(pkgs []source.Package) bool {
|
||||
for _, pkg := range pkgs {
|
||||
if strings.Contains(pkg.ID(), "command-line-arguments") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) error {
|
||||
// Do not return results until the snapshot's view has been initialized.
|
||||
s.AwaitInitialized(ctx)
|
||||
|
||||
|
|
@ -1002,15 +1080,10 @@ func (s *snapshot) awaitLoaded(ctx context.Context) error {
|
|||
if err := s.reloadOrphanedFiles(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
// If we still have absolutely no metadata, check if the view failed to
|
||||
// initialize and return any errors.
|
||||
// TODO(rstambler): Should we clear the error after we return it?
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if len(s.metadata) == 0 {
|
||||
return s.initializedErr
|
||||
}
|
||||
return nil
|
||||
// TODO(rstambler): Should we be more careful about returning the
|
||||
// initialization error? Is it possible for the initialization error to be
|
||||
// corrected without a successful reinitialization?
|
||||
return s.initializedErr
|
||||
}
|
||||
|
||||
func (s *snapshot) AwaitInitialized(ctx context.Context) {
|
||||
|
|
|
|||
|
|
@ -580,6 +580,10 @@ func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
|
|||
} else {
|
||||
s.initializedErr = err
|
||||
}
|
||||
} else {
|
||||
// Clear out the initialization error, in case it had been set
|
||||
// previously.
|
||||
s.initializedErr = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,9 +172,9 @@ require a.com master
|
|||
if parseErr == nil {
|
||||
t.Fatalf("%s: expected an unparseable go.mod file", uri.Filename())
|
||||
}
|
||||
srcErr, err := extractErrorWithPosition(ctx, parseErr.Error(), src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
srcErr := extractErrorWithPosition(ctx, parseErr.Error(), src)
|
||||
if srcErr == nil {
|
||||
t.Fatalf("unable to extract positions from %v", parseErr.Error())
|
||||
}
|
||||
if srcErr.URI != uri {
|
||||
t.Errorf("unexpected URI: got %s, wanted %s", srcErr.URI, uri)
|
||||
|
|
|
|||
|
|
@ -178,29 +178,13 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
|
|||
if s.shouldIgnoreError(ctx, snapshot, err) {
|
||||
return
|
||||
}
|
||||
// Even if packages didn't fail to load, we still may want to show
|
||||
// additional warnings.
|
||||
if err == nil {
|
||||
if msg := shouldShowAdHocPackagesWarning(snapshot, wsPkgs); msg != "" {
|
||||
err = fmt.Errorf(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Even if workspace packages were returned, there still may be an error
|
||||
// with the user's workspace layout. Workspace packages that only have the
|
||||
// ID "command-line-arguments" are usually a symptom of a bad workspace
|
||||
// configuration.
|
||||
if containsCommandLineArguments(wsPkgs) {
|
||||
if criticalErr := snapshot.WorkspaceLayoutError(ctx); criticalErr != nil {
|
||||
err = criticalErr
|
||||
}
|
||||
}
|
||||
criticalErr := snapshot.GetCriticalError(ctx)
|
||||
|
||||
// Show the error as a progress error report so that it appears in the
|
||||
// status bar. If a client doesn't support progress reports, the error
|
||||
// will still be shown as a ShowMessage. If there is no error, any running
|
||||
// error progress reports will be closed.
|
||||
s.showCriticalErrorStatus(ctx, snapshot, err)
|
||||
s.showCriticalErrorStatus(ctx, snapshot, criticalErr)
|
||||
|
||||
// If there are no workspace packages, there is nothing to diagnose and
|
||||
// there are no orphaned files.
|
||||
|
|
@ -337,27 +321,11 @@ func (s *Server) clearDiagnosticSource(dsource diagnosticSource) {
|
|||
}
|
||||
}
|
||||
|
||||
const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
|
||||
If you are using modules, please open your editor to a directory in your module.
|
||||
If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
|
||||
|
||||
func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string {
|
||||
if snapshot.ValidBuildConfiguration() {
|
||||
return ""
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if len(pkg.MissingDependencies()) > 0 {
|
||||
return adHocPackagesWarning
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
const WorkspaceLoadFailure = "Error loading workspace"
|
||||
|
||||
// showCriticalErrorStatus shows the error as a progress report.
|
||||
// If the error is nil, it clears any existing error progress report.
|
||||
func (s *Server) showCriticalErrorStatus(ctx context.Context, snapshot source.Snapshot, err error) {
|
||||
func (s *Server) showCriticalErrorStatus(ctx context.Context, snapshot source.Snapshot, err *source.CriticalError) {
|
||||
s.criticalErrorStatusMu.Lock()
|
||||
defer s.criticalErrorStatusMu.Unlock()
|
||||
|
||||
|
|
@ -578,12 +546,3 @@ func (s *Server) shouldIgnoreError(ctx context.Context, snapshot source.Snapshot
|
|||
})
|
||||
return !hasGo
|
||||
}
|
||||
|
||||
func containsCommandLineArguments(pkgs []source.Package) bool {
|
||||
for _, pkg := range pkgs {
|
||||
if strings.Contains(pkg.ID(), "command-line-arguments") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -757,6 +757,10 @@ func (e *Editor) codeAction(ctx context.Context, path string, rng *protocol.Rang
|
|||
return err
|
||||
}
|
||||
}
|
||||
// Some commands may edit files on disk.
|
||||
if err := e.sandbox.Workdir.CheckForFileChanges(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/debug/tag"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
|
||||
|
|
@ -66,10 +65,6 @@ func ErrorsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileH
|
|||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
// Some error messages can also be displayed as diagnostics.
|
||||
if criticalErr := (*source.CriticalError)(nil); errors.As(err, &criticalErr) {
|
||||
return criticalErr.ErrorList, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return tidied.Errors, nil
|
||||
|
|
|
|||
|
|
@ -147,10 +147,8 @@ type Snapshot interface {
|
|||
// WorkspacePackages returns the snapshot's top-level packages.
|
||||
WorkspacePackages(ctx context.Context) ([]Package, error)
|
||||
|
||||
// WorkspaceLayoutError reports whether there might be any problems with
|
||||
// the user's workspace configuration, which would cause bad or incorrect
|
||||
// diagnostics.
|
||||
WorkspaceLayoutError(ctx context.Context) *CriticalError
|
||||
// GetCriticalError returns any critical errors in the workspace.
|
||||
GetCriticalError(ctx context.Context) *CriticalError
|
||||
}
|
||||
|
||||
// PackageFilter sets how a package is filtered out from a set of packages
|
||||
|
|
|
|||
Loading…
Reference in New Issue