Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deps: upgrade x/tools and gopls to d456b1cd #706

Merged
merged 1 commit into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/govim/internal/golang_org_x_tools/imports/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, e
// GetAllCandidates gets all of the packages starting with prefix that can be
// imported by filename, sorted by import path.
func GetAllCandidates(ctx context.Context, callback func(ImportFix), searchPrefix, filename, filePkg string, opt *Options) error {
_, opt, err := initialize(filename, nil, opt)
_, opt, err := initialize(filename, []byte{}, opt)
if err != nil {
return err
}
Expand All @@ -128,7 +128,7 @@ func GetAllCandidates(ctx context.Context, callback func(ImportFix), searchPrefi

// GetPackageExports returns all known packages with name pkg and their exports.
func GetPackageExports(ctx context.Context, callback func(PackageExport), searchPkg, filename, filePkg string, opt *Options) error {
_, opt, err := initialize(filename, nil, opt)
_, opt, err := initialize(filename, []byte{}, opt)
if err != nil {
return err
}
Expand Down
7 changes: 1 addition & 6 deletions cmd/govim/internal/golang_org_x_tools/lsp/cache/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package cache
import (
"bytes"
"context"
"fmt"
"go/scanner"
"go/token"
"go/types"
Expand Down Expand Up @@ -105,12 +104,8 @@ func sourceError(ctx context.Context, fset *token.FileSet, pkg *pkg, e interface
if err != nil {
return nil, err
}
ph, err := pkg.File(spn.URI())
if err != nil {
return nil, fmt.Errorf("finding file for error %q: %v", msg, err)
}
return &source.Error{
File: ph.File().Identity(),
URI: spn.URI(),
Range: rng,
Message: msg,
Kind: kind,
Expand Down
16 changes: 0 additions & 16 deletions cmd/govim/internal/golang_org_x_tools/lsp/cache/modfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,6 @@ func (v *view) modFiles(ctx context.Context) (span.URI, span.URI, error) {
if v.mod == nil {
return "", "", nil
}
// Copy the real go.mod file content into the temp go.mod file.
origFile, err := os.Open(v.mod.realMod.Filename())
if err != nil {
return "", "", err
}
defer origFile.Close()

tempFile, err := os.OpenFile(v.mod.tempMod.Filename(), os.O_WRONLY, os.ModePerm)
if err != nil {
return "", "", err
}
defer tempFile.Close()

if _, err := io.Copy(tempFile, origFile); err != nil {
return "", "", err
}
return v.mod.realMod, v.mod.tempMod, nil
}

Expand Down
272 changes: 230 additions & 42 deletions cmd/govim/internal/golang_org_x_tools/lsp/cache/parse_mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ package cache

import (
"context"
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"

"golang.org/x/mod/modfile"
"golang.org/x/tools/go/packages"
"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/protocol"
"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/source"
"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/telemetry"
Expand All @@ -21,82 +25,266 @@ import (
errors "golang.org/x/xerrors"
)

const ModTidyError = "go mod tidy"
const SyntaxError = "syntax"

type parseModKey struct {
snapshot source.Snapshot
cfg string
}

type parseModHandle struct {
handle *memoize.Handle
file source.FileHandle
cfg *packages.Config
}

type parseModData struct {
memoize.NoCopy

modfile *modfile.File
mapper *protocol.ColumnMapper
err error
modfile *modfile.File
mapper *protocol.ColumnMapper
parseErrors []source.Error
err error
}

func (pgh *parseModHandle) String() string {
return pgh.File().Identity().URI.Filename()
}

func (pgh *parseModHandle) File() source.FileHandle {
return pgh.file
}

func (pgh *parseModHandle) Parse(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, []source.Error, error) {
v := pgh.handle.Get(ctx)
if v == nil {
return nil, nil, nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI)
}
data := v.(*parseModData)
return data.modfile, data.mapper, data.parseErrors, data.err
}

func (c *cache) ParseModHandle(fh source.FileHandle) source.ParseModHandle {
h := c.store.Bind(fh.Identity(), func(ctx context.Context) interface{} {
func (s *snapshot) ParseModHandle(ctx context.Context, fh source.FileHandle) source.ParseModHandle {
realfh, tempfh, err := s.ModFiles(ctx)
cfg := s.View().Config(ctx)
folder := s.View().Folder().Filename()

key := parseModKey{
snapshot: s,
cfg: hashConfig(cfg),
}
h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
data := &parseModData{}
data.modfile, data.mapper, data.err = parseMod(ctx, fh)
if err != nil {
data.err = err
return data
}
// Check the case when the tempModfile flag is turned off.
if realfh == nil || tempfh == nil {
return data
}
data.modfile, data.mapper, data.parseErrors, data.err = goModFileDiagnostics(ctx, realfh, tempfh, cfg, folder)
return data
})
return &parseModHandle{
handle: h,
file: fh,
cfg: cfg,
}
}

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()))
func goModFileDiagnostics(ctx context.Context, realfh, tempfh source.FileHandle, cfg *packages.Config, folder string) (*modfile.File, *protocol.ColumnMapper, []source.Error, error) {
ctx, done := trace.StartSpan(ctx, "cache.parseMod", telemetry.File.Of(realfh.Identity().URI.Filename()))
defer done()

buf, _, err := fh.Read(ctx)
// Copy the real go.mod file content into the temp go.mod file.
contents, err := ioutil.ReadFile(realfh.Identity().URI.Filename())
if err != nil {
return nil, nil, nil, err
}
if err := ioutil.WriteFile(tempfh.Identity().URI.Filename(), contents, os.ModePerm); err != nil {
return nil, nil, nil, err
}

// We want to run "go mod tidy" to be able to diff between the real and the temp files.
args := append([]string{"mod", "tidy"}, cfg.BuildFlags...)
if _, err := source.InvokeGo(ctx, folder, 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, nil, nil, err
}
}

realMod, m, parseErr, err := parseModFile(ctx, realfh)
if parseErr != nil {
return nil, nil, []source.Error{*parseErr}, nil
}
if err != nil {
return nil, nil, nil, err
}

tempMod, _, _, err := parseModFile(ctx, tempfh)
if err != nil {
return nil, nil, nil, err
}

errors, err := modRequireErrors(realfh, m, realMod, tempMod)
if err != nil {
return nil, nil, nil, err
}
return realMod, m, errors, nil
}

func modParseErrors(ctx context.Context, uri span.URI, m *protocol.ColumnMapper, modTidyErr error, buf []byte) (source.Error, error) {
re := regexp.MustCompile(`.*:([\d]+): (.+)`)
matches := re.FindStringSubmatch(strings.TrimSpace(modTidyErr.Error()))
if len(matches) < 3 {
log.Error(ctx, "could not parse golang/x/mod error message", modTidyErr)
return source.Error{}, modTidyErr
}
line, err := strconv.Atoi(matches[1])
if err != nil {
return source.Error{}, modTidyErr
}
lines := strings.Split(string(buf), "\n")
if len(lines) <= line {
return source.Error{}, errors.Errorf("could not parse goland/x/mod error message, line number out of range")
}
// Get the length of the line that the error is present on.
endOfLine := len(lines[line-1])
sOffset, err := m.Converter.ToOffset(line, 0)
if err != nil {
return source.Error{}, err
}
eOffset, err := m.Converter.ToOffset(line, endOfLine)
if err != nil {
return nil, nil, err
return source.Error{}, err
}
parsed, err := modfile.Parse(fh.Identity().URI.Filename(), buf, nil)
spn := span.New(uri, span.NewPoint(line, 0, sOffset), span.NewPoint(line, endOfLine, eOffset))
rng, err := m.Range(spn)
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, nil, err
return source.Error{}, err
}
return source.Error{
Category: SyntaxError,
Message: matches[2],
Range: rng,
URI: uri,
}, nil
}

func modRequireErrors(realfh source.FileHandle, m *protocol.ColumnMapper, realMod, tempMod *modfile.File) ([]source.Error, error) {
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)
}
line, e := strconv.Atoi(matches[1])
if e != nil {
return nil, nil, err
tempReqs[req.Mod.Path] = req
}

var errors []source.Error
for _, req := range realReqs {
if req.Syntax == nil {
continue
}
dep := req.Mod.Path
// Handle dependencies that are incorrectly labeled indirect and vice versa.
if tempReqs[dep] != nil && req.Indirect != tempReqs[dep].Indirect {
directErr, err := modDirectnessErrors(realfh, m, req)
if err != nil {
return nil, err
}
errors = append(errors, directErr)
}
contents := strings.Split(string(buf), "\n")[line-1]
return nil, nil, &source.Error{
Message: matches[2],
Range: protocol.Range{
Start: protocol.Position{Line: float64(line - 1), Character: float64(0)},
End: protocol.Position{Line: float64(line - 1), Character: float64(len(contents))},
},
// Handle unused dependencies.
if tempReqs[dep] == nil {
rng, err := rangeFromPositions(realfh.Identity().URI, m, req.Syntax.Start, req.Syntax.End)
if err != nil {
return nil, err
}
errors = append(errors, source.Error{
Category: ModTidyError,
Message: fmt.Sprintf("%s is not used in this module.", dep),
Range: rng,
URI: realfh.Identity().URI,
})
}
}
return errors, nil
}

func modDirectnessErrors(fh source.FileHandle, m *protocol.ColumnMapper, req *modfile.Require) (source.Error, error) {
rng, err := rangeFromPositions(fh.Identity().URI, m, req.Syntax.Start, req.Syntax.End)
if err != nil {
return source.Error{}, err
}
if req.Indirect {
// If the dependency should be direct, just highlight the // indirect.
if comments := req.Syntax.Comment(); comments != nil && len(comments.Suffix) > 0 {
end := comments.Suffix[0].Start
end.LineRune += len(comments.Suffix[0].Token)
end.Byte += len([]byte(comments.Suffix[0].Token))
rng, err = rangeFromPositions(fh.Identity().URI, m, comments.Suffix[0].Start, end)
if err != nil {
return source.Error{}, err
}
}
return source.Error{
Category: ModTidyError,
Message: fmt.Sprintf("%s should be a direct dependency.", req.Mod.Path),
Range: rng,
URI: fh.Identity().URI,
}, nil
}
return source.Error{
Category: ModTidyError,
Message: fmt.Sprintf("%s should be an indirect dependency.", req.Mod.Path),
Range: rng,
URI: fh.Identity().URI,
}, nil
}

func parseModFile(ctx context.Context, fh source.FileHandle) (*modfile.File, *protocol.ColumnMapper, *source.Error, error) {
contents, _, err := fh.Read(ctx)
if err != nil {
return nil, nil, nil, err
}
m := &protocol.ColumnMapper{
URI: fh.Identity().URI,
Converter: span.NewContentConverter(fh.Identity().URI.Filename(), buf),
Content: buf,
Converter: span.NewContentConverter(fh.Identity().URI.Filename(), contents),
Content: contents,
}
parsed, err := modfile.Parse(fh.Identity().URI.Filename(), contents, nil)
if err != nil {
parseErr, err := modParseErrors(ctx, fh.Identity().URI, m, err, contents)
return nil, nil, &parseErr, err
}
return parsed, m, nil
return parsed, m, nil, nil
}

func (pgh *parseModHandle) String() string {
return pgh.File().Identity().URI.Filename()
}
func rangeFromPositions(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
line, col, err := m.Converter.ToPosition(s.Byte)
if err != nil {
return protocol.Range{}, err
}
start := span.NewPoint(line, col, s.Byte)

func (pgh *parseModHandle) File() source.FileHandle {
return pgh.file
}
line, col, err = m.Converter.ToPosition(e.Byte)
if err != nil {
return protocol.Range{}, err
}
end := span.NewPoint(line, col, e.Byte)

func (pgh *parseModHandle) Parse(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, error) {
v := pgh.handle.Get(ctx)
if v == nil {
return nil, nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI)
spn := span.New(uri, start, end)
rng, err := m.Range(spn)
if err != nil {
return protocol.Range{}, err
}
data := v.(*parseModData)
return data.modfile, data.mapper, data.err
return rng, nil
}
Loading