internal/lsp: show errors in indirect dependencies as diagnostics

This change is a follow-up from CL 242477. We now also surface errors
in indirect dependencies as diagnostics in the go.mod file. Any errors
we cannot match are surfaced as diagnostics on the entire go.mod file.
This isn't the most user-friendly way, but it seems simplest.

Change-Id: Ife92c68c09b74a0b53de29d95b6c4c2a9c78d3b9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/244438
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Rebecca Stambler 2020-07-22 20:29:48 -04:00
parent 0cdb17d11b
commit d2c1b4b270
2 changed files with 64 additions and 9 deletions

View File

@ -8,9 +8,9 @@ package mod
import (
"context"
"fmt"
"regexp"
"strings"
"unicode"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
@ -68,21 +68,25 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File
return reports, nil
}
var moduleAtVersionRe = regexp.MustCompile(`(?P<module>.*)@(?P<version>.*)`)
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 ExtractGoCommandError(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, loadErr error) (*source.Diagnostic, error) {
// We try to match module versions in error messages. Some examples:
//
// err: exit status 1: stderr: go: example.com@v1.2.2: reading example.com/@v/v1.2.2.mod: no such file or directory
// exit status 1: 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
// 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 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.
// 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.
var v module.Version
for _, s := range strings.Split(loadErr.Error(), ":") {
fields := strings.FieldsFunc(loadErr.Error(), 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 {
@ -133,7 +137,9 @@ func ExtractGoCommandError(ctx context.Context, snapshot source.Snapshot, fh sou
}
return toDiagnostic(rep.Syntax)
}
return nil, fmt.Errorf("no diagnostics for %v", loadErr)
// No match for the module path was found in the go.mod file.
// Show the error on the module declaration.
return toDiagnostic(parsed.Module.Syntax)
}
func rangeFromPositions(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {

View File

@ -445,3 +445,52 @@ require example.com v1.2.3
}
})
}
// Confirm that an error in an indirect dependency of a requirement is surfaced
// as a diagnostic in the go.mod file.
func TestErrorInIndirectDependency(t *testing.T) {
testenv.NeedsGo1Point(t, 14)
const badProxy = `
-- example.com@v1.2.3/go.mod --
module example.com
go 1.12
require random.org v1.2.3 // indirect
-- example.com@v1.2.3/blah/blah.go --
package blah
const Name = "Blah"
-- random.org@v1.2.3/go.mod --
module bob.org
go 1.12
-- random.org@v1.2.3/blah/blah.go --
package hello
const Name = "Hello"
`
const module = `
-- go.mod --
module mod.com
go 1.14
require example.com v1.2.3
-- main.go --
package main
import "example.com/blah"
func main() {
println(blah.Name)
}
`
withOptions(WithProxy(badProxy)).run(t, module, func(t *testing.T, env *Env) {
env.OpenFile("go.mod")
env.Await(
env.DiagnosticAtRegexp("go.mod", "require example.com v1.2.3"),
)
})
}