diff --git a/gopls/doc/assets/extract-expressions-after.png b/gopls/doc/assets/extract-expressions-after.png new file mode 100644 index 00000000000..a89dbe851b0 Binary files /dev/null and b/gopls/doc/assets/extract-expressions-after.png differ diff --git a/gopls/doc/assets/extract-expressions-before.png b/gopls/doc/assets/extract-expressions-before.png new file mode 100644 index 00000000000..e75d7731871 Binary files /dev/null and b/gopls/doc/assets/extract-expressions-before.png differ diff --git a/gopls/doc/assets/extract-var-after.png b/gopls/doc/assets/extract-var-after.png index db558d6736a..c4dab01b770 100644 Binary files a/gopls/doc/assets/extract-var-after.png and b/gopls/doc/assets/extract-var-after.png differ diff --git a/gopls/doc/assets/extract-var-before.png b/gopls/doc/assets/extract-var-before.png deleted file mode 100644 index 356a242db3c..00000000000 Binary files a/gopls/doc/assets/extract-var-before.png and /dev/null differ diff --git a/gopls/doc/features/transformation.md b/gopls/doc/features/transformation.md index ac1bf5f8333..5cd5f9047fc 100644 --- a/gopls/doc/features/transformation.md +++ b/gopls/doc/features/transformation.md @@ -72,6 +72,7 @@ Gopls supports the following code actions: - [`refactor.extract.method`](#extract) - [`refactor.extract.toNewFile`](#extract.toNewFile) - [`refactor.extract.variable`](#extract) +- [`refactor.extract.variable.all`](#extract) - [`refactor.inline.call`](#refactor.inline.call) - [`refactor.rewrite.changeQuote`](#refactor.rewrite.changeQuote) - [`refactor.rewrite.fillStruct`](#refactor.rewrite.fillStruct) @@ -313,11 +314,18 @@ newly created declaration that contains the selected code: will be a method of the same receiver type. - **`refactor.extract.variable`** replaces an expression by a reference to a new - local variable named `x` initialized by the expression: + local variable named `newVar` initialized by the expression: - ![Before extracting a var](../assets/extract-var-before.png) + ![Before extracting a var](../assets/extract-expressions-before.png) ![After extracting a var](../assets/extract-var-after.png) +- **`refactor.extract.variable.all`** replaces all occurrences of the selected expression +within the function with a reference to a new local variable named `newVar`. +This extracts the expression once and reuses it wherever it appears in the function. + + ![Before extracting all expressions](../assets/extract-expressions-before.png) + ![After extracting all expressions](../assets/extract-expressions-after.png) + If the default name for the new declaration is already in use, gopls generates a fresh name. diff --git a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md index 08fd3451f27..deb5b765c24 100644 --- a/gopls/doc/release/v0.17.0.md +++ b/gopls/doc/release/v0.17.0.md @@ -84,3 +84,8 @@ from the context of the call. The new `yield` analyzer detects mistakes using the `yield` function in a Go 1.23 iterator, such as failure to check its boolean result and break out of a loop. + +## Extract all occurrences of the same expression under selection +When you have multiple instances of the same expression in a function, +you can use this code action to extract it into a variable. +All occurrences of the expression will be replaced with a reference to the new variable. diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go index 3e4f3113f9e..a8a1925c564 100644 --- a/gopls/internal/golang/codeaction.go +++ b/gopls/internal/golang/codeaction.go @@ -27,6 +27,7 @@ import ( "golang.org/x/tools/gopls/internal/protocol" "golang.org/x/tools/gopls/internal/protocol/command" "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/safetoken" "golang.org/x/tools/gopls/internal/util/typesutil" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/imports" @@ -236,6 +237,7 @@ var codeActionProducers = [...]codeActionProducer{ {kind: settings.RefactorExtractFunction, fn: refactorExtractFunction}, {kind: settings.RefactorExtractMethod, fn: refactorExtractMethod}, {kind: settings.RefactorExtractToNewFile, fn: refactorExtractToNewFile}, + {kind: settings.RefactorExtractAllOccursOfExpr, fn: refactorExtractAllOccursOfExpr}, {kind: settings.RefactorExtractVariable, fn: refactorExtractVariable}, {kind: settings.RefactorInlineCall, fn: refactorInlineCall, needPkg: true}, {kind: settings.RefactorRewriteChangeQuote, fn: refactorRewriteChangeQuote}, @@ -452,12 +454,32 @@ func refactorExtractMethod(ctx context.Context, req *codeActionsRequest) error { // refactorExtractVariable produces "Extract variable" code actions. // See [extractVariable] for command implementation. func refactorExtractVariable(ctx context.Context, req *codeActionsRequest) error { - if _, _, ok, _ := canExtractVariable(req.start, req.end, req.pgf.File); ok { + if _, ok, _ := canExtractVariable(req.start, req.end, req.pgf.File, false); ok { req.addApplyFixAction("Extract variable", fixExtractVariable, req.loc) } return nil } +// refactorExtractAllOccursOfExpr produces "Extract n occurrences of expression" code action. +// See [extractAllOccursOfExpr] for command implementation. +func refactorExtractAllOccursOfExpr(ctx context.Context, req *codeActionsRequest) error { + // Don't suggest if only one expr is found, + // otherwise it will duplicate with [refactorExtractVariable] + if exprs, ok, _ := canExtractVariable(req.start, req.end, req.pgf.File, true); ok && len(exprs) > 1 { + startOffset, err := safetoken.Offset(req.pgf.Tok, exprs[0].Pos()) + if err != nil { + return nil + } + endOffset, err := safetoken.Offset(req.pgf.Tok, exprs[0].End()) + if err != nil { + return nil + } + expr := req.pgf.Src[startOffset:endOffset] + req.addApplyFixAction(fmt.Sprintf("Extract %d occurrences of %s", len(exprs), expr), fixExtractAllOccursOfExpr, req.loc) + } + return nil +} + // refactorExtractToNewFile produces "Extract declarations to new file" code actions. // See [server.commandHandler.ExtractToNewFile] for command implementation. func refactorExtractToNewFile(ctx context.Context, req *codeActionsRequest) error { diff --git a/gopls/internal/golang/extract.go b/gopls/internal/golang/extract.go index 82ef6fd69ad..b3074feded2 100644 --- a/gopls/internal/golang/extract.go +++ b/gopls/internal/golang/extract.go @@ -25,26 +25,61 @@ import ( ) 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) { + return extractExprs(fset, start, end, src, file, pkg, info, false) +} + +func extractAllOccursOfExpr(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { + return extractExprs(fset, start, end, src, file, pkg, info, true) +} + +// extractExprs replaces occurrence(s) of a specified expression within the same function +// with newVar. If 'extractAll' is true, it replaces all occurrences of the same expression; +// otherwise, it only replaces the selected expression. +// +// The new variable is declared as close as possible to the first found expression +// within the deepest common scope accessible to all candidate occurrences. +func extractExprs(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info, extractAll bool) (*token.FileSet, *analysis.SuggestedFix, error) { tokFile := fset.File(file.FileStart) - expr, path, ok, err := canExtractVariable(start, end, file) - if !ok { - return nil, nil, fmt.Errorf("extractVariable: cannot extract %s: %v", safetoken.StartPosition(fset, start), err) + exprs, _, err := canExtractVariable(start, end, file, extractAll) + if err != nil { + return nil, nil, fmt.Errorf("extractExpression: cannot extract %s: %v", safetoken.StartPosition(fset, start), err) + } + + scopes := make([][]*types.Scope, len(exprs)) + for i, e := range exprs { + path, _ := astutil.PathEnclosingInterval(file, e.Pos(), e.End()) + scopes[i] = CollectScopes(info, path, e.Pos()) + } + + // Deduplicate, prepare to generate new variable name. + var scopeSet []*types.Scope + seen := make(map[*types.Scope]struct{}) + for _, scope := range scopes { + for _, s := range scope { + if s != nil { + if _, exist := seen[s]; !exist { + seen[s] = struct{}{} + scopeSet = append(scopeSet, s) + } + } + } } + scopeSet = append(scopeSet, pkg.Scope()) // Create new AST node for extracted code. var lhsNames []string - switch expr := expr.(type) { + switch expr := exprs[0].(type) { // TODO: stricter rules for selectorExpr. case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr, - *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr: - lhsName, _ := generateAvailableName(expr.Pos(), path, pkg, info, "x", 0) + *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr, *ast.FuncLit: + lhsName, _ := generateAvailableNameByScopes(scopeSet, "newVar", 0) lhsNames = append(lhsNames, lhsName) case *ast.CallExpr: tup, ok := info.TypeOf(expr).(*types.Tuple) if !ok { // If the call expression only has one return value, we can treat it the // same as our standard extract variable case. - lhsName, _ := generateAvailableName(expr.Pos(), path, pkg, info, "x", 0) + lhsName, _ := generateAvailableNameByScopes(scopeSet, "newVar", 0) lhsNames = append(lhsNames, lhsName) break } @@ -52,13 +87,42 @@ func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file for i := 0; i < tup.Len(); i++ { // Generate a unique variable for each return value. var lhsName string - lhsName, idx = generateAvailableName(expr.Pos(), path, pkg, info, "x", idx) + lhsName, idx = generateAvailableNameByScopes(scopeSet, "newVar", idx) lhsNames = append(lhsNames, lhsName) } default: return nil, nil, fmt.Errorf("cannot extract %T", expr) } + var enclosingScopeOfFirstExpr *types.Scope + for _, scope := range scopes[0] { + if scope != nil { + enclosingScopeOfFirstExpr = scope + break + } + } + // Where all the extractable positions can see variable being declared. + commonScope, err := findDeepestCommonScope(scopes) + if err != nil { + return nil, nil, fmt.Errorf("extractExpression: %v", err) + } + var visiblePath []ast.Node + if commonScope != enclosingScopeOfFirstExpr { + // This means the first expr within function body is not the largest scope, + // we need to find the scope immediately follow the common + // scope where we will insert the statement before. + child := enclosingScopeOfFirstExpr + for p := child; p != nil; p = p.Parent() { + if p == commonScope { + break + } + child = p + } + visiblePath, _ = astutil.PathEnclosingInterval(file, child.Pos(), child.End()) + } else { + // Insert newVar inside commonScope before the first occurrence of the expression. + visiblePath, _ = astutil.PathEnclosingInterval(file, exprs[0].Pos(), exprs[0].End()) + } // TODO: There is a bug here: for a variable declared in a labeled // switch/for statement it returns the for/switch statement itself // which produces the below code which is a compiler error e.g. @@ -68,7 +132,7 @@ func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file // label: // x := r() // switch r1 := x { ... break label ... } // compiler error - insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path) + insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(visiblePath) if insertBeforeStmt == nil { return nil, nil, fmt.Errorf("cannot find location to insert extraction") } @@ -82,59 +146,132 @@ func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file assignStmt := &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent(lhs)}, Tok: token.DEFINE, - Rhs: []ast.Expr{expr}, + Rhs: []ast.Expr{exprs[0]}, } var buf bytes.Buffer if err := format.Node(&buf, fset, assignStmt); err != nil { return nil, nil, err } assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent - + textEdits := []analysis.TextEdit{{ + Pos: insertBeforeStmt.Pos(), + End: insertBeforeStmt.Pos(), + NewText: []byte(assignment), + }} + for _, e := range exprs { + textEdits = append(textEdits, analysis.TextEdit{ + Pos: e.Pos(), + End: e.End(), + NewText: []byte(lhs), + }) + } return fset, &analysis.SuggestedFix{ - TextEdits: []analysis.TextEdit{ - { - Pos: insertBeforeStmt.Pos(), - End: insertBeforeStmt.Pos(), - NewText: []byte(assignment), - }, - { - Pos: start, - End: end, - NewText: []byte(lhs), - }, - }, + TextEdits: textEdits, }, nil } +// findDeepestCommonScope finds the deepest (innermost) scope that is common to all provided scope chains. +// Each scope chain represents the scopes of an expression from innermost to outermost. +// If no common scope is found, it returns an error. +func findDeepestCommonScope(scopeChains [][]*types.Scope) (*types.Scope, error) { + if len(scopeChains) == 0 { + return nil, fmt.Errorf("no scopes provided") + } + // Get the first scope chain as the reference. + referenceChain := scopeChains[0] + + // Iterate from innermost to outermost scope. + for i := 0; i < len(referenceChain); i++ { + candidateScope := referenceChain[i] + if candidateScope == nil { + continue + } + isCommon := true + // See if other exprs' chains all have candidateScope as a common ancestor. + for _, chain := range scopeChains[1:] { + found := false + for j := 0; j < len(chain); j++ { + if chain[j] == candidateScope { + found = true + break + } + } + if !found { + isCommon = false + break + } + } + if isCommon { + return candidateScope, nil + } + } + return nil, fmt.Errorf("no common scope found") +} + +// generateAvailableNameByScopes adjusts the new identifier name +// until there are no collisions in any of the provided scopes. +func generateAvailableNameByScopes(scopes []*types.Scope, prefix string, idx int) (string, int) { + return generateName(idx, prefix, func(name string) bool { + for _, scope := range scopes { + if scope != nil && scope.Lookup(name) != nil { + return true + } + } + return false + }) +} + // canExtractVariable reports whether the code in the given range can be -// extracted to a variable. -func canExtractVariable(start, end token.Pos, file *ast.File) (ast.Expr, []ast.Node, bool, error) { +// extracted to a variable. It returns the selected expression or +// all occurrences of expression structural equal to selected one +// sorted by position depends on extractAll. +func canExtractVariable(start, end token.Pos, file *ast.File, extractAll bool) ([]ast.Expr, bool, error) { if start == end { - return nil, nil, false, fmt.Errorf("start and end are equal") + return nil, false, fmt.Errorf("start and end are equal") } path, _ := astutil.PathEnclosingInterval(file, start, end) if len(path) == 0 { - return nil, nil, false, fmt.Errorf("no path enclosing interval") + return nil, false, fmt.Errorf("no path enclosing interval") } for _, n := range path { if _, ok := n.(*ast.ImportSpec); ok { - return nil, nil, false, fmt.Errorf("cannot extract variable in an import block") + return nil, false, fmt.Errorf("cannot extract variable in an import block") } } node := path[0] if start != node.Pos() || end != node.End() { - return nil, nil, false, fmt.Errorf("range does not map to an AST node") + return nil, false, fmt.Errorf("range does not map to an AST node") } expr, ok := node.(ast.Expr) if !ok { - return nil, nil, false, fmt.Errorf("node is not an expression") + return nil, false, fmt.Errorf("node is not an expression") } + + var exprs []ast.Expr + if !extractAll { + exprs = append(exprs, expr) + } else if funcDecl, ok := path[len(path)-2].(*ast.FuncDecl); ok { + ast.Inspect(funcDecl, func(n ast.Node) bool { + if e, ok := n.(ast.Expr); ok { + if exprIdentical(e, expr) { + exprs = append(exprs, e) + } + } + return true + }) + } else { + return nil, false, fmt.Errorf("node %T is not inside a function", expr) + } + sort.Slice(exprs, func(i, j int) bool { + return exprs[i].Pos() < exprs[j].Pos() + }) + switch expr.(type) { case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.CallExpr, - *ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr: - return expr, path, true, nil + *ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr, *ast.FuncLit: + return exprs, true, nil } - return nil, nil, false, fmt.Errorf("cannot extract an %T to a variable", expr) + return nil, false, fmt.Errorf("cannot extract an %T to a variable", expr) } // Calculate indentation for insertion. @@ -1466,3 +1603,217 @@ func getDecls(retVars []*returnVariable) []*ast.Field { } return decls } + +// exprIdentical recursively compares two ast.Expr nodes for structural equality, +// ignoring position fields. If both of them are nil, consider them equal. +func exprIdentical(x, y ast.Expr) bool { + if x == nil || y == nil { + return x == y + } + switch x := x.(type) { + case *ast.BadExpr: + return false + case *ast.BasicLit: + y, ok := y.(*ast.BasicLit) + if x == nil || y == nil { + return x == y + } + return ok && x.Kind == y.Kind && x.Value == y.Value + case *ast.CompositeLit: + y, ok := y.(*ast.CompositeLit) + if x == nil || y == nil { + return x == y + } + if !ok || len(x.Elts) != len(y.Elts) || !exprIdentical(x.Type, y.Type) { + return false + } + for i := range x.Elts { + if !exprIdentical(x.Elts[i], y.Elts[i]) { + return false + } + } + return true + case *ast.ArrayType: + y, ok := y.(*ast.ArrayType) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.Len, y.Len) && exprIdentical(x.Elt, y.Elt) + case *ast.Ellipsis: + y, ok := y.(*ast.Ellipsis) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.Elt, y.Elt) + case *ast.FuncLit: + y, ok := y.(*ast.FuncLit) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.Type, y.Type) + case *ast.IndexExpr: + y, ok := y.(*ast.IndexExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.X, y.X) && exprIdentical(x.Index, y.Index) + case *ast.IndexListExpr: + y, ok := y.(*ast.IndexListExpr) + if x == nil || y == nil { + return x == y + } + if !ok || len(x.Indices) != len(y.Indices) || !exprIdentical(x.X, y.X) { + return false + } + for i := range x.Indices { + if !exprIdentical(x.Indices[i], y.Indices[i]) { + return false + } + } + return true + case *ast.SliceExpr: + y, ok := y.(*ast.SliceExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.X, y.X) && exprIdentical(x.Low, y.Low) && exprIdentical(x.High, y.High) && exprIdentical(x.Max, y.Max) && x.Slice3 == y.Slice3 + case *ast.TypeAssertExpr: + y, ok := y.(*ast.TypeAssertExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.X, y.X) && exprIdentical(x.Type, y.Type) + case *ast.StarExpr: + y, ok := y.(*ast.StarExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.X, y.X) + case *ast.KeyValueExpr: + y, ok := y.(*ast.KeyValueExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.Key, y.Key) && exprIdentical(x.Value, y.Value) + case *ast.UnaryExpr: + y, ok := y.(*ast.UnaryExpr) + if x == nil || y == nil { + return x == y + } + return ok && x.Op == y.Op && exprIdentical(x.X, y.X) + case *ast.MapType: + y, ok := y.(*ast.MapType) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.Value, y.Value) && exprIdentical(x.Key, y.Key) + case *ast.ChanType: + y, ok := y.(*ast.ChanType) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.Value, y.Value) && x.Dir == y.Dir + case *ast.FuncType: + y, ok := y.(*ast.FuncType) + if x == nil || y == nil { + return x == y + } + return ok && fieldListIdentical(x.Params, y.Params) && fieldListIdentical(x.TypeParams, y.TypeParams) && fieldListIdentical(x.Results, y.Results) + case *ast.InterfaceType: + y, ok := y.(*ast.InterfaceType) + if x == nil || y == nil { + return x == y + } + return ok && x.Incomplete == y.Incomplete && fieldListIdentical(x.Methods, y.Methods) + case *ast.StructType: + y, ok := y.(*ast.StructType) + if x == nil || y == nil { + return x == y + } + return ok && x.Incomplete == y.Incomplete && fieldListIdentical(x.Fields, y.Fields) + case *ast.BinaryExpr: + y, ok := y.(*ast.BinaryExpr) + if x == nil || y == nil { + return x == y + } + return ok && x.Op == y.Op && exprIdentical(x.X, y.X) && exprIdentical(x.Y, y.Y) + case *ast.Ident: + y, ok := y.(*ast.Ident) + if x == nil || y == nil { + return x == y + } + return ok && x.Name == y.Name + case *ast.ParenExpr: + y, ok := y.(*ast.ParenExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.X, y.X) + case *ast.SelectorExpr: + y, ok := y.(*ast.SelectorExpr) + if x == nil || y == nil { + return x == y + } + return ok && exprIdentical(x.X, y.X) && exprIdentical(x.Sel, y.Sel) + case *ast.CallExpr: + y, ok := y.(*ast.CallExpr) + if x == nil || y == nil { + return x == y + } + if !ok || len(x.Args) != len(y.Args) { + return false + } + if !exprIdentical(x.Fun, y.Fun) { + return false + } + for i := range x.Args { + if !exprIdentical(x.Args[i], y.Args[i]) { + return false + } + } + return true + default: + // All 23 impls of ast.Expr in ast are handled, consider others unequal. + return false + } +} + +// fieldListIdentical recursively compares two ast.FieldList nodes for structural equality, +// ignoring position fields. +func fieldListIdentical(x, y *ast.FieldList) bool { + if x == nil || y == nil { + return x == y + } + if len(x.List) != len(y.List) { + return false + } + for i := range x.List { + if !fieldIdentical(x.List[i], y.List[i]) { + return false + } + } + return true +} + +// fieldIdentical recursively compares two ast.Field nodes for structural equality, +// ignoring position fields. +func fieldIdentical(x, y *ast.Field) bool { + if x == nil || y == nil { + return x == y + } + if len(x.Names) != len(y.Names) { + return false + } + for i := range x.Names { + if !exprIdentical(x.Names[i], y.Names[i]) { + return false + } + } + if !exprIdentical(x.Type, y.Type) { + return false + } + if !exprIdentical(x.Tag, y.Tag) { + return false + } + return true +} diff --git a/gopls/internal/golang/fix.go b/gopls/internal/golang/fix.go index a20658fce7c..658882cad80 100644 --- a/gopls/internal/golang/fix.go +++ b/gopls/internal/golang/fix.go @@ -59,6 +59,7 @@ func singleFile(fixer1 singleFileFixer) fixer { // Names of ApplyFix.Fix created directly by the CodeAction handler. const ( fixExtractVariable = "extract_variable" + fixExtractAllOccursOfExpr = "extract_all_occurs_of_expr" fixExtractFunction = "extract_function" fixExtractMethod = "extract_method" fixInlineCall = "inline_call" @@ -106,6 +107,7 @@ func ApplyFix(ctx context.Context, fix string, snapshot *cache.Snapshot, fh file fixExtractFunction: singleFile(extractFunction), fixExtractMethod: singleFile(extractMethod), fixExtractVariable: singleFile(extractVariable), + fixExtractAllOccursOfExpr: singleFile(extractAllOccursOfExpr), fixInlineCall: inlineCall, fixInvertIfCondition: singleFile(invertIfCondition), fixSplitLines: singleFile(splitLines), diff --git a/gopls/internal/settings/codeactionkind.go b/gopls/internal/settings/codeactionkind.go index 16a2eecb2cb..ea14eec45ca 100644 --- a/gopls/internal/settings/codeactionkind.go +++ b/gopls/internal/settings/codeactionkind.go @@ -97,10 +97,11 @@ const ( RefactorInlineCall protocol.CodeActionKind = "refactor.inline.call" // refactor.extract - RefactorExtractFunction protocol.CodeActionKind = "refactor.extract.function" - RefactorExtractMethod protocol.CodeActionKind = "refactor.extract.method" - RefactorExtractVariable protocol.CodeActionKind = "refactor.extract.variable" - RefactorExtractToNewFile protocol.CodeActionKind = "refactor.extract.toNewFile" + RefactorExtractFunction protocol.CodeActionKind = "refactor.extract.function" + RefactorExtractMethod protocol.CodeActionKind = "refactor.extract.method" + RefactorExtractVariable protocol.CodeActionKind = "refactor.extract.variable" + RefactorExtractAllOccursOfExpr protocol.CodeActionKind = "refactor.extract.variable.all" + RefactorExtractToNewFile protocol.CodeActionKind = "refactor.extract.toNewFile" // Note: add new kinds to: // - the SupportedCodeActions map in default.go diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_expressions.txt b/gopls/internal/test/marker/testdata/codeaction/extract_expressions.txt new file mode 100644 index 00000000000..00cfe7cebe1 --- /dev/null +++ b/gopls/internal/test/marker/testdata/codeaction/extract_expressions.txt @@ -0,0 +1,212 @@ +This test checks the behavior of the 'extract all occurrences of expression' code action. +See extract_expressions_resolve.txt for the same test with resolve support. + +-- flags -- +-ignore_extra_diags + +-- basic_lit.go -- +package extract_all + +func _() { + var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) + var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) +} +-- @basic_lit/basic_lit.go -- +@@ -4,2 +4,3 @@ +- var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) +- var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) ++ newVar := 1 + 2 ++ var _ = newVar + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) ++ var _ = newVar + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) +-- nested_scope.go -- +package extract_all + +func _() { + newVar1 := 0 + if true { + x := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) + } + if true { + newVar := 0 + if false { + y := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) + } + } + z := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +} +-- @nested_scope/nested_scope.go -- +@@ -5 +5 @@ ++ newVar2 := 1 + 2 + 3 +@@ -6 +7 @@ +- x := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) ++ x := newVar2 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +@@ -11 +12 @@ +- y := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) ++ y := newVar2 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +@@ -14 +15 @@ +- z := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) ++ z := newVar2 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +-- function_call.go -- +package extract_all + +import "fmt" + +func _() { + result := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) + if result != "" { + anotherResult := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) + _ = anotherResult + } +} +-- @replace_func_call/function_call.go -- +@@ -6 +6,2 @@ +- result := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) ++ newVar := fmt.Sprintf("%d", 42) ++ result := newVar //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) +@@ -8 +9 @@ +- anotherResult := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) ++ anotherResult := newVar //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) +-- composite_literals.go -- +package extract_all + +func _() { + data := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) + processData(data) + moreData := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) + processData(moreData) +} + +func processData(d []int) {} +-- @composite/composite_literals.go -- +@@ -4 +4,2 @@ +- data := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) ++ newVar := []int{1, 2, 3} ++ data := newVar //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) +@@ -6 +7 @@ +- moreData := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) ++ moreData := newVar //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) +-- selector.go -- +package extract_all + +type MyStruct struct { + Value int +} + +func _() { + s := MyStruct{Value: 10} + v := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) + if v > 0 { + w := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) + _ = w + } +} +-- @sel/selector.go -- +@@ -9 +9,2 @@ +- v := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) ++ newVar := s.Value ++ v := newVar //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) +@@ -11 +12 @@ +- w := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) ++ w := newVar //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) +-- index.go -- +package extract_all + +func _() { + arr := []int{1, 2, 3} + val := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) + val2 := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) +} +-- @index/index.go -- +@@ -5,2 +5,3 @@ +- val := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) +- val2 := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) ++ newVar := arr[0] ++ val := newVar //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) ++ val2 := newVar //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) +-- slice_expr.go -- +package extract_all + +func _() { + data := []int{1, 2, 3, 4, 5} + part := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) + anotherPart := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) +} +-- @slice/slice_expr.go -- +@@ -5,2 +5,3 @@ +- part := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) +- anotherPart := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) ++ newVar := data[1:3] ++ part := newVar //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) ++ anotherPart := newVar //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) +-- nested_func.go -- +package extract_all + +func outer() { + inner := func() { + val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) + _ = val + } + inner() + val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) + _ = val +} +-- @nested/nested_func.go -- +@@ -4 +4 @@ ++ newVar := 100 + 200 +@@ -5 +6 @@ +- val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) ++ val := newVar //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) +@@ -9 +10 @@ +- val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) ++ val := newVar //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) +-- switch.go -- +package extract_all + +func _() { + value := 2 + switch value { + case 1: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) + _ = result + case 2: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) + _ = result + default: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) + _ = result + } +} +-- @switch/switch.go -- +@@ -5 +5 @@ ++ newVar := value * 10 +@@ -7 +8 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) +@@ -10 +11 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) +@@ -13 +14 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) +-- switch_single.go -- +package extract_all + +func _() { + value := 2 + switch value { + case 1: + result := value * 10 + _ = result + case 2: + result := value * 10 + _ = result + default: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable", switch_single) + _ = result + } +} +-- @switch_single/switch_single.go -- +@@ -13 +13,2 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable", switch_single) ++ newVar := value * 10 ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable", switch_single) diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_expressions_resolve.txt b/gopls/internal/test/marker/testdata/codeaction/extract_expressions_resolve.txt new file mode 100644 index 00000000000..7faf163b8d6 --- /dev/null +++ b/gopls/internal/test/marker/testdata/codeaction/extract_expressions_resolve.txt @@ -0,0 +1,223 @@ +This test checks the behavior of the 'replace all occurrences of expression' code action, with resolve support. +See extract_expressions.txt for the same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- flags -- +-ignore_extra_diags + +-- basic_lit.go -- +package extract_all + +func _() { + var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) + var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) +} +-- @basic_lit/basic_lit.go -- +@@ -4,2 +4,3 @@ +- var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) +- var _ = 1 + 2 + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) ++ newVar := 1 + 2 ++ var _ = newVar + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) ++ var _ = newVar + 3 //@codeactionedit("1 + 2", "refactor.extract.variable.all", basic_lit) +-- nested_scope.go -- +package extract_all + +func _() { + newVar1 := 0 + if true { + x := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) + } + if true { + newVar := 0 + if false { + y := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) + } + } + z := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +} +-- @nested_scope/nested_scope.go -- +@@ -5 +5 @@ ++ newVar2 := 1 + 2 + 3 +@@ -6 +7 @@ +- x := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) ++ x := newVar2 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +@@ -11 +12 @@ +- y := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) ++ y := newVar2 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +@@ -14 +15 @@ +- z := 1 + 2 + 3 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) ++ z := newVar2 //@codeactionedit("1 + 2 + 3", "refactor.extract.variable.all", nested_scope) +-- function_call.go -- +package extract_all + +import "fmt" + +func _() { + result := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) + if result != "" { + anotherResult := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) + _ = anotherResult + } +} +-- @replace_func_call/function_call.go -- +@@ -6 +6,2 @@ +- result := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) ++ newVar := fmt.Sprintf("%d", 42) ++ result := newVar //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) +@@ -8 +9 @@ +- anotherResult := fmt.Sprintf("%d", 42) //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) ++ anotherResult := newVar //@codeactionedit(`fmt.Sprintf("%d", 42)`, "refactor.extract.variable.all", replace_func_call) +-- composite_literals.go -- +package extract_all + +func _() { + data := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) + processData(data) + moreData := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) + processData(moreData) +} + +func processData(d []int) {} +-- @composite/composite_literals.go -- +@@ -4 +4,2 @@ +- data := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) ++ newVar := []int{1, 2, 3} ++ data := newVar //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) +@@ -6 +7 @@ +- moreData := []int{1, 2, 3} //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) ++ moreData := newVar //@codeactionedit("[]int{1, 2, 3}", "refactor.extract.variable.all", composite) +-- selector.go -- +package extract_all + +type MyStruct struct { + Value int +} + +func _() { + s := MyStruct{Value: 10} + v := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) + if v > 0 { + w := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) + _ = w + } +} +-- @sel/selector.go -- +@@ -9 +9,2 @@ +- v := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) ++ newVar := s.Value ++ v := newVar //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) +@@ -11 +12 @@ +- w := s.Value //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) ++ w := newVar //@codeactionedit("s.Value", "refactor.extract.variable.all", sel) +-- index.go -- +package extract_all + +func _() { + arr := []int{1, 2, 3} + val := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) + val2 := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) +} +-- @index/index.go -- +@@ -5,2 +5,3 @@ +- val := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) +- val2 := arr[0] //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) ++ newVar := arr[0] ++ val := newVar //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) ++ val2 := newVar //@codeactionedit("arr[0]", "refactor.extract.variable.all", index) +-- slice_expr.go -- +package extract_all + +func _() { + data := []int{1, 2, 3, 4, 5} + part := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) + anotherPart := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) +} +-- @slice/slice_expr.go -- +@@ -5,2 +5,3 @@ +- part := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) +- anotherPart := data[1:3] //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) ++ newVar := data[1:3] ++ part := newVar //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) ++ anotherPart := newVar //@codeactionedit("data[1:3]", "refactor.extract.variable.all", slice) +-- nested_func.go -- +package extract_all + +func outer() { + inner := func() { + val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) + _ = val + } + inner() + val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) + _ = val +} +-- @nested/nested_func.go -- +@@ -4 +4 @@ ++ newVar := 100 + 200 +@@ -5 +6 @@ +- val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) ++ val := newVar //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) +@@ -9 +10 @@ +- val := 100 + 200 //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) ++ val := newVar //@codeactionedit("100 + 200", "refactor.extract.variable.all", nested) +-- switch.go -- +package extract_all + +func _() { + value := 2 + switch value { + case 1: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) + _ = result + case 2: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) + _ = result + default: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) + _ = result + } +} +-- @switch/switch.go -- +@@ -5 +5 @@ ++ newVar := value * 10 +@@ -7 +8 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) +@@ -10 +11 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) +@@ -13 +14 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable.all", switch) +-- switch_single.go -- +package extract_all + +func _() { + value := 2 + switch value { + case 1: + result := value * 10 + _ = result + case 2: + result := value * 10 + _ = result + default: + result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable", switch_single) + _ = result + } +} +-- @switch_single/switch_single.go -- +@@ -13 +13,2 @@ +- result := value * 10 //@codeactionedit("value * 10", "refactor.extract.variable", switch_single) ++ newVar := value * 10 ++ result := newVar //@codeactionedit("value * 10", "refactor.extract.variable", switch_single) diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt b/gopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt index 259b84a09a3..b2471cc22f9 100644 --- a/gopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt +++ b/gopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt @@ -25,5 +25,5 @@ func main() { -- @type_switch_func_call/extract_switch.go -- @@ -10 +10,2 @@ - switch r := f().(type) { //@codeactionedit("f()", "refactor.extract.variable", type_switch_func_call) -+ x := f() -+ switch r := x.(type) { //@codeactionedit("f()", "refactor.extract.variable", type_switch_func_call) ++ newVar := f() ++ switch r := newVar.(type) { //@codeactionedit("f()", "refactor.extract.variable", type_switch_func_call) diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt b/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt index 8c500d02c1e..e372064df52 100644 --- a/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt +++ b/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt @@ -15,13 +15,13 @@ func _() { -- @basic_lit1/basic_lit.go -- @@ -4 +4,2 @@ - var _ = 1 + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) -+ x := 1 -+ var _ = x + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) ++ newVar := 1 ++ var _ = newVar + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) -- @basic_lit2/basic_lit.go -- @@ -5 +5,2 @@ - var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) -+ x := 3 + 4 -+ var _ = x //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) ++ newVar := 3 + 4 ++ var _ = newVar //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) -- func_call.go -- package extract @@ -36,13 +36,13 @@ func _() { -- @func_call1/func_call.go -- @@ -6 +6,2 @@ - x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) -+ x := append([]int{}, 1) -+ x0 := x //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) ++ newVar := append([]int{}, 1) ++ x0 := newVar //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) -- @func_call2/func_call.go -- @@ -8 +8,2 @@ - b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) -+ x, x1 := strconv.Atoi(str) -+ b, err := x, x1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) ++ newVar, newVar1 := strconv.Atoi(str) ++ b, err := newVar, newVar1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) -- scope.go -- package extract @@ -61,10 +61,10 @@ func _() { -- @scope1/scope.go -- @@ -8 +8,2 @@ - y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) -+ x := ast.CompositeLit{} -+ y := x //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) ++ newVar := ast.CompositeLit{} ++ y := newVar //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) -- @scope2/scope.go -- @@ -11 +11,2 @@ - x1 := !false //@codeactionedit("!false", "refactor.extract.variable", scope2) -+ x := !false -+ x1 := x //@codeactionedit("!false", "refactor.extract.variable", scope2) ++ newVar := !false ++ x1 := newVar //@codeactionedit("!false", "refactor.extract.variable", scope2) diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt b/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt index b3a9a67059f..3b1dc24687c 100644 --- a/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt +++ b/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt @@ -26,13 +26,13 @@ func _() { -- @basic_lit1/basic_lit.go -- @@ -4 +4,2 @@ - var _ = 1 + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) -+ x := 1 -+ var _ = x + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) ++ newVar := 1 ++ var _ = newVar + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) -- @basic_lit2/basic_lit.go -- @@ -5 +5,2 @@ - var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) -+ x := 3 + 4 -+ var _ = x //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) ++ newVar := 3 + 4 ++ var _ = newVar //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) -- func_call.go -- package extract @@ -47,13 +47,13 @@ func _() { -- @func_call1/func_call.go -- @@ -6 +6,2 @@ - x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) -+ x := append([]int{}, 1) -+ x0 := x //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) ++ newVar := append([]int{}, 1) ++ x0 := newVar //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) -- @func_call2/func_call.go -- @@ -8 +8,2 @@ - b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) -+ x, x1 := strconv.Atoi(str) -+ b, err := x, x1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) ++ newVar, newVar1 := strconv.Atoi(str) ++ b, err := newVar, newVar1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) -- scope.go -- package extract @@ -72,10 +72,10 @@ func _() { -- @scope1/scope.go -- @@ -8 +8,2 @@ - y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) -+ x := ast.CompositeLit{} -+ y := x //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) ++ newVar := ast.CompositeLit{} ++ y := newVar //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) -- @scope2/scope.go -- @@ -11 +11,2 @@ - x1 := !false //@codeactionedit("!false", "refactor.extract.variable", scope2) -+ x := !false -+ x1 := x //@codeactionedit("!false", "refactor.extract.variable", scope2) ++ newVar := !false ++ x1 := newVar //@codeactionedit("!false", "refactor.extract.variable", scope2)