/src/ameba/ast/util.cr

https://github.com/veelenga/ameba · Crystal · 154 lines · 101 code · 15 blank · 38 comment · 24 complexity · a0316eb28630d6192f1a489c7f1d8896 MD5 · raw file

  1. # Utility module for Ameba's rules.
  2. module Ameba::AST::Util
  3. # Returns true if current `node` is a literal, false otherwise.
  4. def literal?(node)
  5. case node
  6. when Crystal::NilLiteral,
  7. Crystal::BoolLiteral,
  8. Crystal::NumberLiteral,
  9. Crystal::CharLiteral,
  10. Crystal::StringLiteral,
  11. Crystal::SymbolLiteral,
  12. Crystal::RegexLiteral,
  13. Crystal::ProcLiteral,
  14. Crystal::MacroLiteral
  15. true
  16. when Crystal::RangeLiteral
  17. literal?(node.from) && literal?(node.to)
  18. when Crystal::ArrayLiteral,
  19. Crystal::TupleLiteral
  20. node.elements.all? { |el| literal?(el) }
  21. when Crystal::HashLiteral
  22. node.entries.all? { |entry| literal?(entry.key) && literal?(entry.value) }
  23. when Crystal::NamedTupleLiteral
  24. node.entries.all? { |entry| literal?(entry.value) }
  25. else
  26. false
  27. end
  28. end
  29. # Returns a source code for the current node.
  30. # This method uses `node.location` and `node.end_location`
  31. # to determine and cut a piece of source of the node.
  32. def node_source(node, code_lines)
  33. loc, end_loc = node.location, node.end_location
  34. return unless loc && end_loc
  35. line, column = loc.line_number - 1, loc.column_number - 1
  36. end_line, end_column = end_loc.line_number - 1, end_loc.column_number - 1
  37. node_lines = code_lines[line..end_line]
  38. first_line, last_line = node_lines[0]?, node_lines[-1]?
  39. return unless first_line && last_line
  40. node_lines[0] = first_line.sub(0...column, "")
  41. if line == end_line # one line
  42. end_column = end_column - column
  43. last_line = node_lines[0]
  44. end
  45. node_lines[-1] = last_line.sub(end_column + 1...last_line.size, "")
  46. node_lines
  47. end
  48. # Returns true if node is a flow command, false - otherwise.
  49. # Node represents a flow command if it is a control expression,
  50. # or special call node that interrupts execution (i.e. raise, exit, abort).
  51. def flow_command?(node, in_loop)
  52. case node
  53. when Crystal::Return
  54. true
  55. when Crystal::Break, Crystal::Next
  56. in_loop
  57. when Crystal::Call
  58. raise?(node) || exit?(node) || abort?(node)
  59. else
  60. false
  61. end
  62. end
  63. # Returns true if node is a flow expression, false if not.
  64. # Node represents a flow expression if it is full-filled by a flow command.
  65. #
  66. # For example, this node is a flow expression, because each branch contains
  67. # a flow command `return`:
  68. #
  69. # ```
  70. # if a > 0
  71. # return :positive
  72. # elsif a < 0
  73. # return :negative
  74. # else
  75. # return :zero
  76. # end
  77. # ```
  78. #
  79. # This node is a not a flow expression:
  80. #
  81. # ```
  82. # if a > 0
  83. # return :positive
  84. # end
  85. # ```
  86. #
  87. # That's because not all branches return(i.e. `else` is missing).
  88. #
  89. def flow_expression?(node, in_loop = false)
  90. return true if flow_command? node, in_loop
  91. case node
  92. when Crystal::If, Crystal::Unless
  93. flow_expressions? [node.then, node.else], in_loop
  94. when Crystal::BinaryOp
  95. flow_expression? node.left, in_loop
  96. when Crystal::Case
  97. flow_expressions? [node.whens, node.else].flatten, in_loop
  98. when Crystal::ExceptionHandler
  99. flow_expressions? [node.else || node.body, node.rescues].flatten, in_loop
  100. when Crystal::While, Crystal::Until
  101. flow_expression? node.body, in_loop
  102. when Crystal::Rescue, Crystal::When
  103. flow_expression? node.body, in_loop
  104. when Crystal::Expressions
  105. node.expressions.any? { |exp| flow_expression? exp, in_loop }
  106. else
  107. false
  108. end
  109. end
  110. private def flow_expressions?(nodes, in_loop)
  111. nodes.all? { |exp| flow_expression? exp, in_loop }
  112. end
  113. # Returns true if node represents `raise` method call.
  114. def raise?(node)
  115. node.is_a?(Crystal::Call) &&
  116. node.name == "raise" && node.args.size == 1 && node.obj.nil?
  117. end
  118. # Returns true if node represents `exit` method call.
  119. def exit?(node)
  120. node.is_a?(Crystal::Call) &&
  121. node.name == "exit" && node.args.size <= 1 && node.obj.nil?
  122. end
  123. # Returns true if node represents `abort` method call.
  124. def abort?(node)
  125. node.is_a?(Crystal::Call) &&
  126. node.name == "abort" && node.args.size <= 2 && node.obj.nil?
  127. end
  128. # Returns true if node represents a loop.
  129. def loop?(node)
  130. case node
  131. when Crystal::While, Crystal::Until
  132. true
  133. when Crystal::Call
  134. node.name == "loop" && node.args.size == 0 && node.obj.nil?
  135. else
  136. false
  137. end
  138. end
  139. end