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

     1  // Copyright 2013 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 assign
     6  
     7  // TODO(adonovan): check also for assignments to struct fields inside
     8  // methods that are on T instead of *T.
     9  
    10  import (
    11  	_ "embed"
    12  	"go/ast"
    13  	"go/token"
    14  	"go/types"
    15  	"reflect"
    16  	"strings"
    17  
    18  	"golang.org/x/tools/go/analysis"
    19  	"golang.org/x/tools/go/analysis/passes/inspect"
    20  	"golang.org/x/tools/go/ast/inspector"
    21  	"golang.org/x/tools/internal/analysis/analyzerutil"
    22  	"golang.org/x/tools/internal/astutil"
    23  	"golang.org/x/tools/internal/refactor"
    24  	"golang.org/x/tools/internal/typesinternal"
    25  )
    26  
    27  //go:embed doc.go
    28  var doc string
    29  
    30  var Analyzer = &analysis.Analyzer{
    31  	Name:     "assign",
    32  	Doc:      analyzerutil.MustExtractDoc(doc, "assign"),
    33  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
    34  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    35  	Run:      run,
    36  }
    37  
    38  func run(pass *analysis.Pass) (any, error) {
    39  	var (
    40  		inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    41  		info    = pass.TypesInfo
    42  	)
    43  
    44  	for curAssign := range inspect.Root().Preorder((*ast.AssignStmt)(nil)) {
    45  		stmt := curAssign.Node().(*ast.AssignStmt)
    46  		if stmt.Tok != token.ASSIGN {
    47  			continue // ignore :=
    48  		}
    49  		if len(stmt.Lhs) != len(stmt.Rhs) {
    50  			// If LHS and RHS have different cardinality, they can't be the same.
    51  			continue
    52  		}
    53  
    54  		// Delete redundant LHS, RHS pairs, taking care
    55  		// to include intervening commas.
    56  		var (
    57  			exprs                    []string // expressions appearing on both sides (x = x)
    58  			edits                    []analysis.TextEdit
    59  			runStartLHS, runStartRHS token.Pos // non-zero => within a run
    60  		)
    61  		for i, lhs := range stmt.Lhs {
    62  			rhs := stmt.Rhs[i]
    63  			isSelfAssign := false
    64  			var le string
    65  
    66  			if typesinternal.NoEffects(info, lhs) &&
    67  				typesinternal.NoEffects(info, rhs) &&
    68  				!isMapIndex(info, lhs) &&
    69  				reflect.TypeOf(lhs) == reflect.TypeOf(rhs) { // short-circuit the heavy-weight gofmt check
    70  
    71  				le = astutil.Format(pass.Fset, lhs)
    72  				re := astutil.Format(pass.Fset, rhs)
    73  				if le == re {
    74  					isSelfAssign = true
    75  				}
    76  			}
    77  
    78  			if isSelfAssign {
    79  				exprs = append(exprs, le)
    80  				if !runStartLHS.IsValid() {
    81  					// Start of a new run of self-assignments.
    82  					if i > 0 {
    83  						runStartLHS = stmt.Lhs[i-1].End()
    84  						runStartRHS = stmt.Rhs[i-1].End()
    85  					} else {
    86  						runStartLHS = lhs.Pos()
    87  						runStartRHS = rhs.Pos()
    88  					}
    89  				}
    90  			} else if runStartLHS.IsValid() {
    91  				// End of a run of self-assignments.
    92  				endLHS, endRHS := stmt.Lhs[i-1].End(), stmt.Rhs[i-1].End()
    93  				if runStartLHS == stmt.Lhs[0].Pos() {
    94  					endLHS, endRHS = lhs.Pos(), rhs.Pos()
    95  				}
    96  				edits = append(edits,
    97  					analysis.TextEdit{Pos: runStartLHS, End: endLHS},
    98  					analysis.TextEdit{Pos: runStartRHS, End: endRHS},
    99  				)
   100  				runStartLHS, runStartRHS = 0, 0
   101  			}
   102  		}
   103  
   104  		// If a run of self-assignments continues to the end of the statement, close it.
   105  		if runStartLHS.IsValid() {
   106  			last := len(stmt.Lhs) - 1
   107  			edits = append(edits,
   108  				analysis.TextEdit{Pos: runStartLHS, End: stmt.Lhs[last].End()},
   109  				analysis.TextEdit{Pos: runStartRHS, End: stmt.Rhs[last].End()},
   110  			)
   111  		}
   112  
   113  		if len(exprs) == 0 {
   114  			continue
   115  		}
   116  
   117  		if len(exprs) == len(stmt.Lhs) {
   118  			// If every part of the statement is a self-assignment,
   119  			// remove the whole statement.
   120  			tokFile := pass.Fset.File(stmt.Pos())
   121  			edits = refactor.DeleteStmt(tokFile, curAssign)
   122  		}
   123  
   124  		pass.Report(analysis.Diagnostic{
   125  			Pos:     stmt.Pos(),
   126  			Message: "self-assignment of " + strings.Join(exprs, ", "),
   127  			SuggestedFixes: []analysis.SuggestedFix{{
   128  				Message:   "Remove self-assignment",
   129  				TextEdits: edits,
   130  			}},
   131  		})
   132  	}
   133  
   134  	return nil, nil
   135  }
   136  
   137  // isMapIndex returns true if e is a map index expression.
   138  func isMapIndex(info *types.Info, e ast.Expr) bool {
   139  	if idx, ok := ast.Unparen(e).(*ast.IndexExpr); ok {
   140  		if typ := info.Types[idx.X].Type; typ != nil {
   141  			_, ok := typ.Underlying().(*types.Map)
   142  			return ok
   143  		}
   144  	}
   145  	return false
   146  }
   147  

View as plain text