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  }