Skip to content

Commit 1c1ec42

Browse files
committed
internal/lsp/source: move initial extract function logic to lsp/command
In the previous implementation, the initial verification in lsp/command for whether extract function was relavant to the given range did not contain much of the initial logic for extract function. This meant that "canExtractFunction" produced many false positives (i.e. the lightbulb would appear when it should not have in VSCode). This CL moves more of the verification process from "extractFunction" (lsp/source) to "canExtractFunction" (lsp/command). Change-Id: If2683dc9ac3f4bfa8c3444418cf88edd8cbe73e6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/245398 Run-TryBot: Josh Baum <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent df70183 commit 1c1ec42

File tree

2 files changed

+57
-59
lines changed

2 files changed

+57
-59
lines changed

internal/lsp/source/command.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ var (
111111
Name: "extract_variable",
112112
Title: "Extract to variable",
113113
suggestedFixFn: extractVariable,
114-
appliesFn: func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) bool {
115-
_, _, ok, _ := canExtractVariable(fset, rng, src, file, pkg, info)
114+
appliesFn: func(_ *token.FileSet, rng span.Range, _ []byte, file *ast.File, _ *types.Package, _ *types.Info) bool {
115+
_, _, ok, _ := canExtractVariable(rng, file)
116116
return ok
117117
},
118118
}
@@ -122,7 +122,10 @@ var (
122122
Name: "extract_function",
123123
Title: "Extract to function",
124124
suggestedFixFn: extractFunction,
125-
appliesFn: canExtractFunction,
125+
appliesFn: func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) bool {
126+
_, _, _, _, ok, _ := canExtractFunction(fset, rng, src, file, info)
127+
return ok
128+
},
126129
}
127130
)
128131

internal/lsp/source/extract.go

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
)
2323

2424
func extractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
25-
expr, path, ok, err := canExtractVariable(fset, rng, src, file, pkg, info)
25+
expr, path, ok, err := canExtractVariable(rng, file)
2626
if !ok {
2727
return nil, fmt.Errorf("extractVariable: cannot extract %s: %v", fset.Position(rng.Start), err)
2828
}
@@ -78,7 +78,7 @@ func extractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.
7878

