s1021.go raw
1 package s1021
2
3 import (
4 "go/ast"
5 "go/token"
6
7 "honnef.co/go/tools/analysis/code"
8 "honnef.co/go/tools/analysis/edit"
9 "honnef.co/go/tools/analysis/facts/generated"
10 "honnef.co/go/tools/analysis/lint"
11 "honnef.co/go/tools/analysis/report"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 )
16
17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
18 Analyzer: &analysis.Analyzer{
19 Name: "S1021",
20 Run: run,
21 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
22 },
23 Doc: &lint.RawDocumentation{
24 Title: `Merge variable declaration and assignment`,
25 Before: `
26 var x uint
27 x = 1`,
28 After: `var x uint = 1`,
29 Since: "2017.1",
30 MergeIf: lint.MergeIfAny,
31 },
32 })
33
34 var Analyzer = SCAnalyzer.Analyzer
35
36 func run(pass *analysis.Pass) (interface{}, error) {
37 hasMultipleAssignments := func(root ast.Node, ident *ast.Ident) bool {
38 num := 0
39 ast.Inspect(root, func(node ast.Node) bool {
40 if num >= 2 {
41 return false
42 }
43 assign, ok := node.(*ast.AssignStmt)
44 if !ok {
45 return true
46 }
47 for _, lhs := range assign.Lhs {
48 if oident, ok := lhs.(*ast.Ident); ok {
49 if pass.TypesInfo.ObjectOf(oident) == pass.TypesInfo.ObjectOf(ident) {
50 num++
51 }
52 }
53 }
54
55 return true
56 })
57 return num >= 2
58 }
59 fn := func(node ast.Node) {
60 block := node.(*ast.BlockStmt)
61 if len(block.List) < 2 {
62 return
63 }
64 for i, stmt := range block.List[:len(block.List)-1] {
65 _ = i
66 decl, ok := stmt.(*ast.DeclStmt)
67 if !ok {
68 continue
69 }
70 gdecl, ok := decl.Decl.(*ast.GenDecl)
71 if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 {
72 continue
73 }
74 vspec, ok := gdecl.Specs[0].(*ast.ValueSpec)
75 if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 {
76 continue
77 }
78
79 assign, ok := block.List[i+1].(*ast.AssignStmt)
80 if !ok || assign.Tok != token.ASSIGN {
81 continue
82 }
83 if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
84 continue
85 }
86 ident, ok := assign.Lhs[0].(*ast.Ident)
87 if !ok {
88 continue
89 }
90 if pass.TypesInfo.ObjectOf(vspec.Names[0]) != pass.TypesInfo.ObjectOf(ident) {
91 continue
92 }
93
94 if code.RefersTo(pass, assign.Rhs[0], pass.TypesInfo.ObjectOf(ident)) {
95 continue
96 }
97 if hasMultipleAssignments(block, ident) {
98 continue
99 }
100
101 r := &ast.GenDecl{
102 Specs: []ast.Spec{
103 &ast.ValueSpec{
104 Names: vspec.Names,
105 Values: []ast.Expr{assign.Rhs[0]},
106 Type: vspec.Type,
107 },
108 },
109 Tok: gdecl.Tok,
110 }
111 report.Report(pass, decl, "should merge variable declaration with assignment on next line",
112 report.FilterGenerated(),
113 report.Fixes(edit.Fix("merge declaration with assignment", edit.ReplaceWithNode(pass.Fset, edit.Range{decl.Pos(), assign.End()}, r))))
114 }
115 }
116 code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
117 return nil, nil
118 }
119