github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1009/s1009.go (about) 1 package s1009 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/constant" 7 "go/token" 8 "go/types" 9 10 "github.com/amarpal/go-tools/analysis/code" 11 "github.com/amarpal/go-tools/analysis/facts/generated" 12 "github.com/amarpal/go-tools/analysis/lint" 13 "github.com/amarpal/go-tools/analysis/report" 14 "github.com/amarpal/go-tools/go/types/typeutil" 15 "github.com/amarpal/go-tools/knowledge" 16 17 "golang.org/x/tools/go/analysis" 18 "golang.org/x/tools/go/analysis/passes/inspect" 19 ) 20 21 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 22 Analyzer: &analysis.Analyzer{ 23 Name: "S1009", 24 Run: run, 25 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, 26 }, 27 Doc: &lint.Documentation{ 28 Title: `Omit redundant nil check on slices`, 29 Text: `The \'len\' function is defined for all slices, even nil ones, which have 30 a length of zero. It is not necessary to check if a slice is not nil 31 before checking that its length is not zero.`, 32 Before: `if x != nil && len(x) != 0 {}`, 33 After: `if len(x) != 0 {}`, 34 Since: "2017.1", 35 MergeIf: lint.MergeIfAny, 36 }, 37 }) 38 39 var Analyzer = SCAnalyzer.Analyzer 40 41 // run checks for the following redundant nil-checks: 42 // 43 // if x == nil || len(x) == 0 {} 44 // if x != nil && len(x) != 0 {} 45 // if x != nil && len(x) == N {} (where N != 0) 46 // if x != nil && len(x) > N {} 47 // if x != nil && len(x) >= N {} (where N != 0) 48 func run(pass *analysis.Pass) (interface{}, error) { 49 isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) { 50 _, ok := expr.(*ast.BasicLit) 51 if ok { 52 return true, code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0)) 53 } 54 id, ok := expr.(*ast.Ident) 55 if !ok { 56 return false, false 57 } 58 c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const) 59 if !ok { 60 return false, false 61 } 62 return true, c.Val().Kind() == constant.Int && c.Val().String() == "0" 63 } 64 65 fn := func(node ast.Node) { 66 // check that expr is "x || y" or "x && y" 67 expr := node.(*ast.BinaryExpr) 68 if expr.Op != token.LOR && expr.Op != token.LAND { 69 return 70 } 71 eqNil := expr.Op == token.LOR 72 73 // check that x is "xx == nil" or "xx != nil" 74 x, ok := expr.X.(*ast.BinaryExpr) 75 if !ok { 76 return 77 } 78 if eqNil && x.Op != token.EQL { 79 return 80 } 81 if !eqNil && x.Op != token.NEQ { 82 return 83 } 84 xx, ok := x.X.(*ast.Ident) 85 if !ok { 86 return 87 } 88 if !code.IsNil(pass, x.Y) { 89 return 90 } 91 92 // check that y is "len(xx) == 0" or "len(xx) ... " 93 y, ok := expr.Y.(*ast.BinaryExpr) 94 if !ok { 95 return 96 } 97 if eqNil && y.Op != token.EQL { // must be len(xx) *==* 0 98 return 99 } 100 yx, ok := y.X.(*ast.CallExpr) 101 if !ok { 102 return 103 } 104 if !code.IsCallTo(pass, yx, "len") { 105 return 106 } 107 yxArg, ok := yx.Args[knowledge.Arg("len.v")].(*ast.Ident) 108 if !ok { 109 return 110 } 111 if yxArg.Name != xx.Name { 112 return 113 } 114 115 if eqNil && !code.IsIntegerLiteral(pass, y.Y, constant.MakeInt64(0)) { // must be len(x) == *0* 116 return 117 } 118 119 if !eqNil { 120 isConst, isZero := isConstZero(y.Y) 121 if !isConst { 122 return 123 } 124 switch y.Op { 125 case token.EQL: 126 // avoid false positive for "xx != nil && len(xx) == 0" 127 if isZero { 128 return 129 } 130 case token.GEQ: 131 // avoid false positive for "xx != nil && len(xx) >= 0" 132 if isZero { 133 return 134 } 135 case token.NEQ: 136 // avoid false positive for "xx != nil && len(xx) != <non-zero>" 137 if !isZero { 138 return 139 } 140 case token.GTR: 141 // ok 142 default: 143 return 144 } 145 } 146 147 // finally check that xx type is one of array, slice, map or chan 148 // this is to prevent false positive in case if xx is a pointer to an array 149 typ := pass.TypesInfo.TypeOf(xx) 150 ok = typeutil.All(typ, func(term *types.Term) bool { 151 switch term.Type().Underlying().(type) { 152 case *types.Slice: 153 return true 154 case *types.Map: 155 return true 156 case *types.Chan: 157 return true 158 case *types.Pointer: 159 return false 160 case *types.TypeParam: 161 return false 162 default: 163 lint.ExhaustiveTypeSwitch(term.Type().Underlying()) 164 return false 165 } 166 }) 167 if !ok { 168 return 169 } 170 171 report.Report(pass, expr, fmt.Sprintf("should omit nil check; len() for %s is defined as zero", typ), report.FilterGenerated()) 172 } 173 code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) 174 return nil, nil 175 }