1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
13 "golang.org/x/mod/modfile"
14 "golang.org/x/tools/internal/lsp/command"
15 "golang.org/x/tools/internal/lsp/protocol"
16 "golang.org/x/tools/internal/lsp/source"
17 "golang.org/x/tools/internal/span"
20 // LensFuncs returns the supported lensFuncs for go.mod files.
21 func LensFuncs() map[command.Command]source.LensFunc {
22 return map[command.Command]source.LensFunc{
23 command.UpgradeDependency: upgradeLenses,
24 command.Tidy: tidyLens,
25 command.Vendor: vendorLens,
29 func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
30 pm, err := snapshot.ParseMod(ctx, fh)
31 if err != nil || pm.File == nil {
34 if len(pm.File.Require) == 0 {
35 // Nothing to upgrade.
39 for _, req := range pm.File.Require {
40 requires = append(requires, req.Mod.Path)
42 uri := protocol.URIFromSpanURI(fh.URI())
43 checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{
50 upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{
53 GoCmdArgs: []string{"-u", "all"},
58 upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{
66 // Put the upgrade code lenses above the first require block or statement.
67 rng, err := firstRequireRange(fh, pm)
72 return []protocol.CodeLens{
73 {Range: rng, Command: checkUpgrade},
74 {Range: rng, Command: upgradeTransitive},
75 {Range: rng, Command: upgradeDirect},
79 func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
80 pm, err := snapshot.ParseMod(ctx, fh)
81 if err != nil || pm.File == nil {
84 if len(pm.File.Require) == 0 {
88 uri := protocol.URIFromSpanURI(fh.URI())
89 cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: []protocol.DocumentURI{uri}})
93 rng, err := moduleStmtRange(fh, pm)
97 return []protocol.CodeLens{{
103 func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
104 pm, err := snapshot.ParseMod(ctx, fh)
105 if err != nil || pm.File == nil {
108 rng, err := moduleStmtRange(fh, pm)
112 title := "Create vendor directory"
113 uri := protocol.URIFromSpanURI(fh.URI())
114 cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri})
118 // Change the message depending on whether or not the module already has a
120 vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor")
121 if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() {
122 title = "Sync vendor directory"
124 return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
127 func moduleStmtRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
128 if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil {
129 return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI())
131 syntax := pm.File.Module.Syntax
132 return lineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
135 // firstRequireRange returns the range for the first "require" in the given
136 // go.mod file. This is either a require block or an individual require line.
137 func firstRequireRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
138 if len(pm.File.Require) == 0 {
139 return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI())
141 var start, end modfile.Position
142 for _, stmt := range pm.File.Syntax.Stmt {
143 if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" {
144 start, end = b.Span()
149 firstRequire := pm.File.Require[0].Syntax
150 if start.Byte == 0 || firstRequire.Start.Byte < start.Byte {
151 start, end = firstRequire.Start, firstRequire.End
153 return lineToRange(pm.Mapper, fh.URI(), start, end)
156 func lineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
157 line, col, err := m.Converter.ToPosition(start.Byte)
159 return protocol.Range{}, err
161 s := span.NewPoint(line, col, start.Byte)
162 line, col, err = m.Converter.ToPosition(end.Byte)
164 return protocol.Range{}, err
166 e := span.NewPoint(line, col, end.Byte)
167 return m.Range(span.New(uri, s, e))