Module: RailsCodeHealth::ASTHelpers
- Included in:
- RailsAnalyzer, RubyAnalyzer
- Defined in:
- lib/rails_code_health/ast_helpers.rb
Constant Summary collapse
- SCOPE_BOUNDARY_TYPES =
%i[class module def defs sclass].freeze
- VISIBILITY_MODIFIERS =
%i[public private protected].freeze
Instance Method Summary collapse
-
#class_body_sends(class_node, method_name) ⇒ Object
Returns direct ‘:send` nodes in the class body matching the given method name.
-
#defs_by_visibility(class_node) ⇒ Object
Returns a hash { public: [def_nodes], private: [def_nodes], protected: [def_nodes] } for the given class node.
-
#erb_ruby_fragments(source) ⇒ Object
Yields each Ruby code fragment inside <% %> / <%= %> / <%- %> tags.
-
#find_nodes(node, type) {|node| ... } ⇒ Object
Yields every descendant (and the node itself) of the given type.
-
#find_nodes_in_scope(node, type) {|node| ... } ⇒ Object
Like find_nodes, but does not descend into nested class/module/def/defs/sclass nodes.
Instance Method Details
#class_body_sends(class_node, method_name) ⇒ Object
Returns direct ‘:send` nodes in the class body matching the given method name. Does not descend into nested classes, modules, or method bodies.
NOTE: calls wrapped in a block (e.g., ‘included do … end` in concerns) are NOT matched — the wrapping :block node is not a :send. Use class_body_children directly and inspect :block nodes if you need to find macro calls inside such wrappers.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rails_code_health/ast_helpers.rb', line 61 def class_body_sends(class_node, method_name) matches = [] return matches unless class_node.is_a?(Parser::AST::Node) return matches unless %i[class module].include?(class_node.type) class_body_children(class_node).each do |child| next unless child.is_a?(Parser::AST::Node) next unless child.type == :send # Receiver-less send only (e.g., `has_many :foo`, not `MyMod.has_many :foo`) next unless child.children[0].nil? matches << child if child.children[1] == method_name end matches end |
#defs_by_visibility(class_node) ⇒ Object
Returns a hash { public: [def_nodes], private: [def_nodes], protected: [def_nodes] } for the given class node. Handles bare modifier blocks and inline ‘private def foo`. Defs inside `class << self` are excluded (the :sclass node is not descended into).
NOTE: ‘private :symbol_name` / `private :a, :b` forms are NOT handled —methods so marked stay classified as :public. Only bare modifier blocks (`private` on its own line) and inline `private def foo` are recognized.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/rails_code_health/ast_helpers.rb', line 22 def defs_by_visibility(class_node) result = { public: [], private: [], protected: [] } return result unless class_node.is_a?(Parser::AST::Node) return result unless %i[class module].include?(class_node.type) current = :public body = class_body_children(class_node) body.each do |child| next unless child.is_a?(Parser::AST::Node) if child.type == :send && child.children[0].nil? && VISIBILITY_MODIFIERS.include?(child.children[1]) modifier = child.children[1] if child.children.length == 2 # bare modifier — changes default for subsequent defs current = modifier else # inline form: `private def foo` or `private :foo` inline_target = child.children[2] if inline_target.is_a?(Parser::AST::Node) && inline_target.type == :def result[modifier] << inline_target end end elsif child.type == :def result[current] << child end end result end |
#erb_ruby_fragments(source) ⇒ Object
Yields each Ruby code fragment inside <% %> / <%= %> / <%- %> tags. Multi-line tags are handled. ERB comment tags (<%# … %>) are skipped. Returns an Enumerator if no block given.
80 81 82 83 84 85 86 |
# File 'lib/rails_code_health/ast_helpers.rb', line 80 def erb_ruby_fragments(source) return enum_for(:erb_ruby_fragments, source) unless block_given? source.scan(/<%(?!#)=?-?(.*?)-?%>/m) do |match| yield match[0] end end |
#find_nodes(node, type) {|node| ... } ⇒ Object
Yields every descendant (and the node itself) of the given type. Descends into all children.
8 9 10 11 12 13 |
# File 'lib/rails_code_health/ast_helpers.rb', line 8 def find_nodes(node, type, &block) return unless node.is_a?(Parser::AST::Node) yield(node) if node.type == type node.children.each { |child| find_nodes(child, type, &block) } end |
#find_nodes_in_scope(node, type) {|node| ... } ⇒ Object
Like find_nodes, but does not descend into nested class/module/def/defs/sclass nodes. The starting node itself is inspected; all children are inspected, but nested scope-introducing constructs are not recursed into.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rails_code_health/ast_helpers.rb', line 91 def find_nodes_in_scope(node, type, &block) return unless node.is_a?(Parser::AST::Node) yield(node) if node.type == type node.children.each do |child| next unless child.is_a?(Parser::AST::Node) # Scope-boundary children get yielded if they match the target type, # but we do not recurse into them. if SCOPE_BOUNDARY_TYPES.include?(child.type) yield(child) if child.type == type next end find_nodes_in_scope(child, type, &block) end end |