github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/internal/sharedcheck/lint.go (about) 1 package sharedcheck 2 3 import ( 4 "go/ast" 5 "go/types" 6 7 "github.com/golangci/go-tools/lint" 8 . "github.com/golangci/go-tools/lint/lintdsl" 9 "github.com/golangci/go-tools/ssa" 10 ) 11 12 func CheckRangeStringRunes(j *lint.Job) { 13 for _, ssafn := range j.Program.InitialFunctions { 14 fn := func(node ast.Node) bool { 15 rng, ok := node.(*ast.RangeStmt) 16 if !ok || !IsBlank(rng.Key) { 17 return true 18 } 19 20 v, _ := ssafn.ValueForExpr(rng.X) 21 22 // Check that we're converting from string to []rune 23 val, _ := v.(*ssa.Convert) 24 if val == nil { 25 return true 26 } 27 Tsrc, ok := val.X.Type().(*types.Basic) 28 if !ok || Tsrc.Kind() != types.String { 29 return true 30 } 31 Tdst, ok := val.Type().(*types.Slice) 32 if !ok { 33 return true 34 } 35 TdstElem, ok := Tdst.Elem().(*types.Basic) 36 if !ok || TdstElem.Kind() != types.Int32 { 37 return true 38 } 39 40 // Check that the result of the conversion is only used to 41 // range over 42 refs := val.Referrers() 43 if refs == nil { 44 return true 45 } 46 47 // Expect two refs: one for obtaining the length of the slice, 48 // one for accessing the elements 49 if len(FilterDebug(*refs)) != 2 { 50 // TODO(dh): right now, we check that only one place 51 // refers to our slice. This will miss cases such as 52 // ranging over the slice twice. Ideally, we'd ensure that 53 // the slice is only used for ranging over (without 54 // accessing the key), but that is harder to do because in 55 // SSA form, ranging over a slice looks like an ordinary 56 // loop with index increments and slice accesses. We'd 57 // have to look at the associated AST node to check that 58 // it's a range statement. 59 return true 60 } 61 62 j.Errorf(rng, "should range over string, not []rune(string)") 63 64 return true 65 } 66 Inspect(ssafn.Syntax(), fn) 67 } 68 }