blob: 6a8d57d412a0d238c22d439e80e06e015ff00091 [file] [log] [blame]
Alan Donovan5e8d21a2023-08-28 14:56:34 -04001// 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
5package source
6
7// This file defines the refactor.inline code action.
8
9import (
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.
28func 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
36loop:
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
59func 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}