func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { … } // canExtractVariable reports whether the code in the given range can be // extracted to a variable. func canExtractVariable(info *types.Info, file *ast.File, start, end token.Pos) (ast.Expr, []ast.Node, bool, error) { … } // Calculate indentation for insertion. // When inserting lines of code, we must ensure that the lines have consistent // formatting (i.e. the proper indentation). To do so, we observe the indentation on the // line of code on which the insertion occurs. func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.Node) (string, error) { … } // generateAvailableName adjusts the new function name until there are no collisions in scope. // Possible collisions include other function and variable names. Returns the next index to check for prefix. func generateAvailableName(pos token.Pos, path []ast.Node, pkg *types.Package, info *types.Info, prefix string, idx int) (string, int) { … } // generateNameOutsideOfRange is like generateAvailableName, but ignores names // declared between start and end for the purposes of detecting conflicts. // // This is used for function extraction, where [start, end) will be extracted // to a new scope. func generateNameOutsideOfRange(start, end token.Pos, path []ast.Node, pkg *types.Package, info *types.Info, prefix string, idx int) (string, int) { … } func generateName(idx int, prefix string, hasCollision func(string) bool) (string, int) { … } type returnVariable … // extractMethod refactors the selected block of code into a new method. func extractMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { … } // extractFunction refactors the selected block of code into a new function. func extractFunction(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { … } // extractFunctionMethod refactors the selected block of code into a new function/method. // It also replaces the selected block of code with a call to the extracted // function. First, we manually adjust the selection range. We remove trailing // and leading whitespace characters to ensure the range is precisely bounded // by AST nodes. Next, we determine the variables that will be the parameters // and return values of the extracted function/method. Lastly, we construct the call // of the function/method and insert this call as well as the extracted function/method into // their proper locations. func extractFunctionMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*token.FileSet, *analysis.SuggestedFix, error) { … } // isSelector reports if e is the selector expr <x>, <sel>. It works for pointer and non-pointer selector expressions. func isSelector(e ast.Expr, x, sel string) bool { … } // reorderParams reorders the given parameters in-place to follow common Go conventions. func reorderParams(params []ast.Expr, paramTypes []*ast.Field) { … } func moveParamToFrontIfFound(params []ast.Expr, paramTypes []*ast.Field, x, sel string) { … } // adjustRangeForCommentsAndWhiteSpace adjusts the given range to exclude unnecessary leading or // trailing whitespace characters from selection as well as leading or trailing comments. // In the following example, each line of the if statement is indented once. There are also two // extra spaces after the sclosing bracket before the line break and a comment. // // \tif (true) { // \t _ = 1 // \t} // hello \n // // By default, a valid range begins at 'if' and ends at the first whitespace character // after the '}'. But, users are likely to highlight full lines rather than adjusting // their cursors for whitespace. To support this use case, we must manually adjust the // ranges to match the correct AST node. In this particular example, we would adjust // rng.Start forward to the start of 'if' and rng.End backward to after '}'. func adjustRangeForCommentsAndWhiteSpace(tok *token.File, start, end token.Pos, content []byte, file *ast.File) (token.Pos, token.Pos, error) { … } // isGoWhiteSpace returns true if b is a considered white space in // Go as defined by scanner.GoWhitespace. func isGoWhiteSpace(b byte) bool { … } // findParent finds the parent AST node of the given target node, if the target is a // descendant of the starting node. func findParent(start ast.Node, target ast.Node) ast.Node { … } type variable … // collectFreeVars maps each identifier in the given range to whether it is "free." // Given a range, a variable in that range is defined as "free" if it is declared // outside of the range and neither at the file scope nor package scope. These free // variables will be used as arguments in the extracted function. It also returns a // list of identifiers that may need to be returned by the extracted function. // Some of the code in this function has been adapted from tools/cmd/guru/freevars.go. func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, start, end token.Pos, node ast.Node) ([]*variable, error) { … } // referencesObj checks whether the given object appears in the given expression. func referencesObj(info *types.Info, expr ast.Expr, obj types.Object) bool { … } type fnExtractParams … // canExtractFunction reports whether the code in the given range can be // extracted to a function. func canExtractFunction(tok *token.File, start, end token.Pos, src []byte, file *ast.File) (*fnExtractParams, bool, bool, error) { … } // objUsed checks if the object is used within the range. It returns the first // occurrence of the object in the range, if it exists. func objUsed(info *types.Info, start, end token.Pos, obj types.Object) (bool, *ast.Ident) { … } // varOverridden traverses the given AST node until we find the given identifier. Then, we // examine the occurrence of the given identifier and check for (1) whether the identifier // is being redefined. If the identifier is free, we also check for (2) whether the identifier // is being reassigned. We will not include an identifier in the return statement of the // extracted function if it meets one of the above conditions. func varOverridden(info *types.Info, firstUse *ast.Ident, obj types.Object, isFree bool, node ast.Node) bool { … } // parseStmts parses the specified source (a list of statements) and // returns them as a BlockStmt along with any associated comments. func parseStmts(fset *token.FileSet, src []byte) (*ast.BlockStmt, []*ast.CommentGroup, error) { … } // generateReturnInfo generates the information we need to adjust the return statements and // signature of the extracted function. We prepare names, signatures, and "zero values" that // represent the new variables. We also use this information to construct the if statement that // is inserted below the call to the extracted function. func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.Node, file *ast.File, info *types.Info, start, end token.Pos, hasNonNestedReturns bool) ([]*returnVariable, *ast.IfStmt, error) { … } type objKey … var conventionalVarNames … // varNameForTypeName chooses a "good" name for a variable with the given type, // if possible. Otherwise, it returns "", false. // // For special types, it uses known conventional names. func varNameForType(t types.Type) (string, bool) { … } // adjustReturnStatements adds "zero values" of the given types to each return statement // in the given AST node. func adjustReturnStatements(returnTypes []*ast.Field, seenVars map[types.Object]ast.Expr, file *ast.File, pkg *types.Package, extractedBlock *ast.BlockStmt) error { … } // generateFuncCall constructs a call expression for the extracted function, described by the // given parameters and return variables. func generateFuncCall(hasNonNestedReturn, hasReturnVals bool, params, returns []ast.Expr, name string, token token.Token, selector string) ast.Node { … } // initializeVars creates variable declarations, if needed. // Our preference is to replace the selected block with an "x, y, z := fn()" style // assignment statement. We can use this style when all of the variables in the // extracted function's return statement are either not defined prior to the extracted block // or can be safely redefined. However, for example, if z is already defined // in a different scope, we replace the selected block with: // // var x int // var y string // x, y, z = fn() func initializeVars(uninitialized []types.Object, retVars []*returnVariable, seenUninitialized map[types.Object]struct{ … } // getNames returns the names from the given list of returnVariable. func getNames(retVars []*returnVariable) []ast.Expr { … } // getZeroVals returns the "zero values" from the given list of returnVariable. func getZeroVals(retVars []*returnVariable) []ast.Expr { … } // getDecls returns the declarations from the given list of returnVariable. func getDecls(retVars []*returnVariable) []*ast.Field { … }