mirror of https://github.com/golang/go.git
169 lines
5.0 KiB
Go
169 lines
5.0 KiB
Go
// Copyright 2020 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
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"golang.org/x/mod/modfile"
|
|
"golang.org/x/tools/internal/lsp/command"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
// LensFuncs returns the supported lensFuncs for go.mod files.
|
|
func LensFuncs() map[command.Command]source.LensFunc {
|
|
return map[command.Command]source.LensFunc{
|
|
command.UpgradeDependency: upgradeLenses,
|
|
command.Tidy: tidyLens,
|
|
command.Vendor: vendorLens,
|
|
}
|
|
}
|
|
|
|
func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
|
|
pm, err := snapshot.ParseMod(ctx, fh)
|
|
if err != nil || pm.File == nil {
|
|
return nil, err
|
|
}
|
|
if len(pm.File.Require) == 0 {
|
|
// Nothing to upgrade.
|
|
return nil, nil
|
|
}
|
|
var requires []string
|
|
for _, req := range pm.File.Require {
|
|
requires = append(requires, req.Mod.Path)
|
|
}
|
|
uri := protocol.URIFromSpanURI(fh.URI())
|
|
checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{
|
|
URI: uri,
|
|
Modules: requires,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{
|
|
URI: uri,
|
|
AddRequire: false,
|
|
GoCmdArgs: []string{"-u", "all"},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{
|
|
URI: uri,
|
|
AddRequire: false,
|
|
GoCmdArgs: requires,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Put the upgrade code lenses above the first require block or statement.
|
|
rng, err := firstRequireRange(fh, pm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []protocol.CodeLens{
|
|
{Range: rng, Command: checkUpgrade},
|
|
{Range: rng, Command: upgradeTransitive},
|
|
{Range: rng, Command: upgradeDirect},
|
|
}, nil
|
|
}
|
|
|
|
func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
|
|
pm, err := snapshot.ParseMod(ctx, fh)
|
|
if err != nil || pm.File == nil {
|
|
return nil, err
|
|
}
|
|
if len(pm.File.Require) == 0 {
|
|
// Nothing to vendor.
|
|
return nil, nil
|
|
}
|
|
uri := protocol.URIFromSpanURI(fh.URI())
|
|
cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArg{URI: uri})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rng, err := moduleStmtRange(fh, pm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []protocol.CodeLens{{
|
|
Range: rng,
|
|
Command: cmd,
|
|
}}, nil
|
|
}
|
|
|
|
func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
|
|
pm, err := snapshot.ParseMod(ctx, fh)
|
|
if err != nil || pm.File == nil {
|
|
return nil, err
|
|
}
|
|
rng, err := moduleStmtRange(fh, pm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
title := "Create vendor directory"
|
|
uri := protocol.URIFromSpanURI(fh.URI())
|
|
cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Change the message depending on whether or not the module already has a
|
|
// vendor directory.
|
|
vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor")
|
|
if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() {
|
|
title = "Sync vendor directory"
|
|
}
|
|
return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
|
|
}
|
|
|
|
func moduleStmtRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
|
|
if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil {
|
|
return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI())
|
|
}
|
|
syntax := pm.File.Module.Syntax
|
|
return lineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
|
|
}
|
|
|
|
// firstRequireRange returns the range for the first "require" in the given
|
|
// go.mod file. This is either a require block or an individual require line.
|
|
func firstRequireRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
|
|
if len(pm.File.Require) == 0 {
|
|
return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI())
|
|
}
|
|
var start, end modfile.Position
|
|
for _, stmt := range pm.File.Syntax.Stmt {
|
|
if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" {
|
|
start, end = b.Span()
|
|
break
|
|
}
|
|
}
|
|
|
|
firstRequire := pm.File.Require[0].Syntax
|
|
if start.Byte == 0 || firstRequire.Start.Byte < start.Byte {
|
|
start, end = firstRequire.Start, firstRequire.End
|
|
}
|
|
return lineToRange(pm.Mapper, fh.URI(), start, end)
|
|
}
|
|
|
|
func lineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
|
|
line, col, err := m.Converter.ToPosition(start.Byte)
|
|
if err != nil {
|
|
return protocol.Range{}, err
|
|
}
|
|
s := span.NewPoint(line, col, start.Byte)
|
|
line, col, err = m.Converter.ToPosition(end.Byte)
|
|
if err != nil {
|
|
return protocol.Range{}, err
|
|
}
|
|
e := span.NewPoint(line, col, end.Byte)
|
|
return m.Range(span.New(uri, s, e))
|
|
}
|