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/protocol"
15 "golang.org/x/tools/internal/lsp/source"
16 "golang.org/x/tools/internal/span"
19 // LensFuncs returns the supported lensFuncs for go.mod files.
20 func LensFuncs() map[string]source.LensFunc {
21 return map[string]source.LensFunc{
22 source.CommandUpgradeDependency.Name: upgradeLenses,
23 source.CommandTidy.Name: tidyLens,
24 source.CommandVendor.Name: vendorLens,
28 func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
29 pm, err := snapshot.ParseMod(ctx, fh)
30 if err != nil || pm.File == nil {
33 if len(pm.File.Require) == 0 {
34 // Nothing to upgrade.
37 upgradeTransitiveArgs, err := source.MarshalArgs(fh.URI(), false, []string{"-u", "all"})
42 for _, req := range pm.File.Require {
43 requires = append(requires, req.Mod.Path)
45 upgradeDirectArgs, err := source.MarshalArgs(fh.URI(), false, requires)
49 // Put the upgrade code lenses above the first require block or statement.
50 rng, err := firstRequireRange(fh, pm)
54 return []protocol.CodeLens{
57 Command: protocol.Command{
58 Title: "Upgrade transitive dependencies",
59 Command: source.CommandUpgradeDependency.ID(),
60 Arguments: upgradeTransitiveArgs,
65 Command: protocol.Command{
66 Title: "Upgrade direct dependencies",
67 Command: source.CommandUpgradeDependency.ID(),
68 Arguments: upgradeDirectArgs,
75 func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
76 pm, err := snapshot.ParseMod(ctx, fh)
77 if err != nil || pm.File == nil {
80 if len(pm.File.Require) == 0 {
84 goModArgs, err := source.MarshalArgs(fh.URI())
88 rng, err := moduleStmtRange(fh, pm)
92 return []protocol.CodeLens{{
94 Command: protocol.Command{
95 Title: source.CommandTidy.Title,
96 Command: source.CommandTidy.ID(),
102 func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
103 pm, err := snapshot.ParseMod(ctx, fh)
104 if err != nil || pm.File == nil {
107 rng, err := moduleStmtRange(fh, pm)
111 goModArgs, err := source.MarshalArgs(fh.URI())
115 // Change the message depending on whether or not the module already has a
117 title := "Create vendor directory"
118 vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor")
119 if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() {
120 title = "Sync vendor directory"
122 return []protocol.CodeLens{{
124 Command: protocol.Command{
126 Command: source.CommandVendor.ID(),
127 Arguments: goModArgs,
132 func moduleStmtRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
133 if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil {
134 return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI())
136 syntax := pm.File.Module.Syntax
137 return lineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
140 // firstRequireRange returns the range for the first "require" in the given
141 // go.mod file. This is either a require block or an individual require line.
142 func firstRequireRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {
143 if len(pm.File.Require) == 0 {
144 return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI())
146 var start, end modfile.Position
147 for _, stmt := range pm.File.Syntax.Stmt {
148 if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" {
149 start, end = b.Span()
154 firstRequire := pm.File.Require[0].Syntax
155 if start.Byte == 0 || firstRequire.Start.Byte < start.Byte {
156 start, end = firstRequire.Start, firstRequire.End
158 return lineToRange(pm.Mapper, fh.URI(), start, end)
161 func lineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
162 line, col, err := m.Converter.ToPosition(start.Byte)
164 return protocol.Range{}, err
166 s := span.NewPoint(line, col, start.Byte)
167 line, col, err = m.Converter.ToPosition(end.Byte)
169 return protocol.Range{}, err
171 e := span.NewPoint(line, col, end.Byte)
172 return m.Range(span.New(uri, s, e))