golang.org/x/tools/gopls@v0.15.3/internal/mod/code_lens.go (about) 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. 4 5 package mod 6 7 import ( 8 "context" 9 "fmt" 10 "os" 11 "path/filepath" 12 13 "golang.org/x/mod/modfile" 14 "golang.org/x/tools/gopls/internal/cache" 15 "golang.org/x/tools/gopls/internal/file" 16 "golang.org/x/tools/gopls/internal/golang" 17 "golang.org/x/tools/gopls/internal/protocol" 18 "golang.org/x/tools/gopls/internal/protocol/command" 19 ) 20 21 // LensFuncs returns the supported lensFuncs for go.mod files. 22 func LensFuncs() map[command.Command]golang.LensFunc { 23 return map[command.Command]golang.LensFunc{ 24 command.UpgradeDependency: upgradeLenses, 25 command.Tidy: tidyLens, 26 command.Vendor: vendorLens, 27 command.RunGovulncheck: vulncheckLenses, 28 } 29 } 30 31 func upgradeLenses(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 32 pm, err := snapshot.ParseMod(ctx, fh) 33 if err != nil || pm.File == nil { 34 return nil, err 35 } 36 uri := fh.URI() 37 reset, err := command.NewResetGoModDiagnosticsCommand("Reset go.mod diagnostics", command.ResetGoModDiagnosticsArgs{URIArg: command.URIArg{URI: uri}}) 38 if err != nil { 39 return nil, err 40 } 41 // Put the `Reset go.mod diagnostics` codelens on the module statement. 42 modrng, err := moduleStmtRange(fh, pm) 43 if err != nil { 44 return nil, err 45 } 46 lenses := []protocol.CodeLens{{Range: modrng, Command: &reset}} 47 if len(pm.File.Require) == 0 { 48 // Nothing to upgrade. 49 return lenses, nil 50 } 51 var requires []string 52 for _, req := range pm.File.Require { 53 requires = append(requires, req.Mod.Path) 54 } 55 checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{ 56 URI: uri, 57 Modules: requires, 58 }) 59 if err != nil { 60 return nil, err 61 } 62 upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{ 63 URI: uri, 64 AddRequire: false, 65 GoCmdArgs: []string{"-d", "-u", "-t", "./..."}, 66 }) 67 if err != nil { 68 return nil, err 69 } 70 upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{ 71 URI: uri, 72 AddRequire: false, 73 GoCmdArgs: append([]string{"-d"}, requires...), 74 }) 75 if err != nil { 76 return nil, err 77 } 78 79 // Put the upgrade code lenses above the first require block or statement. 80 rng, err := firstRequireRange(fh, pm) 81 if err != nil { 82 return nil, err 83 } 84 85 return append(lenses, []protocol.CodeLens{ 86 {Range: rng, Command: &checkUpgrade}, 87 {Range: rng, Command: &upgradeTransitive}, 88 {Range: rng, Command: &upgradeDirect}, 89 }...), nil 90 } 91 92 func tidyLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 93 pm, err := snapshot.ParseMod(ctx, fh) 94 if err != nil || pm.File == nil { 95 return nil, err 96 } 97 uri := fh.URI() 98 cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: []protocol.DocumentURI{uri}}) 99 if err != nil { 100 return nil, err 101 } 102 rng, err := moduleStmtRange(fh, pm) 103 if err != nil { 104 return nil, err 105 } 106 return []protocol.CodeLens{{ 107 Range: rng, 108 Command: &cmd, 109 }}, nil 110 } 111 112 func vendorLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 113 pm, err := snapshot.ParseMod(ctx, fh) 114 if err != nil || pm.File == nil { 115 return nil, err 116 } 117 if len(pm.File.Require) == 0 { 118 // Nothing to vendor. 119 return nil, nil 120 } 121 rng, err := moduleStmtRange(fh, pm) 122 if err != nil { 123 return nil, err 124 } 125 title := "Create vendor directory" 126 uri := fh.URI() 127 cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri}) 128 if err != nil { 129 return nil, err 130 } 131 // Change the message depending on whether or not the module already has a 132 // vendor directory. 133 vendorDir := filepath.Join(filepath.Dir(fh.URI().Path()), "vendor") 134 if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() { 135 title = "Sync vendor directory" 136 } 137 return []protocol.CodeLens{{Range: rng, Command: &cmd}}, nil 138 } 139 140 func moduleStmtRange(fh file.Handle, pm *cache.ParsedModule) (protocol.Range, error) { 141 if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { 142 return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI()) 143 } 144 syntax := pm.File.Module.Syntax 145 return pm.Mapper.OffsetRange(syntax.Start.Byte, syntax.End.Byte) 146 } 147 148 // firstRequireRange returns the range for the first "require" in the given 149 // go.mod file. This is either a require block or an individual require line. 150 func firstRequireRange(fh file.Handle, pm *cache.ParsedModule) (protocol.Range, error) { 151 if len(pm.File.Require) == 0 { 152 return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI()) 153 } 154 var start, end modfile.Position 155 for _, stmt := range pm.File.Syntax.Stmt { 156 if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" { 157 start, end = b.Span() 158 break 159 } 160 } 161 162 firstRequire := pm.File.Require[0].Syntax 163 if start.Byte == 0 || firstRequire.Start.Byte < start.Byte { 164 start, end = firstRequire.Start, firstRequire.End 165 } 166 return pm.Mapper.OffsetRange(start.Byte, end.Byte) 167 } 168 169 func vulncheckLenses(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { 170 pm, err := snapshot.ParseMod(ctx, fh) 171 if err != nil || pm.File == nil { 172 return nil, err 173 } 174 // Place the codelenses near the module statement. 175 // A module may not have the require block, 176 // but vulnerabilities can exist in standard libraries. 177 uri := fh.URI() 178 rng, err := moduleStmtRange(fh, pm) 179 if err != nil { 180 return nil, err 181 } 182 183 vulncheck, err := command.NewRunGovulncheckCommand("Run govulncheck", command.VulncheckArgs{ 184 URI: uri, 185 Pattern: "./...", 186 }) 187 if err != nil { 188 return nil, err 189 } 190 return []protocol.CodeLens{ 191 {Range: rng, Command: &vulncheck}, 192 }, nil 193 }