Skip to content

Commit 5e8d21a

Browse files
committed
gopls/internal/lsp/source: implement refactor.inline code action
This change uses the new inlining package to implement the refactor.inline code action. Fixes golang/go#59243 Change-Id: I9455e1989c6e077849c72924e336c0c738c5cebc Reviewed-on: https://go-review.googlesource.com/c/tools/+/523696 Reviewed-by: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent edda81f commit 5e8d21a

File tree

9 files changed

+219
-11
lines changed

9 files changed

+219
-11
lines changed

gopls/internal/lsp/cmd/cmd.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,10 @@ func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfigur
524524
}
525525

526526
func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) {
527-
return &protocol.ApplyWorkspaceEditResult{Applied: false, FailureReason: "not implemented"}, nil
527+
return &protocol.ApplyWorkspaceEditResult{
528+
Applied: false,
529+
FailureReason: "the gopls command-line client does not apply edits",
530+
}, nil
528531
}
529532

530533
func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {

gopls/internal/lsp/cmd/suggested_fix.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,30 @@ func (s *suggestedFix) Run(ctx context.Context, args ...string) error {
101101
// Gather edits from matching code actions.
102102
var edits []protocol.TextEdit
103103
for _, a := range actions {
104-
if a.Command != nil {
105-
return fmt.Errorf("ExecuteCommand is not yet supported on the command line (action: %v)", a.Title)
106-
}
104+
// Without -all, apply only "preferred" fixes.
107105
if !a.IsPreferred && !s.All {
108106
continue
109107
}
108+
109+
// Execute any command.
110+
// This may cause the server to make
111+
// an ApplyEdit downcall to the client.
112+
if a.Command != nil {
113+
if _, err := conn.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
114+
Command: a.Command.Command,
115+
Arguments: a.Command.Arguments,
116+
}); err != nil {
117+
return err
118+
}
119+
// The specification says that commands should
120+
// be executed _after_ edits are applied, not
121+
// instead of them, but we don't want to
122+
// duplicate edits.
123+
continue
124+
}
125+
126+
// Partially apply CodeAction.Edit, a WorkspaceEdit.
127+
// (See also conn.Client.applyWorkspaceEdit(a.Edit)).
110128
if !from.HasPosition() {
111129
for _, c := range a.Edit.DocumentChanges {
112130
if c.TextDocumentEdit != nil {

gopls/internal/lsp/code_action.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
194194
}
195195

196196
// Code actions requiring type information.
197-
if len(stubMethodsDiagnostics) > 0 || want[protocol.RefactorRewrite] || want[protocol.GoTest] {
197+
if len(stubMethodsDiagnostics) > 0 ||
198+
want[protocol.RefactorRewrite] ||
199+
want[protocol.RefactorInline] ||
200+
want[protocol.GoTest] {
198201
pkg, pgf, err := source.NarrowestPackageForFile(ctx, snapshot, fh.URI())
199202
if err != nil {
200203
return nil, err
@@ -250,6 +253,14 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
250253
actions = append(actions, rewrites...)
251254
}
252255

256+
if want[protocol.RefactorInline] {
257+
rewrites, err := refactorInline(ctx, snapshot, pkg, pgf, fh, params.Range)
258+
if err != nil {
259+
return nil, err
260+
}
261+
actions = append(actions, rewrites...)
262+
}
263+
253264
if want[protocol.GoTest] {
254265
fixes, err := goTest(ctx, snapshot, pkg, pgf, params.Range)
255266
if err != nil {
@@ -499,6 +510,35 @@ func refactorRewrite(ctx context.Context, snapshot source.Snapshot, pkg source.P
499510
return actions, nil
500511
}
501512

513+
// refactorInline returns inline actions available at the specified range.
514+
func refactorInline(ctx context.Context, snapshot source.Snapshot, pkg source.Package, pgf *source.ParsedGoFile, fh source.FileHandle, rng protocol.Range) ([]protocol.CodeAction, error) {
515+
var commands []protocol.Command
516+
517+
// If range is within call expression, offer inline action.
518+
if _, fn, err := source.EnclosingStaticCall(pkg, pgf, rng); err == nil {
519+
cmd, err := command.NewApplyFixCommand(fmt.Sprintf("Inline call to %s", fn.Name()), command.ApplyFixArgs{
520+
URI: protocol.URIFromSpanURI(pgf.URI),
521+
Fix: source.InlineCall,
522+
Range: rng,
523+
})
524+
if err != nil {
525+
return nil, err
526+
}
527+
commands = append(commands, cmd)
528+
}
529+
530+
// Convert commands to actions.
531+
var actions []protocol.CodeAction
532+
for i := range commands {
533+
actions = append(actions, protocol.CodeAction{
534+
Title: commands[i].Title,
535+
Kind: protocol.RefactorInline,
536+
Command: &commands[i],
537+
})
538+
}
539+
return actions, nil
540+
}
541+
502542
func documentChanges(fh source.FileHandle, edits []protocol.TextEdit) []protocol.DocumentChanges {
503543
return []protocol.DocumentChanges{
504544
{

gopls/internal/lsp/source/fix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@ type (
3535
singleFileFixFunc func(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
3636
)
3737

38+
// These strings identify kinds of suggested fix, both in Analyzer.Fix
39+
// and in the ApplyFix subcommand (see ExecuteCommand and ApplyFixArgs.Fix).
3840
const (
3941
FillStruct = "fill_struct"
4042
StubMethods = "stub_methods"
4143
UndeclaredName = "undeclared_name"
4244
ExtractVariable = "extract_variable"
4345
ExtractFunction = "extract_function"
4446
ExtractMethod = "extract_method"
47+
InlineCall = "inline_call"
4548
InvertIfCondition = "invert_if_condition"
4649
AddEmbedImport = "add_embed_import"
4750
)
@@ -51,6 +54,7 @@ var suggestedFixes = map[string]SuggestedFixFunc{
5154
FillStruct: singleFile(fillstruct.SuggestedFix),
5255
UndeclaredName: singleFile(undeclaredname.SuggestedFix),
5356
ExtractVariable: singleFile(extractVariable),
57+
InlineCall: inlineCall,
5458
ExtractFunction: singleFile(extractFunction),
5559
ExtractMethod: singleFile(extractMethod),
5660
InvertIfCondition: singleFile(invertIfCondition),

gopls/internal/lsp/source/inline.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2023 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 source
6+
7+
// This file defines the refactor.inline code action.
8+
9+
import (
10+
"context"
11+
"fmt"
12+
"go/ast"
13+
"go/token"
14+
"go/types"
15+
16+
"golang.org/x/tools/go/analysis"
17+
"golang.org/x/tools/go/ast/astutil"
18+
"golang.org/x/tools/go/types/typeutil"
19+
"golang.org/x/tools/gopls/internal/lsp/protocol"
20+
"golang.org/x/tools/gopls/internal/lsp/safetoken"
21+
"golang.org/x/tools/gopls/internal/span"
22+
"golang.org/x/tools/internal/diff"
23+
"golang.org/x/tools/internal/refactor/inline"
24+
)
25+
26+
// EnclosingStaticCall returns the innermost function call enclosing
27+
// the selected range, along with the callee.
28+
func EnclosingStaticCall(pkg Package, pgf *ParsedGoFile, rng protocol.Range) (*ast.CallExpr, *types.Func, error) {
29+
start, end, err := pgf.RangePos(rng)
30+
if err != nil {
31+
return nil, nil, err
32+
}
33+
path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
34+
35+
var call *ast.CallExpr
36+
loop:
37+
for _, n := range path {
38+
switch n := n.(type) {
39+
case *ast.FuncLit:
40+
break loop
41+
case *ast.CallExpr:
42+
call = n
43+
break loop
44+
}
45+
}
46+
if call == nil {
47+
return nil, nil, fmt.Errorf("no enclosing call")
48+
}
49+
if safetoken.Line(pgf.Tok, call.Lparen) != safetoken.Line(pgf.Tok, start) {
50+
return nil, nil, fmt.Errorf("enclosing call is not on this line")
51+
}
52+
fn := typeutil.StaticCallee(pkg.GetTypesInfo(), call)
53+
if fn == nil {
54+
return nil, nil, fmt.Errorf("not a static call to a Go function")
55+
}
56+
return call, fn, nil
57+
}
58+
59+
func inlineCall(ctx context.Context, snapshot Snapshot, fh FileHandle, rng protocol.Range) (*token.FileSet, *analysis.SuggestedFix, error) {
60+
// Find enclosing static call.
61+
callerPkg, callerPGF, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
62+
if err != nil {
63+
return nil, nil, err
64+
}
65+
call, fn, err := EnclosingStaticCall(callerPkg, callerPGF, rng)
66+
if err != nil {
67+
return nil, nil, err
68+
}
69+
70+
// Locate callee by file/line and analyze it.
71+
calleePosn := safetoken.StartPosition(callerPkg.FileSet(), fn.Pos())
72+
calleePkg, calleePGF, err := NarrowestPackageForFile(ctx, snapshot, span.URIFromPath(calleePosn.Filename))
73+
if err != nil {
74+
return nil, nil, err
75+
}
76+
var calleeDecl *ast.FuncDecl
77+
for _, decl := range calleePGF.File.Decls {
78+
if decl, ok := decl.(*ast.FuncDecl); ok {
79+
posn := safetoken.StartPosition(calleePkg.FileSet(), decl.Name.Pos())
80+
if posn.Line == calleePosn.Line && posn.Column == calleePosn.Column {
81+
calleeDecl = decl
82+
break
83+
}
84+
}
85+
}
86+
if calleeDecl == nil {
87+
return nil, nil, fmt.Errorf("can't find callee")
88+
}
89+
callee, err := inline.AnalyzeCallee(calleePkg.FileSet(), calleePkg.GetTypes(), calleePkg.GetTypesInfo(), calleeDecl, calleePGF.Src)
90+
if err != nil {
91+
return nil, nil, err
92+
}
93+
94+
// Inline the call.
95+
caller := &inline.Caller{
96+
Fset: callerPkg.FileSet(),
97+
Types: callerPkg.GetTypes(),
98+
Info: callerPkg.GetTypesInfo(),
99+
File: callerPGF.File,
100+
Call: call,
101+
Content: callerPGF.Src,
102+
}
103+
got, err := inline.Inline(caller, callee)
104+
if err != nil {
105+
return nil, nil, err
106+
}
107+
108+
// Suggest the fix.
109+
return callerPkg.FileSet(), &analysis.SuggestedFix{
110+
Message: fmt.Sprintf("inline call of %v", callee),
111+
TextEdits: diffToTextEdits(callerPGF.Tok, diff.Bytes(callerPGF.Src, got)),
112+
}, nil
113+
}

gopls/internal/lsp/source/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func DefaultOptions() *Options {
104104
protocol.SourceOrganizeImports: true,
105105
protocol.QuickFix: true,
106106
protocol.RefactorRewrite: true,
107+
protocol.RefactorInline: true,
107108
protocol.RefactorExtract: true,
108109
},
109110
Mod: {

gopls/internal/lsp/source/stub.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"golang.org/x/tools/gopls/internal/lsp/analysis/stubmethods"
2323
"golang.org/x/tools/gopls/internal/lsp/protocol"
2424
"golang.org/x/tools/gopls/internal/lsp/safetoken"
25+
"golang.org/x/tools/internal/diff"
2526
"golang.org/x/tools/internal/tokeninternal"
2627
"golang.org/x/tools/internal/typeparams"
2728
)
@@ -231,15 +232,19 @@ func (%s%s%s) %s%s {
231232

232233
// Report the diff.
233234
diffs := snapshot.View().Options().ComputeEdits(string(input), output.String())
234-
var edits []analysis.TextEdit
235+
return tokeninternal.FileSetFor(declPGF.Tok), // edits use declPGF.Tok
236+
&analysis.SuggestedFix{TextEdits: diffToTextEdits(declPGF.Tok, diffs)},
237+
nil
238+
}
239+
240+
func diffToTextEdits(tok *token.File, diffs []diff.Edit) []analysis.TextEdit {
241+
edits := make([]analysis.TextEdit, 0, len(diffs))
235242
for _, edit := range diffs {
236243
edits = append(edits, analysis.TextEdit{
237-
Pos: declPGF.Tok.Pos(edit.Start),
238-
End: declPGF.Tok.Pos(edit.End),
244+
Pos: tok.Pos(edit.Start),
245+
End: tok.Pos(edit.End),
239246
NewText: []byte(edit.New),
240247
})
241248
}
242-
return tokeninternal.FileSetFor(declPGF.Tok), // edits use declPGF.Tok
243-
&analysis.SuggestedFix{TextEdits: edits},
244-
nil
249+
return edits
245250
}

gopls/internal/lsp/tests/tests.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ func DefaultOptions(o *source.Options) {
230230
protocol.SourceOrganizeImports: true,
231231
protocol.QuickFix: true,
232232
protocol.RefactorRewrite: true,
233+
protocol.RefactorInline: true,
233234
protocol.RefactorExtract: true,
234235
protocol.SourceFixAll: true,
235236
},
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
This is a minimal test of the refactor.inline code action.
2+
3+
-- go.mod --
4+
module testdata/codeaction
5+
go 1.18
6+
7+
-- a/a.go --
8+
package a
9+
10+
func _() {
11+
println(add(1, 2)) //@codeaction("refactor.inline", "add", ")", inline)
12+
}
13+
14+
func add(x, y int) int { return x + y }
15+
16+
-- @inline/a/a.go --
17+
package a
18+
19+
func _() {
20+
println(func(x, y int) int { return x + y }(1, 2)) //@codeaction("refactor.inline", "add", ")", inline)
21+
}
22+
23+
func add(x, y int) int { return x + y }

0 commit comments

Comments
 (0)