lint.go 1.9 KB

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