mirror of https://github.com/golang/go.git
internal/lsp: add mapper for go.mod files
This change adds a protocol.ColumnMapper when parsing go.mod files. This will prevent us from having to worry about line and column offsets, specifically when converting from the x/mod/modfile position to a span.Span. Updates golang/go#31999 Change-Id: Iacdfb42d61dfea9b5f70325cf5a87c9575f8f345 Reviewed-on: https://go-review.googlesource.com/c/tools/+/214699 Run-TryBot: Rohan Challa <rohan@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
3be912efc3
commit
579903b3ad
|
|
@ -15,6 +15,7 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/lsp/telemetry"
|
||||
"golang.org/x/tools/internal/memoize"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/telemetry/log"
|
||||
"golang.org/x/tools/internal/telemetry/trace"
|
||||
errors "golang.org/x/xerrors"
|
||||
|
|
@ -29,13 +30,14 @@ type parseModData struct {
|
|||
memoize.NoCopy
|
||||
|
||||
modfile *modfile.File
|
||||
mapper *protocol.ColumnMapper
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *cache) ParseModHandle(fh source.FileHandle) source.ParseModHandle {
|
||||
h := c.store.Bind(fh.Identity(), func(ctx context.Context) interface{} {
|
||||
data := &parseModData{}
|
||||
data.modfile, data.err = parseMod(ctx, fh)
|
||||
data.modfile, data.mapper, data.err = parseMod(ctx, fh)
|
||||
return data
|
||||
})
|
||||
return &parseModHandle{
|
||||
|
|
@ -44,29 +46,29 @@ func (c *cache) ParseModHandle(fh source.FileHandle) source.ParseModHandle {
|
|||
}
|
||||
}
|
||||
|
||||
func parseMod(ctx context.Context, fh source.FileHandle) (*modfile.File, error) {
|
||||
func parseMod(ctx context.Context, fh source.FileHandle) (*modfile.File, *protocol.ColumnMapper, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "cache.parseMod", telemetry.File.Of(fh.Identity().URI.Filename()))
|
||||
defer done()
|
||||
|
||||
buf, _, err := fh.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
f, err := modfile.Parse(fh.Identity().URI.Filename(), buf, nil)
|
||||
parsed, err := modfile.Parse(fh.Identity().URI.Filename(), buf, nil)
|
||||
if err != nil {
|
||||
// TODO(golang/go#36486): This can be removed when modfile.Parse returns structured errors.
|
||||
re := regexp.MustCompile(`.*:([\d]+): (.+)`)
|
||||
matches := re.FindStringSubmatch(strings.TrimSpace(err.Error()))
|
||||
if len(matches) < 3 {
|
||||
log.Error(ctx, "could not parse golang/x/mod error message", err)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
line, e := strconv.Atoi(matches[1])
|
||||
if e != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
contents := strings.Split(string(buf), "\n")[line-1]
|
||||
return nil, &source.Error{
|
||||
return nil, nil, &source.Error{
|
||||
Message: matches[2],
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{Line: float64(line - 1), Character: float64(0)},
|
||||
|
|
@ -74,7 +76,12 @@ func parseMod(ctx context.Context, fh source.FileHandle) (*modfile.File, error)
|
|||
},
|
||||
}
|
||||
}
|
||||
return f, nil
|
||||
m := &protocol.ColumnMapper{
|
||||
URI: fh.Identity().URI,
|
||||
Converter: span.NewContentConverter(fh.Identity().URI.Filename(), buf),
|
||||
Content: buf,
|
||||
}
|
||||
return parsed, m, nil
|
||||
}
|
||||
|
||||
func (pgh *parseModHandle) String() string {
|
||||
|
|
@ -85,11 +92,11 @@ func (pgh *parseModHandle) File() source.FileHandle {
|
|||
return pgh.file
|
||||
}
|
||||
|
||||
func (pgh *parseModHandle) Parse(ctx context.Context) (*modfile.File, error) {
|
||||
func (pgh *parseModHandle) Parse(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, error) {
|
||||
v := pgh.handle.Get(ctx)
|
||||
if v == nil {
|
||||
return nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI)
|
||||
return nil, nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI)
|
||||
}
|
||||
data := v.(*parseModData)
|
||||
return data.modfile, data.err
|
||||
return data.modfile, data.mapper, data.err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/lsp/telemetry"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/telemetry/trace"
|
||||
)
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File
|
|||
}
|
||||
}
|
||||
|
||||
realMod, err := snapshot.View().Session().Cache().ParseModHandle(realfh).Parse(ctx)
|
||||
realMod, m, err := snapshot.View().Session().Cache().ParseModHandle(realfh).Parse(ctx)
|
||||
// If the go.mod file fails to parse, return errors right away.
|
||||
if err, ok := err.(*source.Error); ok {
|
||||
return map[source.FileIdentity][]source.Diagnostic{
|
||||
|
|
@ -58,7 +59,7 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tempMod, err := snapshot.View().Session().Cache().ParseModHandle(tempfh).Parse(ctx)
|
||||
tempMod, _, err := snapshot.View().Session().Cache().ParseModHandle(tempfh).Parse(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -84,10 +85,25 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File
|
|||
continue
|
||||
}
|
||||
dep := req.Mod.Path
|
||||
|
||||
start, err := positionToPoint(m, req.Syntax.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
end, err := positionToPoint(m, req.Syntax.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn := span.New(realfh.Identity().URI, start, end)
|
||||
rng, err := m.Range(spn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diag := &source.Diagnostic{
|
||||
Message: fmt.Sprintf("%s is not used in this module.", dep),
|
||||
Source: "go mod tidy",
|
||||
Range: protocol.Range{Start: getPos(req.Syntax.Start), End: getPos(req.Syntax.End)},
|
||||
Range: rng,
|
||||
Severity: protocol.SeverityWarning,
|
||||
}
|
||||
if tempReqs[dep] != nil && req.Indirect != tempReqs[dep].Indirect {
|
||||
|
|
@ -101,10 +117,10 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File
|
|||
return reports, nil
|
||||
}
|
||||
|
||||
// TODO: Check to see if we need to go through internal/span (for multiple byte characters).
|
||||
func getPos(pos modfile.Position) protocol.Position {
|
||||
return protocol.Position{
|
||||
Line: float64(pos.Line - 1),
|
||||
Character: float64(pos.LineRune - 1),
|
||||
func positionToPoint(m *protocol.ColumnMapper, pos modfile.Position) (span.Point, error) {
|
||||
line, col, err := m.Converter.ToPosition(pos.Byte)
|
||||
if err != nil {
|
||||
return span.Point{}, err
|
||||
}
|
||||
return span.NewPoint(line, col, pos.Byte), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ type ParseModHandle interface {
|
|||
|
||||
// Parse returns the parsed modifle for the go.mod file.
|
||||
// If the file is not available, returns nil and an error.
|
||||
Parse(ctx context.Context) (*modfile.File, error)
|
||||
Parse(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, error)
|
||||
}
|
||||
|
||||
// ParseMode controls the content of the AST produced when parsing a source file.
|
||||
|
|
|
|||
Loading…
Reference in New Issue