@@ -139,11 +139,17 @@ func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.
139
139
// Possible collisions include other function and variable names. Returns the next index to check for prefix.
140
140
func generateAvailableIdentifier (pos token.Pos , file * ast.File , path []ast.Node , info * types.Info , prefix string , idx int ) (string , int ) {
141
141
scopes := CollectScopes (info , path , pos )
142
+ return generateIdentifier (idx , prefix , func (name string ) bool {
143
+ return file .Scope .Lookup (name ) != nil || ! isValidName (name , scopes )
144
+ })
145
+ }
146
+
147
+ func generateIdentifier (idx int , prefix string , hasCollision func (string ) bool ) (string , int ) {
142
148
name := prefix
143
149
if idx != 0 {
144
150
name += fmt .Sprintf ("%d" , idx )
145
151
}
146
- for file . Scope . Lookup (name ) != nil || ! isValidName ( name , scopes ) {
152
+ for hasCollision (name ) {
147
153
idx ++
148
154
name = fmt .Sprintf ("%v%d" , prefix , idx )
149
155
}
@@ -177,28 +183,42 @@ type returnVariable struct {
177
183
zeroVal ast.Expr
178
184
}
179
185
186
+ // extractMethod refactors the selected block of code into a new method.
187
+ func extractMethod (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , pkg * types.Package , info * types.Info ) (* analysis.SuggestedFix , error ) {
188
+ return extractFunctionMethod (fset , rng , src , file , pkg , info , true )
189
+ }
190
+
180
191
// extractFunction refactors the selected block of code into a new function.
192
+ func extractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , pkg * types.Package , info * types.Info ) (* analysis.SuggestedFix , error ) {
193
+ return extractFunctionMethod (fset , rng , src , file , pkg , info , false )
194
+ }
195
+
196
+ // extractFunctionMethod refactors the selected block of code into a new function/method.
181
197
// It also replaces the selected block of code with a call to the extracted
182
198
// function. First, we manually adjust the selection range. We remove trailing
183
199
// and leading whitespace characters to ensure the range is precisely bounded
184
200
// by AST nodes. Next, we determine the variables that will be the parameters
185
- // and return values of the extracted function. Lastly, we construct the call
186
- // of the function and insert this call as well as the extracted function into
201
+ // and return values of the extracted function/method . Lastly, we construct the call
202
+ // of the function/method and insert this call as well as the extracted function/method into
187
203
// their proper locations.
188
- func extractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , pkg * types.Package , info * types.Info ) (* analysis.SuggestedFix , error ) {
189
- p , ok , err := CanExtractFunction (fset , rng , src , file )
190
- if ! ok {
191
- return nil , fmt .Errorf ("extractFunction: cannot extract %s: %v" ,
204
+ func extractFunctionMethod (fset * token.FileSet , rng span.Range , src []byte , file * ast.File , pkg * types.Package , info * types.Info , isMethod bool ) (* analysis.SuggestedFix , error ) {
205
+ errorPrefix := "extractFunction"
206
+ if isMethod {
207
+ errorPrefix = "extractMethod"
208
+ }
209
+ p , ok , methodOk , err := CanExtractFunction (fset , rng , src , file )
210
+ if (! ok && ! isMethod ) || (! methodOk && isMethod ) {
211
+ return nil , fmt .Errorf ("%s: cannot extract %s: %v" , errorPrefix ,
192
212
fset .Position (rng .Start ), err )
193
213
}
194
214
tok , path , rng , outer , start := p .tok , p .path , p .rng , p .outer , p .start
195
215
fileScope := info .Scopes [file ]
196
216
if fileScope == nil {
197
- return nil , fmt .Errorf ("extractFunction : file scope is empty" )
217
+ return nil , fmt .Errorf ("%s : file scope is empty" , errorPrefix )
198
218
}
199
219
pkgScope := fileScope .Parent ()
200
220
if pkgScope == nil {
201
- return nil , fmt .Errorf ("extractFunction : package scope is empty" )
221
+ return nil , fmt .Errorf ("%s : package scope is empty" , errorPrefix )
202
222
}
203
223
204
224
// A return statement is non-nested if its parent node is equal to the parent node
@@ -235,6 +255,25 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
235
255
return nil , err
236
256
}
237
257
258
+ var (
259
+ receiverUsed bool
260
+ receiver * ast.Field
261
+ receiverName string
262
+ receiverObj types.Object
263
+ )
264
+ if isMethod {
265
+ if outer == nil || outer .Recv == nil || len (outer .Recv .List ) == 0 {
266
+ return nil , fmt .Errorf ("%s: cannot extract need method receiver" , errorPrefix )
267
+ }
268
+ receiver = outer .Recv .List [0 ]
269
+ if len (receiver .Names ) == 0 || receiver .Names [0 ] == nil {
270
+ return nil , fmt .Errorf ("%s: cannot extract need method receiver name" , errorPrefix )
271
+ }
272
+ recvName := receiver .Names [0 ]
273
+ receiverName = recvName .Name
274
+ receiverObj = info .ObjectOf (recvName )
275
+ }
276
+
238
277
var (
239
278
params , returns []ast.Expr // used when calling the extracted function
240
279
paramTypes , returnTypes []* ast.Field // used in the signature of the extracted function
@@ -308,6 +347,11 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
308
347
// extracted function. (1) it must be free (isFree), and (2) its first
309
348
// use within the selection cannot be its own definition (isDefined).
310
349
if v .free && ! v .defined {
350
+ // Skip the selector for a method.
351
+ if isMethod && v .obj == receiverObj {
352
+ receiverUsed = true
353
+ continue
354
+ }
311
355
params = append (params , identifier )
312
356
paramTypes = append (paramTypes , & ast.Field {
313
357
Names : []* ast.Ident {identifier },
@@ -471,9 +515,17 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
471
515
if canDefine {
472
516
sym = token .DEFINE
473
517
}
474
- funName , _ := generateAvailableIdentifier (rng .Start , file , path , info , "newFunction" , 0 )
518
+ var name , funName string
519
+ if isMethod {
520
+ name = "newMethod"
521
+ // TODO(suzmue): generate a name that does not conflict for "newMethod".
522
+ funName = name
523
+ } else {
524
+ name = "newFunction"
525
+ funName , _ = generateAvailableIdentifier (rng .Start , file , path , info , name , 0 )
526
+ }
475
527
extractedFunCall := generateFuncCall (hasNonNestedReturn , hasReturnValues , params ,
476
- append (returns , getNames (retVars )... ), funName , sym )
528
+ append (returns , getNames (retVars )... ), funName , sym , receiverName )
477
529
478
530
// Build the extracted function.
479
531
newFunc := & ast.FuncDecl {
@@ -484,6 +536,18 @@ func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.
484
536
},
485
537
Body : extractedBlock ,
486
538
}
539
+ if isMethod {
540
+ var names []* ast.Ident
541
+ if receiverUsed {
542
+ names = append (names , ast .NewIdent (receiverName ))
543
+ }
544
+ newFunc .Recv = & ast.FieldList {
545
+ List : []* ast.Field {{
546
+ Names : names ,
547
+ Type : receiver .Type ,
548
+ }},
549
+ }
550
+ }
487
551
488
552
// Create variable declarations for any identifiers that need to be initialized prior to
489
553
// calling the extracted function. We do not manually initialize variables if every return
@@ -844,24 +908,24 @@ type fnExtractParams struct {
844
908
845
909
// CanExtractFunction reports whether the code in the given range can be
846
910
// extracted to a function.
847
- func CanExtractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File ) (* fnExtractParams , bool , error ) {
911
+ func CanExtractFunction (fset * token.FileSet , rng span.Range , src []byte , file * ast.File ) (* fnExtractParams , bool , bool , error ) {
848
912
if rng .Start == rng .End {
849
- return nil , false , fmt .Errorf ("start and end are equal" )
913
+ return nil , false , false , fmt .Errorf ("start and end are equal" )
850
914
}
851
915
tok := fset .File (file .Pos ())
852
916
if tok == nil {
853
- return nil , false , fmt .Errorf ("no file for pos %v" , fset .Position (file .Pos ()))
917
+ return nil , false , false , fmt .Errorf ("no file for pos %v" , fset .Position (file .Pos ()))
854
918
}
855
919
rng = adjustRangeForWhitespace (rng , tok , src )
856
920
path , _ := astutil .PathEnclosingInterval (file , rng .Start , rng .End )
857
921
if len (path ) == 0 {
858
- return nil , false , fmt .Errorf ("no path enclosing interval" )
922
+ return nil , false , false , fmt .Errorf ("no path enclosing interval" )
859
923
}
860
924
// Node that encloses the selection must be a statement.
861
925
// TODO: Support function extraction for an expression.
862
926
_ , ok := path [0 ].(ast.Stmt )
863
927
if ! ok {
864
- return nil , false , fmt .Errorf ("node is not a statement" )
928
+ return nil , false , false , fmt .Errorf ("node is not a statement" )
865
929
}
866
930
867
931
// Find the function declaration that encloses the selection.
@@ -873,7 +937,7 @@ func CanExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *a
873
937
}
874
938
}
875
939
if outer == nil {
876
- return nil , false , fmt .Errorf ("no enclosing function" )
940
+ return nil , false , false , fmt .Errorf ("no enclosing function" )
877
941
}
878
942
879
943
// Find the nodes at the start and end of the selection.
@@ -893,15 +957,15 @@ func CanExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *a
893
957
return n .Pos () <= rng .End
894
958
})
895
959
if start == nil || end == nil {
896
- return nil , false , fmt .Errorf ("range does not map to AST nodes" )
960
+ return nil , false , false , fmt .Errorf ("range does not map to AST nodes" )
897
961
}
898
962
return & fnExtractParams {
899
963
tok : tok ,
900
964
path : path ,
901
965
rng : rng ,
902
966
outer : outer ,
903
967
start : start ,
904
- }, true , nil
968
+ }, true , outer . Recv != nil , nil
905
969
}
906
970
907
971
// objUsed checks if the object is used within the range. It returns the first
@@ -1089,13 +1153,22 @@ func adjustReturnStatements(returnTypes []*ast.Field, seenVars map[types.Object]
1089
1153
1090
1154
// generateFuncCall constructs a call expression for the extracted function, described by the
1091
1155
// given parameters and return variables.
1092
- func generateFuncCall (hasNonNestedReturn , hasReturnVals bool , params , returns []ast.Expr , name string , token token.Token ) ast.Node {
1156
+ func generateFuncCall (hasNonNestedReturn , hasReturnVals bool , params , returns []ast.Expr , name string , token token.Token , selector string ) ast.Node {
1093
1157
var replace ast.Node
1094
- if hasReturnVals {
1095
- callExpr := & ast.CallExpr {
1096
- Fun : ast .NewIdent (name ),
1158
+ callExpr := & ast.CallExpr {
1159
+ Fun : ast .NewIdent (name ),
1160
+ Args : params ,
1161
+ }
1162
+ if selector != "" {
1163
+ callExpr = & ast.CallExpr {
1164
+ Fun : & ast.SelectorExpr {
1165
+ X : ast .NewIdent (selector ),
1166
+ Sel : ast .NewIdent (name ),
1167
+ },
1097
1168
Args : params ,
1098
1169
}
1170
+ }
1171
+ if hasReturnVals {
1099
1172
if hasNonNestedReturn {
1100
1173
// Create a return statement that returns the result of the function call.
1101
1174
replace = & ast.ReturnStmt {
@@ -1111,10 +1184,7 @@ func generateFuncCall(hasNonNestedReturn, hasReturnVals bool, params, returns []
1111
1184
}
1112
1185
}
1113
1186
} else {
1114
- replace = & ast.CallExpr {
1115
- Fun : ast .NewIdent (name ),
1116
- Args : params ,
1117
- }
1187
+ replace = callExpr
1118
1188
}
1119
1189
return replace
1120
1190
}
0 commit comments