| package mod |
| |
| import ( |
| "context" |
| "fmt" |
| "os" |
| "path/filepath" |
| |
| "golang.org/x/mod/modfile" |
| "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[string]source.LensFunc { |
| return map[string]source.LensFunc{ |
| source.CommandUpgradeDependency.Name: upgradeLens, |
| source.CommandTidy.Name: tidyLens, |
| source.CommandVendor.Name: vendorLens, |
| } |
| } |
| |
| func upgradeLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { |
| pm, err := snapshot.ParseMod(ctx, fh) |
| if err != nil { |
| return nil, err |
| } |
| module := pm.File.Module |
| if module == nil || module.Syntax == nil { |
| return nil, nil |
| } |
| upgrades, err := snapshot.ModUpgrade(ctx, fh) |
| if err != nil { |
| return nil, err |
| } |
| var ( |
| codelenses []protocol.CodeLens |
| allUpgrades []string |
| ) |
| for _, req := range pm.File.Require { |
| dep := req.Mod.Path |
| latest, ok := upgrades[dep] |
| if !ok { |
| continue |
| } |
| if req.Syntax == nil { |
| continue |
| } |
| // Get the range of the require directive. |
| rng, err := positionsToRange(fh.URI(), pm.Mapper, req.Syntax.Start, req.Syntax.End) |
| if err != nil { |
| return nil, err |
| } |
| upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, []string{dep}) |
| if err != nil { |
| return nil, err |
| } |
| codelenses = append(codelenses, protocol.CodeLens{ |
| Range: rng, |
| Command: protocol.Command{ |
| Title: fmt.Sprintf("Upgrade dependency to %s", latest), |
| Command: source.CommandUpgradeDependency.ID(), |
| Arguments: upgradeDepArgs, |
| }, |
| }) |
| allUpgrades = append(allUpgrades, dep) |
| } |
| // If there is at least 1 upgrade, add "Upgrade all dependencies" to |
| // the module statement. |
| if len(allUpgrades) > 0 { |
| upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, append([]string{"-u"}, allUpgrades...)) |
| if err != nil { |
| return nil, err |
| } |
| // Get the range of the module directive. |
| moduleRng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, module.Syntax.Start, module.Syntax.End) |
| if err != nil { |
| return nil, err |
| } |
| codelenses = append(codelenses, protocol.CodeLens{ |
| Range: moduleRng, |
| Command: protocol.Command{ |
| Title: "Upgrade all dependencies", |
| Command: source.CommandUpgradeDependency.ID(), |
| Arguments: upgradeDepArgs, |
| }, |
| }) |
| } |
| return codelenses, err |
| } |
| |
| func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { |
| goModArgs, err := source.MarshalArgs(fh.URI()) |
| if err != nil { |
| return nil, err |
| } |
| tidied, err := snapshot.ModTidy(ctx, fh) |
| if err != nil { |
| return nil, err |
| } |
| if len(tidied.Errors) == 0 { |
| return nil, nil |
| } |
| pm, err := snapshot.ParseMod(ctx, fh) |
| if err != nil { |
| return nil, err |
| } |
| if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { |
| return nil, fmt.Errorf("no parsed go.mod for %s", fh.URI()) |
| } |
| rng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End) |
| if err != nil { |
| return nil, err |
| } |
| return []protocol.CodeLens{{ |
| Range: rng, |
| Command: protocol.Command{ |
| Title: source.CommandTidy.Title, |
| Command: source.CommandTidy.ID(), |
| Arguments: goModArgs, |
| }, |
| }}, err |
| } |
| |
| func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { |
| goModArgs, err := source.MarshalArgs(fh.URI()) |
| if err != nil { |
| return nil, err |
| } |
| pm, err := snapshot.ParseMod(ctx, fh) |
| if err != nil { |
| return nil, err |
| } |
| if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { |
| return nil, fmt.Errorf("no parsed go.mod for %s", fh.URI()) |
| } |
| rng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End) |
| if err != nil { |
| return nil, err |
| } |
| // Change the message depending on whether or not the module already has a |
| // vendor directory. |
| title := "Create 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: protocol.Command{ |
| Title: title, |
| Command: source.CommandVendor.ID(), |
| Arguments: goModArgs, |
| }, |
| }}, nil |
| } |
| |
| func positionsToRange(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) |
| line, col, err = m.Converter.ToPosition(e.Byte) |
| if err != nil { |
| return protocol.Range{}, err |
| } |
| end := span.NewPoint(line, col, e.Byte) |
| rng, err := m.Range(span.New(uri, start, end)) |
| if err != nil { |
| return protocol.Range{}, err |
| } |
| return rng, err |
| } |