Class: Ast::Merge::FreezeNodeBase
- Inherits:
-
Object
- Object
- Ast::Merge::FreezeNodeBase
- Includes:
- Freezable
- Defined in:
- lib/ast/merge/freeze_node_base.rb
Overview
Base class for freeze block nodes in AST merge libraries.
A freeze block is a section marked with freeze/unfreeze comment markers that
should be preserved from the destination during merges. The entire content
between the markers is treated as opaque and matched by content identity.
Key Distinction from FrozenWrapper
FreezeNodeBase represents explicit freeze blocks with clear boundaries:
- Starts with
# token:freeze(or equivalent in other comment styles) - Ends with
# token:unfreeze - The content between markers is opaque and preserved verbatim
- Matched by CONTENT identity via
freeze_signature
In contrast, NodeTyping::FrozenWrapper represents AST nodes with freeze markers
in their leading comments:
- The marker appears in the node’s leading comments, not as a block boundary
- The node is still a structural AST element (e.g., a
gemcall) - Matched by the underlying node’s STRUCTURAL identity
Signature Generation Behavior
When FileAnalyzable#generate_signature encounters a FreezeNodeBase, it uses
the freeze_signature method directly, which returns [:FreezeNode, content].
This ensures that explicit freeze blocks are matched by their exact content.
This class provides shared functionality for file-type-specific implementations
(e.g., Prism::Merge::FreezeNode, Psych::Merge::FreezeNode).
Supports multiple comment syntax styles via configurable marker patterns:
:hash_comment- Ruby/Python/YAML style (# freeze-begin/# freeze-end):html_comment- HTML/Markdown style (<!-- freeze-begin -->/<!-- freeze-end -->):c_style_line- C/JavaScript line comments (// freeze-begin/// freeze-end):c_style_block- C/JavaScript block comments (/* freeze-begin *///* freeze-end */)
Defined Under Namespace
Classes: InvalidStructureError, Location
Constant Summary collapse
- MARKER_PATTERNS =
Pattern configuration for freeze block markers.
Mutable to allow runtime registration of custom patterns. { hash_comment: { start: /^\s*#\s*[\w-]+:freeze\b/i, end: /^\s*#\s*[\w-]+:unfreeze\b/i, }, html_comment: { start: /^\s*<!--\s*[\w-]+:freeze\b.*-->/i, end: /^\s*<!--\s*[\w-]+:unfreeze\b.*-->/i, }, c_style_line: { start: %r{^\s*//\s*[\w-]+:freeze\b}i, end: %r{^\s*//\s*[\w-]+:unfreeze\b}i, }, c_style_block: { start: %r{^\s*/\*\s*[\w-]+:freeze\b.*\*/}i, end: %r{^\s*/\*\s*[\w-]+:unfreeze\b.*\*/}i, }, }
- DEFAULT_PATTERN =
Default pattern when none specified
:hash_comment
Instance Attribute Summary collapse
-
#analysis ⇒ Object?
readonly
Reference to FileAnalysis (for subclasses that need it).
-
#content ⇒ String
readonly
Content of the freeze block.
-
#end_line ⇒ Integer
readonly
Line number of unfreeze marker (1-based).
-
#end_marker ⇒ String?
readonly
The freeze end marker text.
-
#lines ⇒ Array<String>?
readonly
Lines within the freeze block.
-
#nodes ⇒ Array
readonly
AST nodes contained within the freeze block.
-
#overlapping_nodes ⇒ Array?
readonly
Nodes that overlap with the freeze block boundaries.
-
#pattern_type ⇒ Symbol
readonly
The pattern type used for this freeze node.
-
#start_line ⇒ Integer
readonly
Line number of freeze marker (1-based).
-
#start_marker ⇒ String?
readonly
The freeze start marker text.
Class Method Summary collapse
-
.end_pattern(pattern_type = DEFAULT_PATTERN) ⇒ Regexp
Get end marker pattern for a given pattern type.
-
.freeze_end?(line, pattern_type = DEFAULT_PATTERN) ⇒ Boolean
Check if a line matches a freeze end marker.
-
.freeze_start?(line, pattern_type = DEFAULT_PATTERN) ⇒ Boolean
Check if a line matches a freeze start marker.
-
.pattern_for(pattern_type = DEFAULT_PATTERN, token = nil) ⇒ Hash{Symbol => Regexp}, Regexp
Get both start and end patterns for a given pattern type When token is provided, returns a combined pattern with capture groups for marker type (freeze/unfreeze) and optional reason.
-
.pattern_types ⇒ Array<Symbol>
Available pattern types.
-
.register_pattern(name, start:, end_pattern:) ⇒ Hash{Symbol => Regexp}
Register a custom marker pattern.
-
.start_pattern(pattern_type = DEFAULT_PATTERN) ⇒ Regexp
Get start marker pattern for a given pattern type.
Instance Method Summary collapse
-
#freeze_node? ⇒ Boolean
Check if this is a freeze node (always true for FreezeNode).
-
#initialize(start_line:, end_line:, lines: nil, analysis: nil, content: nil, nodes: [], overlapping_nodes: nil, start_marker: nil, end_marker: nil, pattern_type: DEFAULT_PATTERN, reason: nil) ⇒ FreezeNodeBase
constructor
Initialize a freeze node.
-
#inspect ⇒ String
String representation for debugging.
-
#location ⇒ Location
Returns a location-like object for compatibility with AST nodes.
-
#merge_type ⇒ Symbol
(also: #type)
Node type for merge classification.
-
#reason ⇒ String?
Extract the reason/comment from the freeze start marker.
-
#signature ⇒ Array
Returns a stable signature for this freeze block.
-
#slice ⇒ String
Returns the freeze block content.
-
#to_s ⇒ String
Methods included from Freezable
Constructor Details
#initialize(start_line:, end_line:, lines: nil, analysis: nil, content: nil, nodes: [], overlapping_nodes: nil, start_marker: nil, end_marker: nil, pattern_type: DEFAULT_PATTERN, reason: nil) ⇒ FreezeNodeBase
Initialize a freeze node.
This unified constructor accepts all parameters that any *-merge gem might need.
Subclasses should call super with the parameters they use.
Content can be provided via:
lines:- Direct array of line stringsanalysis:- FileAnalysis reference (lines extracted via analysis.lines)content:- Direct content string (will be split into lines)
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/ast/merge/freeze_node_base.rb', line 278 def initialize( start_line:, end_line:, lines: nil, analysis: nil, content: nil, nodes: [], overlapping_nodes: nil, start_marker: nil, end_marker: nil, pattern_type: DEFAULT_PATTERN, reason: nil ) @start_line = start_line @end_line = end_line @start_marker = start_marker @end_marker = end_marker @pattern_type = pattern_type @explicit_reason = reason @nodes = nodes @overlapping_nodes = overlapping_nodes @analysis = analysis # Handle content from various sources @lines = resolve_lines(lines, analysis, content) @content = resolve_content(@lines, content) end |
Instance Attribute Details
#analysis ⇒ Object? (readonly)
Returns Reference to FileAnalysis (for subclasses that need it).
249 250 251 |
# File 'lib/ast/merge/freeze_node_base.rb', line 249 def analysis @analysis end |
#content ⇒ String (readonly)
Returns Content of the freeze block.
234 235 236 |
# File 'lib/ast/merge/freeze_node_base.rb', line 234 def content @content end |
#end_line ⇒ Integer (readonly)
Returns Line number of unfreeze marker (1-based).
231 232 233 |
# File 'lib/ast/merge/freeze_node_base.rb', line 231 def end_line @end_line end |
#end_marker ⇒ String? (readonly)
Returns The freeze end marker text.
240 241 242 |
# File 'lib/ast/merge/freeze_node_base.rb', line 240 def end_marker @end_marker end |
#lines ⇒ Array<String>? (readonly)
Returns Lines within the freeze block.
246 247 248 |
# File 'lib/ast/merge/freeze_node_base.rb', line 246 def lines @lines end |
#nodes ⇒ Array (readonly)
Returns AST nodes contained within the freeze block.
252 253 254 |
# File 'lib/ast/merge/freeze_node_base.rb', line 252 def nodes @nodes end |
#overlapping_nodes ⇒ Array? (readonly)
Returns Nodes that overlap with the freeze block boundaries.
255 256 257 |
# File 'lib/ast/merge/freeze_node_base.rb', line 255 def overlapping_nodes @overlapping_nodes end |
#pattern_type ⇒ Symbol (readonly)
Returns The pattern type used for this freeze node.
243 244 245 |
# File 'lib/ast/merge/freeze_node_base.rb', line 243 def pattern_type @pattern_type end |
#start_line ⇒ Integer (readonly)
Returns Line number of freeze marker (1-based).
228 229 230 |
# File 'lib/ast/merge/freeze_node_base.rb', line 228 def start_line @start_line end |
#start_marker ⇒ String? (readonly)
Returns The freeze start marker text.
237 238 239 |
# File 'lib/ast/merge/freeze_node_base.rb', line 237 def start_marker @start_marker end |
Class Method Details
.end_pattern(pattern_type = DEFAULT_PATTERN) ⇒ Regexp
Get end marker pattern for a given pattern type
151 152 153 154 155 156 |
# File 'lib/ast/merge/freeze_node_base.rb', line 151 def end_pattern(pattern_type = DEFAULT_PATTERN) patterns = MARKER_PATTERNS[pattern_type] raise ArgumentError, "Unknown pattern type: #{pattern_type}" unless patterns patterns[:end] end |
.freeze_end?(line, pattern_type = DEFAULT_PATTERN) ⇒ Boolean
Check if a line matches a freeze end marker
214 215 216 217 218 |
# File 'lib/ast/merge/freeze_node_base.rb', line 214 def freeze_end?(line, pattern_type = DEFAULT_PATTERN) return false if line.nil? end_pattern(pattern_type).match?(line) end |
.freeze_start?(line, pattern_type = DEFAULT_PATTERN) ⇒ Boolean
Check if a line matches a freeze start marker
204 205 206 207 208 |
# File 'lib/ast/merge/freeze_node_base.rb', line 204 def freeze_start?(line, pattern_type = DEFAULT_PATTERN) return false if line.nil? start_pattern(pattern_type).match?(line) end |
.pattern_for(pattern_type = DEFAULT_PATTERN, token = nil) ⇒ Hash{Symbol => Regexp}, Regexp
Get both start and end patterns for a given pattern type
When token is provided, returns a combined pattern with capture groups
for marker type (freeze/unfreeze) and optional reason.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/ast/merge/freeze_node_base.rb', line 176 def pattern_for(pattern_type = DEFAULT_PATTERN, token = nil) raise ArgumentError, "Unknown pattern type: #{pattern_type}" unless MARKER_PATTERNS.key?(pattern_type) # If no token provided, return the static patterns hash return MARKER_PATTERNS[pattern_type] unless token # Build a combined pattern with capture groups for the specific token escaped_token = Regexp.escape(token) case pattern_type when :hash_comment /^\s*#\s*#{escaped_token}:(freeze|unfreeze)\b\s*(.*)?$/i when :html_comment /^\s*<!--\s*#{escaped_token}:(freeze|unfreeze)(?:\s+(.+?))?\s*-->/i when :c_style_line %r{^\s*//\s*#{escaped_token}:(freeze|unfreeze)\b\s*(.*)?$}i when :c_style_block %r{^\s*/\*\s*#{escaped_token}:(freeze|unfreeze)\b\s*(.*)? *\*/}i else # Fallback for custom registered patterns - can't build token-specific raise ArgumentError, "Cannot build token-specific pattern for custom type: #{pattern_type}" end end |
.pattern_types ⇒ Array<Symbol>
Available pattern types
222 223 224 |
# File 'lib/ast/merge/freeze_node_base.rb', line 222 def pattern_types MARKER_PATTERNS.keys end |
.register_pattern(name, start:, end_pattern:) ⇒ Hash{Symbol => Regexp}
Register a custom marker pattern
128 129 130 131 132 133 134 |
# File 'lib/ast/merge/freeze_node_base.rb', line 128 def register_pattern(name, start:, end_pattern:) raise ArgumentError, "Pattern :#{name} already registered" if MARKER_PATTERNS.key?(name) raise ArgumentError, "Start pattern must be a Regexp" unless start.is_a?(Regexp) raise ArgumentError, "End pattern must be a Regexp" unless end_pattern.is_a?(Regexp) MARKER_PATTERNS[name] = {start: start, end: end_pattern} end |
.start_pattern(pattern_type = DEFAULT_PATTERN) ⇒ Regexp
Get start marker pattern for a given pattern type
140 141 142 143 144 145 |
# File 'lib/ast/merge/freeze_node_base.rb', line 140 def start_pattern(pattern_type = DEFAULT_PATTERN) patterns = MARKER_PATTERNS[pattern_type] raise ArgumentError, "Unknown pattern type: #{pattern_type}" unless patterns patterns[:start] end |
Instance Method Details
#freeze_node? ⇒ Boolean
Check if this is a freeze node (always true for FreezeNode)
353 354 355 |
# File 'lib/ast/merge/freeze_node_base.rb', line 353 def freeze_node? true end |
#inspect ⇒ String
String representation for debugging
375 376 377 |
# File 'lib/ast/merge/freeze_node_base.rb', line 375 def inspect "#<#{self.class.name} lines=#{start_line}..#{end_line} pattern=#{pattern_type}>" end |
#location ⇒ Location
Returns a location-like object for compatibility with AST nodes
308 309 310 |
# File 'lib/ast/merge/freeze_node_base.rb', line 308 def location @location ||= Location.new(@start_line, @end_line) end |
#merge_type ⇒ Symbol Also known as: type
Node type for merge classification
359 360 361 |
# File 'lib/ast/merge/freeze_node_base.rb', line 359 def merge_type :freeze_block end |
#reason ⇒ String?
Extract the reason/comment from the freeze start marker.
The reason is any text after the freeze directive.
If an explicit reason was provided at initialization, that takes precedence.
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/ast/merge/freeze_node_base.rb', line 325 def reason # Return explicit reason if provided at initialization return @explicit_reason if @explicit_reason return unless @start_marker # Use the canonical pattern which has capture group 2 for reason # We need to extract the token from the marker first token = extract_token_from_marker return unless token pattern = self.class.pattern_for(@pattern_type, token) match = @start_marker.match(pattern) return unless match # Capture group 2 is the reason text reason_text = match[2]&.strip reason_text&.empty? ? nil : reason_text end |
#signature ⇒ Array
Returns a stable signature for this freeze block.
Override in subclasses for file-type-specific normalization.
369 370 371 |
# File 'lib/ast/merge/freeze_node_base.rb', line 369 def signature [:FreezeNode, @content&.strip] end |
#slice ⇒ String
Returns the freeze block content
347 348 349 |
# File 'lib/ast/merge/freeze_node_base.rb', line 347 def slice @content end |
#to_s ⇒ String
380 381 382 |
# File 'lib/ast/merge/freeze_node_base.rb', line 380 def to_s inspect end |