Class: Ast::Merge::Navigable::Statement
- Inherits:
-
Object
- Object
- Ast::Merge::Navigable::Statement
- Defined in:
- lib/ast/merge/navigable/statement.rb
Overview
Wraps any node (parser-backed or synthetic) with uniform navigation.
Provides two levels of navigation:
- Flat list navigation: prev_statement, next_statement, index
- Works for ALL nodes (synthetic and parser-backed)
- Represents position in the flattened statement list
- Tree navigation: tree_parent, tree_next, tree_previous, tree_children
- Only available for parser-backed nodes
- Delegates to inner_node’s tree methods
This allows code to work with the flat list for simple merging,
while still accessing tree structure for section-aware operations.
Instance Attribute Summary collapse
-
#context ⇒ Object?
Optional context/metadata for this statement.
-
#index ⇒ Integer
readonly
Index in the flattened statement list.
-
#next_statement ⇒ Statement?
Next statement in flat list.
-
#node ⇒ Object
readonly
The wrapped node (parser-backed or synthetic).
-
#prev_statement ⇒ Statement?
Previous statement in flat list.
Class Method Summary collapse
-
.build_list(raw_statements) ⇒ Array<Statement>
Build a linked list of Statements from raw statements.
-
.find_first(statements, type: nil, text: nil) {|Statement| ... } ⇒ Statement?
Find the first statement matching criteria.
-
.find_matching(statements, type: nil, text: nil) {|Statement| ... } ⇒ Array<Statement>
Find statements matching a query.
Instance Method Summary collapse
-
#each_following {|Statement| ... } ⇒ Enumerator?
Iterate from this statement to the end (or until block returns false).
-
#end_line ⇒ Integer?
End line number.
-
#first? ⇒ Boolean
True if this is the first statement.
-
#has_tree_navigation? ⇒ Boolean
True if tree navigation is available.
-
#initialize(node, index:) ⇒ Statement
constructor
Initialize a Statement wrapper.
-
#inspect ⇒ String
Human-readable representation.
-
#last? ⇒ Boolean
True if this is the last statement.
-
#method_missing(method, *args, &block) ⇒ Object
Delegate unknown methods to the wrapped node.
-
#next ⇒ Statement?
Next statement in flat list.
-
#node_attribute(name, *aliases) ⇒ Object?
Get an attribute from the underlying node.
-
#previous ⇒ Statement?
Previous statement in flat list.
-
#respond_to_missing?(method, include_private = false) ⇒ Boolean
-
#same_or_shallower_than?(other) ⇒ Boolean
Check if this node is at same or shallower depth than another.
-
#signature ⇒ Array, ...
Node signature for matching.
-
#source_position ⇒ Hash?
Source position info.
-
#start_line ⇒ Integer?
Start line number.
-
#synthetic? ⇒ Boolean
True if this is a synthetic node (no tree navigation).
-
#take_until {|Statement| ... } ⇒ Array<Statement>
Collect statements until a condition is met.
-
#text ⇒ String
Node text content.
-
#text_matches?(pattern) ⇒ Boolean
Check if this node’s text matches a pattern.
-
#to_s ⇒ String
String representation.
-
#tree_children ⇒ Array<Object>
Children in original AST.
-
#tree_depth ⇒ Integer
Calculate the tree depth (distance from root).
-
#tree_first_child ⇒ Object?
First child in original AST.
-
#tree_last_child ⇒ Object?
Last child in original AST.
-
#tree_next ⇒ Object?
Next sibling in original AST.
-
#tree_parent ⇒ Object?
Parent node in original AST.
-
#tree_previous ⇒ Object?
Previous sibling in original AST.
-
#type ⇒ Symbol, String
Node type.
-
#type?(expected_type) ⇒ Boolean
Check if this node matches a type.
-
#unwrapped_node ⇒ Object
Get the unwrapped inner node.
Constructor Details
#initialize(node, index:) ⇒ Statement
Initialize a Statement wrapper.
62 63 64 65 66 67 68 |
# File 'lib/ast/merge/navigable/statement.rb', line 62 def initialize(node, index:) @node = node @index = index @prev_statement = nil @next_statement = nil @context = nil end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args, &block) ⇒ Object
Delegate unknown methods to the wrapped node.
366 367 368 369 370 371 372 |
# File 'lib/ast/merge/navigable/statement.rb', line 366 def method_missing(method, *args, &block) if node.respond_to?(method) node.send(method, *args, &block) else super end end |
Instance Attribute Details
#context ⇒ Object?
Returns Optional context/metadata for this statement.
56 57 58 |
# File 'lib/ast/merge/navigable/statement.rb', line 56 def context @context end |
#index ⇒ Integer (readonly)
Returns Index in the flattened statement list.
47 48 49 |
# File 'lib/ast/merge/navigable/statement.rb', line 47 def index @index end |
#next_statement ⇒ Statement?
Returns Next statement in flat list.
53 54 55 |
# File 'lib/ast/merge/navigable/statement.rb', line 53 def next_statement @next_statement end |
#node ⇒ Object (readonly)
Returns The wrapped node (parser-backed or synthetic).
44 45 46 |
# File 'lib/ast/merge/navigable/statement.rb', line 44 def node @node end |
#prev_statement ⇒ Statement?
Returns Previous statement in flat list.
50 51 52 |
# File 'lib/ast/merge/navigable/statement.rb', line 50 def prev_statement @prev_statement end |
Class Method Details
.build_list(raw_statements) ⇒ Array<Statement>
Build a linked list of Statements from raw statements.
75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/ast/merge/navigable/statement.rb', line 75 def build_list(raw_statements) statements = raw_statements.each_with_index.map do |node, i| new(node, index: i) end # Link siblings in flat list statements.each_cons(2) do |prev_stmt, next_stmt| prev_stmt.next_statement = next_stmt next_stmt.prev_statement = prev_stmt end statements end |
.find_first(statements, type: nil, text: nil) {|Statement| ... } ⇒ Statement?
Find the first statement matching criteria.
116 117 118 |
# File 'lib/ast/merge/navigable/statement.rb', line 116 def find_first(statements, type: nil, text: nil, &block) find_matching(statements, type: type, text: text, &block).first end |
.find_matching(statements, type: nil, text: nil) {|Statement| ... } ⇒ Array<Statement>
Find statements matching a query.
96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/ast/merge/navigable/statement.rb', line 96 def find_matching(statements, type: nil, text: nil, &block) # If no criteria specified, return empty array (nothing to match) return [] if type.nil? && text.nil? && !block_given? statements.select do |stmt| matches = true matches &&= stmt.type.to_s == type.to_s if type matches &&= text.is_a?(Regexp) ? stmt.text.match?(text) : stmt.text.include?(text.to_s) if text matches &&= yield(stmt) if block_given? matches end end |
Instance Method Details
#each_following {|Statement| ... } ⇒ Enumerator?
Iterate from this statement to the end (or until block returns false).
149 150 151 152 153 154 155 156 157 |
# File 'lib/ast/merge/navigable/statement.rb', line 149 def each_following(&block) return to_enum(:each_following) unless block_given? current = self.next while current break unless yield(current) current = current.next end end |
#end_line ⇒ Integer?
Returns End line number.
295 296 297 298 |
# File 'lib/ast/merge/navigable/statement.rb', line 295 def end_line pos = source_position pos[:end_line] if pos end |
#first? ⇒ Boolean
Returns true if this is the first statement.
136 137 138 |
# File 'lib/ast/merge/navigable/statement.rb', line 136 def first? prev_statement.nil? end |
#has_tree_navigation? ⇒ Boolean
Returns true if tree navigation is available.
220 221 222 223 |
# File 'lib/ast/merge/navigable/statement.rb', line 220 def inner = unwrapped_node inner.respond_to?(:parent) || inner.respond_to?(:next) end |
#inspect ⇒ String
Returns Human-readable representation.
356 357 358 |
# File 'lib/ast/merge/navigable/statement.rb', line 356 def inspect "#<Navigable::Statement[#{index}] type=#{type} tree=#{}>" end |
#last? ⇒ Boolean
Returns true if this is the last statement.
141 142 143 |
# File 'lib/ast/merge/navigable/statement.rb', line 141 def last? next_statement.nil? end |
#next ⇒ Statement?
Returns Next statement in flat list.
126 127 128 |
# File 'lib/ast/merge/navigable/statement.rb', line 126 def next next_statement end |
#node_attribute(name, *aliases) ⇒ Object?
Get an attribute from the underlying node.
Tries multiple method names to support different parser APIs.
332 333 334 335 336 337 338 |
# File 'lib/ast/merge/navigable/statement.rb', line 332 def node_attribute(name, *aliases) inner = unwrapped_node [name, *aliases].each do |method_name| return inner.send(method_name) if inner.respond_to?(method_name) end nil end |
#previous ⇒ Statement?
Returns Previous statement in flat list.
131 132 133 |
# File 'lib/ast/merge/navigable/statement.rb', line 131 def previous prev_statement end |
#respond_to_missing?(method, include_private = false) ⇒ Boolean
374 375 376 |
# File 'lib/ast/merge/navigable/statement.rb', line 374 def respond_to_missing?(method, include_private = false) node.respond_to?(method, include_private) || super end |
#same_or_shallower_than?(other) ⇒ Boolean
Check if this node is at same or shallower depth than another.
Useful for determining section boundaries.
253 254 255 256 |
# File 'lib/ast/merge/navigable/statement.rb', line 253 def same_or_shallower_than?(other) other_depth = other.is_a?(Integer) ? other : other.tree_depth tree_depth <= other_depth end |
#signature ⇒ Array, ...
Returns Node signature for matching.
272 273 274 |
# File 'lib/ast/merge/navigable/statement.rb', line 272 def signature node.signature if node.respond_to?(:signature) end |
#source_position ⇒ Hash?
Returns Source position info.
284 285 286 |
# File 'lib/ast/merge/navigable/statement.rb', line 284 def source_position node.source_position if node.respond_to?(:source_position) end |
#start_line ⇒ Integer?
Returns Start line number.
289 290 291 292 |
# File 'lib/ast/merge/navigable/statement.rb', line 289 def start_line pos = source_position pos[:start_line] if pos end |
#synthetic? ⇒ Boolean
Returns true if this is a synthetic node (no tree navigation).
226 227 228 |
# File 'lib/ast/merge/navigable/statement.rb', line 226 def synthetic? ! end |
#take_until {|Statement| ... } ⇒ Array<Statement>
Collect statements until a condition is met.
163 164 165 166 167 168 169 170 171 |
# File 'lib/ast/merge/navigable/statement.rb', line 163 def take_until(&block) result = [] each_following do |stmt| break if yield(stmt) result << stmt true end result end |
#text ⇒ String
Returns Node text content.
277 278 279 280 281 |
# File 'lib/ast/merge/navigable/statement.rb', line 277 def text # TreeHaver nodes (and any node conforming to the unified API) provide #text. # No conditional fallbacks - nodes must conform to the API. node.text.to_s end |
#text_matches?(pattern) ⇒ Boolean
Check if this node’s text matches a pattern.
316 317 318 319 320 321 322 323 |
# File 'lib/ast/merge/navigable/statement.rb', line 316 def text_matches?(pattern) case pattern when Regexp text.match?(pattern) else text.include?(pattern.to_s) end end |
#to_s ⇒ String
Returns String representation.
361 362 363 |
# File 'lib/ast/merge/navigable/statement.rb', line 361 def to_s text.to_s.strip[0, 50] end |
#tree_children ⇒ Array<Object>
Returns Children in original AST.
196 197 198 199 200 201 202 203 204 205 |
# File 'lib/ast/merge/navigable/statement.rb', line 196 def tree_children inner = unwrapped_node if inner.respond_to?(:each) inner.to_a elsif inner.respond_to?(:children) inner.children else [] end end |
#tree_depth ⇒ Integer
Calculate the tree depth (distance from root).
233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/ast/merge/navigable/statement.rb', line 233 def tree_depth depth = 0 current = tree_parent while current depth += 1 # Navigate up through parents if current.respond_to?(:parent) current = current.parent else break end end depth end |
#tree_first_child ⇒ Object?
Returns First child in original AST.
208 209 210 211 |
# File 'lib/ast/merge/navigable/statement.rb', line 208 def tree_first_child inner = unwrapped_node inner.first_child if inner.respond_to?(:first_child) end |
#tree_last_child ⇒ Object?
Returns Last child in original AST.
214 215 216 217 |
# File 'lib/ast/merge/navigable/statement.rb', line 214 def tree_last_child inner = unwrapped_node inner.last_child if inner.respond_to?(:last_child) end |
#tree_next ⇒ Object?
Returns Next sibling in original AST.
184 185 186 187 |
# File 'lib/ast/merge/navigable/statement.rb', line 184 def tree_next inner = unwrapped_node inner.next if inner.respond_to?(:next) end |
#tree_parent ⇒ Object?
Returns Parent node in original AST.
178 179 180 181 |
# File 'lib/ast/merge/navigable/statement.rb', line 178 def tree_parent inner = unwrapped_node inner.parent if inner.respond_to?(:parent) end |
#tree_previous ⇒ Object?
Returns Previous sibling in original AST.
190 191 192 193 |
# File 'lib/ast/merge/navigable/statement.rb', line 190 def tree_previous inner = unwrapped_node inner.previous if inner.respond_to?(:previous) end |
#type ⇒ Symbol, String
Returns Node type.
263 264 265 266 267 268 269 |
# File 'lib/ast/merge/navigable/statement.rb', line 263 def type return node.type if node.respond_to?(:type) # Fallback: derive type from class name (handle anonymous classes) class_name = node.class.name class_name ? class_name.split("::").last : "Anonymous" end |
#type?(expected_type) ⇒ Boolean
Check if this node matches a type.
308 309 310 |
# File 'lib/ast/merge/navigable/statement.rb', line 308 def type?(expected_type) type.to_s == expected_type.to_s end |
#unwrapped_node ⇒ Object
Get the unwrapped inner node.
347 348 349 350 351 352 353 |
# File 'lib/ast/merge/navigable/statement.rb', line 347 def unwrapped_node current = node while current.respond_to?(:inner_node) && current.inner_node != current current = current.inner_node end current end |