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.
365 366 367 368 369 370 371 |
# File 'lib/ast/merge/navigable/statement.rb', line 365 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 158 |
# 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.
296 297 298 299 |
# File 'lib/ast/merge/navigable/statement.rb', line 296 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.
222 223 224 225 |
# File 'lib/ast/merge/navigable/statement.rb', line 222 def inner = unwrapped_node inner.respond_to?(:parent) || inner.respond_to?(:next) end |
#inspect ⇒ String
Returns Human-readable representation.
355 356 357 |
# File 'lib/ast/merge/navigable/statement.rb', line 355 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.
333 334 335 336 337 338 339 |
# File 'lib/ast/merge/navigable/statement.rb', line 333 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
373 374 375 |
# File 'lib/ast/merge/navigable/statement.rb', line 373 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.
254 255 256 257 |
# File 'lib/ast/merge/navigable/statement.rb', line 254 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.
273 274 275 |
# File 'lib/ast/merge/navigable/statement.rb', line 273 def signature node.signature if node.respond_to?(:signature) end |
#source_position ⇒ Hash?
Returns Source position info.
285 286 287 |
# File 'lib/ast/merge/navigable/statement.rb', line 285 def source_position node.source_position if node.respond_to?(:source_position) end |
#start_line ⇒ Integer?
Returns Start line number.
290 291 292 293 |
# File 'lib/ast/merge/navigable/statement.rb', line 290 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).
228 229 230 |
# File 'lib/ast/merge/navigable/statement.rb', line 228 def synthetic? ! end |
#take_until {|Statement| ... } ⇒ Array<Statement>
Collect statements until a condition is met.
164 165 166 167 168 169 170 171 172 173 |
# File 'lib/ast/merge/navigable/statement.rb', line 164 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.
278 279 280 281 282 |
# File 'lib/ast/merge/navigable/statement.rb', line 278 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.
317 318 319 320 321 322 323 324 |
# File 'lib/ast/merge/navigable/statement.rb', line 317 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.
360 361 362 |
# File 'lib/ast/merge/navigable/statement.rb', line 360 def to_s text.to_s.strip[0, 50] end |
#tree_children ⇒ Array<Object>
Returns Children in original AST.
198 199 200 201 202 203 204 205 206 207 |
# File 'lib/ast/merge/navigable/statement.rb', line 198 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).
235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/ast/merge/navigable/statement.rb', line 235 def tree_depth depth = 0 current = tree_parent while current depth += 1 # Navigate up through parents break unless current.respond_to?(:parent) current = current.parent end depth end |
#tree_first_child ⇒ Object?
Returns First child in original AST.
210 211 212 213 |
# File 'lib/ast/merge/navigable/statement.rb', line 210 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.
216 217 218 219 |
# File 'lib/ast/merge/navigable/statement.rb', line 216 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.
186 187 188 189 |
# File 'lib/ast/merge/navigable/statement.rb', line 186 def tree_next inner = unwrapped_node inner.next if inner.respond_to?(:next) end |
#tree_parent ⇒ Object?
Returns Parent node in original AST.
180 181 182 183 |
# File 'lib/ast/merge/navigable/statement.rb', line 180 def tree_parent inner = unwrapped_node inner.parent if inner.respond_to?(:parent) end |
#tree_previous ⇒ Object?
Returns Previous sibling in original AST.
192 193 194 195 |
# File 'lib/ast/merge/navigable/statement.rb', line 192 def tree_previous inner = unwrapped_node inner.previous if inner.respond_to?(:previous) end |
#type ⇒ Symbol, String
Returns Node type.
264 265 266 267 268 269 270 |
# File 'lib/ast/merge/navigable/statement.rb', line 264 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.
309 310 311 |
# File 'lib/ast/merge/navigable/statement.rb', line 309 def type?(expected_type) type.to_s == expected_type.to_s end |
#unwrapped_node ⇒ Object
Get the unwrapped inner node.
348 349 350 351 352 |
# File 'lib/ast/merge/navigable/statement.rb', line 348 def unwrapped_node current = node current = current.inner_node while current.respond_to?(:inner_node) && current.inner_node != current current end |