Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go

     1  // Copyright 2025 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  
     5  package modernize
     6  
     7  import (
     8  	"go/ast"
     9  	"go/token"
    10  
    11  	"golang.org/x/tools/go/analysis"
    12  	"golang.org/x/tools/go/analysis/passes/inspect"
    13  	"golang.org/x/tools/internal/analysis/analyzerutil"
    14  	"golang.org/x/tools/internal/astutil"
    15  	"golang.org/x/tools/internal/refactor"
    16  	"golang.org/x/tools/internal/versions"
    17  )
    18  
    19  var ForVarAnalyzer = &analysis.Analyzer{
    20  	Name:     "forvar",
    21  	Doc:      analyzerutil.MustExtractDoc(doc, "forvar"),
    22  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    23  	Run:      forvar,
    24  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
    25  }
    26  
    27  // forvar offers to fix unnecessary copying of a for variable
    28  //
    29  //	for _, x := range foo {
    30  //		x := x // offer to remove this superfluous assignment
    31  //	}
    32  //
    33  // Prerequisites:
    34  // First statement in a range loop has to be <ident> := <ident>
    35  // where the two idents are the same,
    36  // and the ident is defined (:=) as a variable in the for statement.
    37  // (Note that this 'fix' does not work for three clause loops
    38  // because the Go spec says "The variable used by each subsequent iteration
    39  // is declared implicitly before executing the post statement and initialized to the
    40  // value of the previous iteration's variable at that moment.")
    41  //
    42  // Variant: same thing in an IfStmt.Init, when the IfStmt is the sole
    43  // loop body statement:
    44  //
    45  //	for _, x := range foo {
    46  //		if x := x; cond { ... }
    47  //	}
    48  //
    49  // (The restriction is necessary to avoid potential problems arising
    50  // from merging two distinct variables.)
    51  //
    52  // This analyzer is synergistic with stditerators,
    53  // which may create redundant "x := x" statements.
    54  func forvar(pass *analysis.Pass) (any, error) {
    55  	for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
    56  		for curLoop := range curFile.Preorder((*ast.RangeStmt)(nil)) {
    57  			loop := curLoop.Node().(*ast.RangeStmt)
    58  			if loop.Tok != token.DEFINE {
    59  				continue
    60  			}
    61  			isLoopVarRedecl := func(stmt ast.Stmt) bool {
    62  				if assign, ok := stmt.(*ast.AssignStmt); ok &&
    63  					assign.Tok == token.DEFINE &&
    64  					len(assign.Lhs) == len(assign.Rhs) {
    65  
    66  					for i, lhs := range assign.Lhs {
    67  						if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
    68  							(astutil.EqualSyntax(lhs, loop.Key) ||
    69  								astutil.EqualSyntax(lhs, loop.Value))) {
    70  							return false
    71  						}
    72  					}
    73  					return true
    74  				}
    75  				return false
    76  			}
    77  			// Have: for k, v := range x { stmts }
    78  			//
    79  			// Delete the prefix of stmts that are
    80  			// of the form k := k; v := v; k, v := k, v; v, k := v, k.
    81  			for _, stmt := range loop.Body.List {
    82  				if isLoopVarRedecl(stmt) {
    83  					// { x := x; ... }
    84  					//   ------
    85  				} else if ifstmt, ok := stmt.(*ast.IfStmt); ok &&
    86  					ifstmt.Init != nil &&
    87  					len(loop.Body.List) == 1 && // must be sole statement in loop body
    88  					isLoopVarRedecl(ifstmt.Init) {
    89  					// if x := x; cond {
    90  					//    ------
    91  					stmt = ifstmt.Init
    92  				} else {
    93  					break // stop at first other statement
    94  				}
    95  
    96  				curStmt, _ := curLoop.FindNode(stmt)
    97  				edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
    98  				if len(edits) > 0 {
    99  					pass.Report(analysis.Diagnostic{
   100  						Pos:     stmt.Pos(),
   101  						End:     stmt.End(),
   102  						Message: "copying variable is unneeded",
   103  						SuggestedFixes: []analysis.SuggestedFix{{
   104  							Message:   "Remove unneeded redeclaration",
   105  							TextEdits: edits,
   106  						}},
   107  					})
   108  				}
   109  			}
   110  		}
   111  	}
   112  	return nil, nil
   113  }
   114  

View as plain text