7979
// canExtractVariable reports whether the code in the given range can be
8080
// extracted to a variable.
81-
func canExtractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (ast.Expr, []ast.Node, bool, error) {
81+
func canExtractVariable(rng span.Range, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
8282
if rng.Start == rng.End {
8383
return nil, nil, false, fmt.Errorf("start and end are equal")
8484
}
@@ -156,19 +156,10 @@ type returnVariable struct {
156156
// of the function and insert this call as well as the extracted function into
157157
// their proper locations.
158158
func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
159-
tok := fset.File(file.Pos())
160-
if tok == nil {
161-
return nil, fmt.Errorf("extractFunction: no token.File")
162-
}
163-
rng = adjustRangeForWhitespace(rng, tok, src)
164-
path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
165-
if len(path) == 0 {
166-
return nil, fmt.Errorf("extractFunction: no path enclosing interval")
167-
}
168-
// Node that encloses the selection must be a statement.
169-
// TODO: Support function extraction for an expression.
170-
if _, ok := path[0].(ast.Stmt); !ok {
171-
return nil, fmt.Errorf("extractFunction: ast.Node is not a statement")
159+
tok, path, outer, start, ok, err := canExtractFunction(fset, rng, src, file, info)
160+
if !ok {
161+
return nil, fmt.Errorf("extractFunction: cannot extract %s: %v",
162+
fset.Position(rng.Start), err)
172163
}
173164
fileScope := info.Scopes[file]
174165
if fileScope == nil {
@@ -178,37 +169,6 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
178169
if pkgScope == nil {
179170
return nil, fmt.Errorf("extractFunction: package scope is empty")
180171
}
181-
// Find the function declaration that encloses the selection.
182-
var outer *ast.FuncDecl
183-
for _, p := range path {
184-
if p, ok := p.(*ast.FuncDecl); ok {
185-
outer = p
186-
break
187-
}
188-
}
189-
if outer == nil {
190-
return nil, fmt.Errorf("extractFunction: no enclosing function")
191-
}
192-
193-
// Find the nodes at the start and end of the selection.
194-
var start, end ast.Node
195-
ast.Inspect(outer, func(n ast.Node) bool {
196-
if n == nil {
197-
return true
198-
}
199-
// Do not override 'start' with a node that begins at the same location but is
200-
// nested further from 'outer'.
201-
if start == nil && n.Pos() == rng.Start && n.End() <= rng.End {
202-
start = n
203-
}
204-
if end == nil && n.End() == rng.End && n.Pos() >= rng.Start {
205-
end = n
206-
}
207-
return n.Pos() <= rng.End
208-
})
209-
if start == nil || end == nil {
210-
return nil, nil
211-
}
212172

213173
// TODO: Support non-nested return statements.
214174
// A return statement is non-nested if its parent node is equal to the parent node
@@ -237,7 +197,7 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
237197
return true
238198
})
239199
if hasNonNestedReturn {
240-
return nil, fmt.Errorf("extractFunction: selected bloc kcontains non-nested return")
200+
return nil, fmt.Errorf("extractFunction: selected block contains non-nested return")
241201
}
242202
containsReturnStatement := len(retStmts) > 0
243203

@@ -673,25 +633,60 @@ func collectFreeVars(info *types.Info, file *ast.File, fileScope *types.Scope,
673633
return free, vars, assigned
674634
}
675635

676-
// canExtractFunction reports whether the code in the given range can be
677-
// extracted to a function.
678-
// TODO(rstambler): De-duplicate the logic between extractFunction and
679-
// canExtractFunction.
680-
func canExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) bool {
636+
// canExtractFunction reports whether the code in the given range can be extracted to a function.
637+
func canExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, info *types.Info) (*token.File, []ast.Node, *ast.FuncDecl, ast.Node, bool, error) {
681638
if rng.Start == rng.End {
682-
return false
639+
return nil, nil, nil, nil, false, fmt.Errorf("start and end are equal")
683640
}
684641
tok := fset.File(file.Pos())
685642
if tok == nil {
686-
return false
643+
return nil, nil, nil, nil, false,
644+
fmt.Errorf("no file for pos %v", fset.Position(file.Pos()))
687645
}
688646
rng = adjustRangeForWhitespace(rng, tok, src)
689647
path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
690648
if len(path) == 0 {
691-
return false
649+
return nil, nil, nil, nil, false, fmt.Errorf("no path enclosing interval")
692650
}
651+
// Node that encloses the selection must be a statement.
652+
// TODO: Support function extraction for an expression.
693653
_, ok := path[0].(ast.Stmt)
694-
return ok
654+
if !ok {
655+
return nil, nil, nil, nil, false, fmt.Errorf("node is not a statement")
656+
}
657+
658+
// Find the function declaration that encloses the selection.
659+
var outer *ast.FuncDecl
660+
for _, p := range path {
661+
if p, ok := p.(*ast.FuncDecl); ok {
662+
outer = p
663+
break
664+
}
665+
}
666+
if outer == nil {
667+
return nil, nil, nil, nil, false, fmt.Errorf("no enclosing function")
668+
}
669+
670+
// Find the nodes at the start and end of the selection.
671+
var start, end ast.Node
672+
ast.Inspect(outer, func(n ast.Node) bool {
673+
if n == nil {
674+
return true
675+
}
676+
// Do not override 'start' with a node that begins at the same location but is
677+
// nested further from 'outer'.
678+
if start == nil && n.Pos() == rng.Start && n.End() <= rng.End {
679+
start = n
680+
}
681+
if end == nil && n.End() == rng.End && n.Pos() >= rng.Start {
682+
end = n
683+
}
684+
return n.Pos() <= rng.End
685+
})
686+
if start == nil || end == nil {
687+
return nil, nil, nil, nil, false, fmt.Errorf("range does not map to AST nodes")
688+
}
689+
return tok, path, outer, start, true, nil
695690
}
696691

697692
// objUsed checks if the object is used between the given positions.

0 commit comments

Comments
 (0)