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
+}