1
0
mirror of https://github.com/golang/go synced 2024-09-30 22:38:33 -06:00
go/internal/lsp/mod/diagnostics.go
Rohan Challa 544dc8ea2d internal/lsp: change go1.14 check to be more lenient
This change adds the -e flag to ensure that we get the release tags when we are checking if the go version is at least 1.14. This also adjusts the check to be more lenient when it comes to processing the output of the version check.

This also fixes another issue where if the version is not 1.14 we were publishing empty diagnostics for an empty uri. This arose because we did not check if there was a valid go.mod file before we published the reports.

Fixes golang/go#36488

Change-Id: I5a8057c153f49167811e2bf8e4ade52bf6171d6b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/214290
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rohan Challa <rohan@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-01-13 22:38:16 +00:00

111 lines
3.6 KiB
Go

// Copyright 2019 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.
// Package mod provides core features related to go.mod file
// handling for use by Go editors and tools.
package mod
import (
"context"
"fmt"
"strings"
"golang.org/x/mod/modfile"
"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/telemetry/trace"
)
func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.FileIdentity][]source.Diagnostic, error) {
// TODO: We will want to support diagnostics for go.mod files even when the -modfile flag is turned off.
realfh, tempfh, err := snapshot.ModFiles(ctx)
if err != nil {
return nil, err
}
// Check the case when the tempModfile flag is turned off.
if realfh == nil || tempfh == nil {
return nil, nil
}
ctx, done := trace.StartSpan(ctx, "modfiles.Diagnostics", telemetry.File.Of(realfh.Identity().URI))
defer done()
// If the view has a temporary go.mod file, we want to run "go mod tidy" to be able to
// diff between the real and the temp files.
cfg := snapshot.View().Config(ctx)
args := append([]string{"mod", "tidy"}, cfg.BuildFlags...)
if _, err := source.InvokeGo(ctx, snapshot.View().Folder().Filename(), cfg.Env, args...); err != nil {
// Ignore parse errors here. They'll be handled below.
if !strings.Contains(err.Error(), "errors parsing go.mod") {
return nil, err
}
}
realMod, 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{
realfh.Identity(): []source.Diagnostic{{
Message: err.Message,
Source: "syntax",
Range: err.Range,
Severity: protocol.SeverityError,
}},
}, nil
}
if err != nil {
return nil, err
}
tempMod, err := snapshot.View().Session().Cache().ParseModHandle(tempfh).Parse(ctx)
if err != nil {
return nil, err
}
// Check indirect vs direct, and removal of dependencies.
reports := map[source.FileIdentity][]source.Diagnostic{
realfh.Identity(): []source.Diagnostic{},
}
realReqs := make(map[string]*modfile.Require, len(realMod.Require))
tempReqs := make(map[string]*modfile.Require, len(tempMod.Require))
for _, req := range realMod.Require {
realReqs[req.Mod.Path] = req
}
for _, req := range tempMod.Require {
realReq := realReqs[req.Mod.Path]
if realReq != nil && realReq.Indirect == req.Indirect {
delete(realReqs, req.Mod.Path)
}
tempReqs[req.Mod.Path] = req
}
for _, req := range realReqs {
if req.Syntax == nil {
continue
}
dep := req.Mod.Path
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)},
Severity: protocol.SeverityWarning,
}
if tempReqs[dep] != nil && req.Indirect != tempReqs[dep].Indirect {
diag.Message = fmt.Sprintf("%s should be an indirect dependency.", dep)
if req.Indirect {
diag.Message = fmt.Sprintf("%s should not be an indirect dependency.", dep)
}
}
reports[realfh.Identity()] = append(reports[realfh.Identity()], *diag)
}
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),
}
}