1
2
3
4
5 package assign
6
7
8
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
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
48 }
49 if len(stmt.Lhs) != len(stmt.Rhs) {
50
51 continue
52 }
53
54
55
56 var (
57 exprs []string
58 edits []analysis.TextEdit
59 runStartLHS, runStartRHS token.Pos
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) {
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
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
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
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
119
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
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