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 <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/source/inline.go b/gopls/internal/lsp/source/inline.go
new file mode 100644
index 0000000..6a8d57d
--- /dev/null
+++ b/gopls/internal/lsp/source/inline.go
@@ -0,0 +1,113 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package source
+
+// This file defines the refactor.inline code action.
+
+import (
+ "context"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/safetoken"
+ "golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/internal/diff"
+ "golang.org/x/tools/internal/refactor/inline"
+)
+
+// EnclosingStaticCall returns the innermost function call enclosing
+// the selected range, along with the callee.
+func EnclosingStaticCall(pkg Package, pgf *ParsedGoFile, rng protocol.Range) (*ast.CallExpr, *types.Func, error) {
+ start, end, err := pgf.RangePos(rng)
+ if err != nil {
+ return nil, nil, err
+ }
+ path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
+
+ var call *ast.CallExpr
+loop:
+ for _, n := range path {
+ switch n := n.(type) {
+ case *ast.FuncLit:
+ break loop
+ case *ast.CallExpr:
+ call = n
+ break loop
+ }
+ }
+ if call == nil {
+ return nil, nil, fmt.Errorf("no enclosing call")
+ }
+ if safetoken.Line(pgf.Tok, call.Lparen) != safetoken.Line(pgf.Tok, start) {
+ return nil, nil, fmt.Errorf("enclosing call is not on this line")
+ }
+ fn := typeutil.StaticCallee(pkg.GetTypesInfo(), call)
+ if fn == nil {
+ return nil, nil, fmt.Errorf("not a static call to a Go function")
+ }
+ return call, fn, nil
+}
+
+func inlineCall(ctx context.Context, snapshot Snapshot, fh FileHandle, rng protocol.Range) (*token.FileSet, *analysis.SuggestedFix, error) {
+ // Find enclosing static call.
+ callerPkg, callerPGF, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+ if err != nil {
+ return nil, nil, err
+ }
+ call, fn, err := EnclosingStaticCall(callerPkg, callerPGF, rng)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Locate callee by file/line and analyze it.
+ calleePosn := safetoken.StartPosition(callerPkg.FileSet(), fn.Pos())
+ calleePkg, calleePGF, err := NarrowestPackageForFile(ctx, snapshot, span.URIFromPath(calleePosn.Filename))
+ if err != nil {
+ return nil, nil, err
+ }
+ var calleeDecl *ast.FuncDecl
+ for _, decl := range calleePGF.File.Decls {
+ if decl, ok := decl.(*ast.FuncDecl); ok {
+ posn := safetoken.StartPosition(calleePkg.FileSet(), decl.Name.Pos())
+ if posn.Line == calleePosn.Line && posn.Column == calleePosn.Column {
+ calleeDecl = decl
+ break
+ }
+ }
+ }
+ if calleeDecl == nil {
+ return nil, nil, fmt.Errorf("can't find callee")
+ }
+ callee, err := inline.AnalyzeCallee(calleePkg.FileSet(), calleePkg.GetTypes(), calleePkg.GetTypesInfo(), calleeDecl, calleePGF.Src)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Inline the call.
+ caller := &inline.Caller{
+ Fset: callerPkg.FileSet(),
+ Types: callerPkg.GetTypes(),
+ Info: callerPkg.GetTypesInfo(),
+ File: callerPGF.File,
+ Call: call,
+ Content: callerPGF.Src,
+ }
+ got, err := inline.Inline(caller, callee)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Suggest the fix.
+ return callerPkg.FileSet(), &analysis.SuggestedFix{
+ Message: fmt.Sprintf("inline call of %v", callee),
+ TextEdits: diffToTextEdits(callerPGF.Tok, diff.Bytes(callerPGF.Src, got)),
+ }, nil
+}