Class: Ast::Merge::NodeWrapperBase Abstract
- Inherits:
-
Object
- Object
- Ast::Merge::NodeWrapperBase
- Defined in:
- lib/ast/merge/node_wrapper_base.rb
Overview
Subclass and implement #compute_signature
Base class for format-specific node wrappers used in *-merge gems.
This provides common functionality for wrapping TreeHaver nodes with:
- Source context (lines, source string)
- Line information (start_line, end_line)
- Comment associations (leading_comments, inline_comment)
- Content extraction (text, content)
- Signature generation (abstract)
Relationship to NodeTyping::Wrapper
This class is DIFFERENT from Ast::Merge::NodeTyping::Wrapper:
-
NodeWrapperBase: Provides format-specific functionality (line info, signatures, comments, type predicates). Used to wrap raw TreeHaver nodes with rich context needed for merging.
-
NodeTyping::Wrapper: Adds a custom
merge_typeattribute for merge classification. Used by SmartMergerBase to apply custom typing rules.
A node CAN be wrapped by both:
NodeTyping::Wrapper(Toml::Merge::NodeWrapper(tree_sitter_node))
The NodeTyping.unwrap method handles unwrapping NodeTyping::Wrapper,
while NodeWrapperBase#node provides access to the underlying TreeHaver node.
Subclass Responsibilities
Subclasses MUST implement:
#compute_signature(node)- Generate a signature for node matching
Subclasses SHOULD implement format-specific type predicates:
- TOML:
#table?,#pair?,#array_of_tables?, etc. - JSON:
#object?,#array?,#pair?, etc. - Bash:
#function_definition?,#variable_assignment?, etc.
Instance Attribute Summary collapse
-
#end_line ⇒ Integer?
readonly
End line (1-based).
-
#inline_comment ⇒ Hash?
readonly
Inline/trailing comment on the same line.
-
#leading_comments ⇒ Array<Hash>
readonly
Leading comments associated with this node.
-
#lines ⇒ Array<String>
readonly
Source lines for content extraction.
-
#node ⇒ Object
readonly
The wrapped TreeHaver node.
-
#source ⇒ String
readonly
The original source string.
-
#start_line ⇒ Integer?
readonly
Start line (1-based).
Instance Method Summary collapse
-
#children ⇒ Array<NodeWrapperBase>
Get children wrapped as NodeWrappers.
-
#comment_attachment(style: nil, **metadata) ⇒ Comment::Attachment
Build a shared attachment from the wrapper's raw comment hashes.
-
#container? ⇒ Boolean
Check if this node is a container (has children for merging).
-
#content ⇒ String
Get the content for this node from source lines.
-
#freeze_node? ⇒ Boolean
Check if this is a freeze node.
-
#initialize(node, lines:, source: nil, leading_comments: [], inline_comment: nil, **options) ⇒ NodeWrapperBase
constructor
Initialize the node wrapper with source context.
-
#inline_comment_region(style: nil, **metadata) ⇒ Comment::Region?
Convert the wrapper's raw inline comment hash into a shared region.
-
#inspect ⇒ String
String representation for debugging.
-
#leading_comment_freeze?(freeze_token, style: nil, **metadata) ⇒ Boolean
Check whether the wrapper's leading comment region contains a freeze directive.
-
#leading_comment_region(style: nil, **metadata) ⇒ Comment::Region?
Convert the wrapper's raw leading comment hashes into a shared region.
-
#leading_comment_unfreeze?(freeze_token, style: nil, **metadata) ⇒ Boolean
Check whether the wrapper's leading comment region contains an unfreeze directive.
-
#leaf? ⇒ Boolean
Check if this node is a leaf (no mergeable children).
-
#mergeable_children ⇒ Array<NodeWrapperBase>
Get mergeable children - the semantically meaningful children for tree merging.
-
#node_text(ts_node) ⇒ String
Extract text from a node using byte positions.
-
#node_wrapper? ⇒ Boolean
Returns true to indicate this is a node wrapper.
-
#process_additional_options(options) ⇒ Object
Process additional options.
-
#signature ⇒ Array?
Generate a signature for this node for matching purposes.
-
#text ⇒ String
Get the text content for this node by extracting from source using byte positions.
-
#type ⇒ Symbol
Get the node type as a symbol.
-
#type?(type_name) ⇒ Boolean
Check if this node has a specific type.
-
#underlying_node ⇒ Object
Get the underlying TreeHaver node.
Constructor Details
#initialize(node, lines:, source: nil, leading_comments: [], inline_comment: nil, **options) ⇒ NodeWrapperBase
Initialize the node wrapper with source context.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 92 def initialize(node, lines:, source: nil, leading_comments: [], inline_comment: nil, **) @node = node @lines = lines @source = source || lines.join("\n") @leading_comments = leading_comments @inline_comment = inline_comment # Store additional options for subclasses to use () # Extract line information from the node (0-indexed to 1-indexed) extract_line_info(node) # Handle edge case where end_line might be before start_line @end_line = @start_line if @start_line && @end_line && @end_line < @start_line end |
Instance Attribute Details
#end_line ⇒ Integer? (readonly)
Returns End line (1-based).
82 83 84 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 82 def end_line @end_line end |
#inline_comment ⇒ Hash? (readonly)
Returns Inline/trailing comment on the same line.
76 77 78 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 76 def inline_comment @inline_comment end |
#leading_comments ⇒ Array<Hash> (readonly)
Returns Leading comments associated with this node.
73 74 75 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 73 def leading_comments @leading_comments end |
#lines ⇒ Array<String> (readonly)
Returns Source lines for content extraction.
67 68 69 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 67 def lines @lines end |
#node ⇒ Object (readonly)
Returns The wrapped TreeHaver node.
64 65 66 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 64 def node @node end |
#source ⇒ String (readonly)
Returns The original source string.
70 71 72 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 70 def source @source end |
#start_line ⇒ Integer? (readonly)
Returns Start line (1-based).
79 80 81 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 79 def start_line @start_line end |
Instance Method Details
#children ⇒ Array<NodeWrapperBase>
Get children wrapped as NodeWrappers. Override in subclasses to return wrapped children.
251 252 253 254 255 256 257 258 259 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 251 def children return [] unless @node.respond_to?(:each) result = [] @node.each do |child| result << wrap_child(child) end result end |
#comment_attachment(style: nil, **metadata) ⇒ Comment::Attachment
Build a shared attachment from the wrapper's raw comment hashes.
This provides an incremental bridge from existing leading_comments /
inline_comment flows to the normalized merge-facing comment model.
202 203 204 205 206 207 208 209 210 211 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 202 def (style: nil, **) Comment::Attachment.new( owner: self, leading_region: leading_comment_region(style: style, **), inline_region: inline_comment_region(style: style, **), metadata: { source: :node_wrapper_base }.merge() ) end |
#container? ⇒ Boolean
Check if this node is a container (has children for merging). Override in subclasses to define container types.
238 239 240 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 238 def container? false end |
#content ⇒ String
Get the content for this node from source lines.
170 171 172 173 174 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 170 def content return '' unless @start_line && @end_line (@start_line..@end_line).map { |ln| @lines[ln - 1] }.compact.join("\n") end |
#freeze_node? ⇒ Boolean
Check if this is a freeze node. Override in subclasses if freeze node detection differs.
139 140 141 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 139 def freeze_node? false end |
#inline_comment_region(style: nil, **metadata) ⇒ Comment::Region?
Convert the wrapper's raw inline comment hash into a shared region.
190 191 192 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 190 def inline_comment_region(style: nil, **) comments_to_region(:inline, inline_comment ? [inline_comment] : [], style: style, **) end |
#inspect ⇒ String
String representation for debugging.
270 271 272 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 270 def inspect "#<#{self.class.name} type=#{@node.type} lines=#{@start_line}..#{@end_line}>" end |
#leading_comment_freeze?(freeze_token, style: nil, **metadata) ⇒ Boolean
Check whether the wrapper's leading comment region contains a freeze directive.
219 220 221 222 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 219 def leading_comment_freeze?(freeze_token, style: nil, **) region = leading_comment_region(style: style, **) region.respond_to?(:freeze?) && region.freeze?(freeze_token) end |
#leading_comment_region(style: nil, **metadata) ⇒ Comment::Region?
Convert the wrapper's raw leading comment hashes into a shared region.
181 182 183 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 181 def leading_comment_region(style: nil, **) comments_to_region(:leading, leading_comments, style: style, **) end |
#leading_comment_unfreeze?(freeze_token, style: nil, **metadata) ⇒ Boolean
Check whether the wrapper's leading comment region contains an unfreeze directive.
230 231 232 233 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 230 def leading_comment_unfreeze?(freeze_token, style: nil, **) region = leading_comment_region(style: style, **) region.respond_to?(:unfreeze?) && region.unfreeze?(freeze_token) end |
#leaf? ⇒ Boolean
Check if this node is a leaf (no mergeable children).
244 245 246 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 244 def leaf? !container? end |
#mergeable_children ⇒ Array<NodeWrapperBase>
Get mergeable children - the semantically meaningful children for tree merging. Override in subclasses to return format-specific mergeable children.
264 265 266 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 264 def mergeable_children children end |
#node_text(ts_node) ⇒ String
Extract text from a node using byte positions. Uses byteslice for correct multi-byte character handling (e.g. emoji).
153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 153 def node_text(ts_node) return '' unless ts_node.respond_to?(:start_byte) && ts_node.respond_to?(:end_byte) length = ts_node.end_byte - ts_node.start_byte text = @source.byteslice(ts_node.start_byte, length) return text if text&.valid_encoding? # Some parser adapters expose character offsets through byte-named # methods. Fall back before scrubbing so Unicode source is preserved. text = @source[ts_node.start_byte, length] return text if text&.valid_encoding? text.to_s.scrub end |
#node_wrapper? ⇒ Boolean
Returns true to indicate this is a node wrapper. Used to distinguish from NodeTyping::Wrapper.
277 278 279 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 277 def node_wrapper? true end |
#process_additional_options(options) ⇒ Object
Process additional options. Override in subclasses to handle format-specific options.
111 112 113 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 111 def () # Default: no-op. Subclasses can override to process options like :backend, :document_root end |
#signature ⇒ Array?
Generate a signature for this node for matching purposes. Signatures are used to identify corresponding nodes between template and destination.
119 120 121 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 119 def signature compute_signature(@node) end |
#text ⇒ String
Get the text content for this node by extracting from source using byte positions.
145 146 147 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 145 def text node_text(@node) end |
#type ⇒ Symbol
Get the node type as a symbol.
125 126 127 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 125 def type @node.type.to_sym end |
#type?(type_name) ⇒ Boolean
Check if this node has a specific type.
132 133 134 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 132 def type?(type_name) @node.type.to_s == type_name.to_s end |
#underlying_node ⇒ Object
Get the underlying TreeHaver node. Note: This is NOT the same as NodeTyping::Wrapper#unwrap which removes the typing wrapper. This method provides access to the raw parser node.
285 286 287 |
# File 'lib/ast/merge/node_wrapper_base.rb', line 285 def @node end |