golang.org/x/tools/gopls@v0.15.3/internal/cmd/suggested_fix.go (about) 1 // Copyright 2019 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 cmd 6 7 import ( 8 "context" 9 "flag" 10 "fmt" 11 12 "golang.org/x/tools/gopls/internal/protocol" 13 "golang.org/x/tools/gopls/internal/util/slices" 14 "golang.org/x/tools/internal/tool" 15 ) 16 17 // TODO(adonovan): this command has a very poor user interface. It 18 // should have a way to query the available fixes for a file (without 19 // a span), enumerate the valid fix kinds, enable all fixes, and not 20 // require the pointless -all flag. See issue #60290. 21 22 // suggestedFix implements the fix verb for gopls. 23 type suggestedFix struct { 24 EditFlags 25 All bool `flag:"a,all" help:"apply all fixes, not just preferred fixes"` 26 27 app *Application 28 } 29 30 func (s *suggestedFix) Name() string { return "fix" } 31 func (s *suggestedFix) Parent() string { return s.app.Name() } 32 func (s *suggestedFix) Usage() string { return "[fix-flags] <filename>" } 33 func (s *suggestedFix) ShortHelp() string { return "apply suggested fixes" } 34 func (s *suggestedFix) DetailedHelp(f *flag.FlagSet) { 35 fmt.Fprintf(f.Output(), ` 36 Example: apply fixes to this file, rewriting it: 37 38 $ gopls fix -a -w internal/cmd/check.go 39 40 The -a (-all) flag causes all fixes, not just preferred ones, to be 41 applied, but since no fixes are currently preferred, this flag is 42 essentially mandatory. 43 44 Arguments after the filename are interpreted as LSP CodeAction kinds 45 to be applied; the default set is {"quickfix"}, but valid kinds include: 46 47 quickfix 48 refactor 49 refactor.extract 50 refactor.inline 51 refactor.rewrite 52 source.organizeImports 53 source.fixAll 54 55 CodeAction kinds are hierarchical, so "refactor" includes 56 "refactor.inline". There is currently no way to enable or even 57 enumerate all kinds. 58 59 Example: apply any "refactor.rewrite" fixes at the specific byte 60 offset within this file: 61 62 $ gopls fix -a internal/cmd/check.go:#43 refactor.rewrite 63 64 fix-flags: 65 `) 66 printFlagDefaults(f) 67 } 68 69 // Run performs diagnostic checks on the file specified and either; 70 // - if -w is specified, updates the file in place; 71 // - if -d is specified, prints out unified diffs of the changes; or 72 // - otherwise, prints the new versions to stdout. 73 func (s *suggestedFix) Run(ctx context.Context, args ...string) error { 74 if len(args) < 1 { 75 return tool.CommandLineErrorf("fix expects at least 1 argument") 76 } 77 s.app.editFlags = &s.EditFlags 78 conn, err := s.app.connect(ctx, nil) 79 if err != nil { 80 return err 81 } 82 defer conn.terminate(ctx) 83 84 from := parseSpan(args[0]) 85 uri := from.URI() 86 file, err := conn.openFile(ctx, uri) 87 if err != nil { 88 return err 89 } 90 rng, err := file.spanRange(from) 91 if err != nil { 92 return err 93 } 94 95 // Get diagnostics. 96 if err := conn.diagnoseFiles(ctx, []protocol.DocumentURI{uri}); err != nil { 97 return err 98 } 99 diagnostics := []protocol.Diagnostic{} // LSP wants non-nil slice 100 conn.client.filesMu.Lock() 101 diagnostics = append(diagnostics, file.diagnostics...) 102 conn.client.filesMu.Unlock() 103 104 // Request code actions 105 codeActionKinds := []protocol.CodeActionKind{protocol.QuickFix} 106 if len(args) > 1 { 107 codeActionKinds = []protocol.CodeActionKind{} 108 for _, k := range args[1:] { 109 codeActionKinds = append(codeActionKinds, protocol.CodeActionKind(k)) 110 } 111 } 112 p := protocol.CodeActionParams{ 113 TextDocument: protocol.TextDocumentIdentifier{ 114 URI: uri, 115 }, 116 Context: protocol.CodeActionContext{ 117 Only: codeActionKinds, 118 Diagnostics: diagnostics, 119 }, 120 Range: rng, 121 } 122 actions, err := conn.CodeAction(ctx, &p) 123 if err != nil { 124 return fmt.Errorf("%v: %v", from, err) 125 } 126 127 // Gather edits from matching code actions. 128 var edits []protocol.TextEdit 129 for _, a := range actions { 130 // Without -all, apply only "preferred" fixes. 131 if !a.IsPreferred && !s.All { 132 continue 133 } 134 135 // Execute any command. 136 // This may cause the server to make 137 // an ApplyEdit downcall to the client. 138 if a.Command != nil { 139 if _, err := conn.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{ 140 Command: a.Command.Command, 141 Arguments: a.Command.Arguments, 142 }); err != nil { 143 return err 144 } 145 // The specification says that commands should 146 // be executed _after_ edits are applied, not 147 // instead of them, but we don't want to 148 // duplicate edits. 149 continue 150 } 151 152 // If the provided span has a position (not just offsets), 153 // and the action has diagnostics, the action must have a 154 // diagnostic with the same range as it. 155 if from.HasPosition() && len(a.Diagnostics) > 0 && 156 !slices.ContainsFunc(a.Diagnostics, func(diag protocol.Diagnostic) bool { 157 return diag.Range.Start == rng.Start 158 }) { 159 continue 160 } 161 162 // Partially apply CodeAction.Edit, a WorkspaceEdit. 163 // (See also conn.Client.applyWorkspaceEdit(a.Edit)). 164 for _, c := range a.Edit.DocumentChanges { 165 tde := c.TextDocumentEdit 166 if tde != nil && tde.TextDocument.URI == uri { 167 edits = append(edits, protocol.AsTextEdits(tde.Edits)...) 168 } 169 } 170 } 171 172 return applyTextEdits(file.mapper, edits, s.app.editFlags) 173 }