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 ‘gem` call)
-
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.
-
#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 strings
-
‘analysis:` - FileAnalysis reference (lines extracted via analysis.lines)
-
‘content:` - Direct content string (will be split into lines)
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 305 306 |
# File 'lib/ast/merge/freeze_node_base.rb', line 280 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).
251 252 253 |
# File 'lib/ast/merge/freeze_node_base.rb', line 251 def analysis @analysis end |
#content ⇒ String (readonly)
Returns Content of the freeze block.
236 237 238 |
# File 'lib/ast/merge/freeze_node_base.rb', line 236 def content @content end |
#end_line ⇒ Integer (readonly)
Returns Line number of unfreeze marker (1-based).
233 234 235 |
# File 'lib/ast/merge/freeze_node_base.rb', line 233 def end_line @end_line end |
#end_marker ⇒ String? (readonly)
Returns The freeze end marker text.
242 243 244 |
# File 'lib/ast/merge/freeze_node_base.rb', line 242 def end_marker @end_marker end |
#lines ⇒ Array<String>? (readonly)
Returns Lines within the freeze block.
248 249 250 |
# File 'lib/ast/merge/freeze_node_base.rb', line 248 def lines @lines end |
#nodes ⇒ Array (readonly)
Returns AST nodes contained within the freeze block.
254 255 256 |
# File 'lib/ast/merge/freeze_node_base.rb', line 254 def nodes @nodes end |
#overlapping_nodes ⇒ Array? (readonly)
Returns Nodes that overlap with the freeze block boundaries.
257 258 259 |
# File 'lib/ast/merge/freeze_node_base.rb', line 257 def overlapping_nodes @overlapping_nodes end |
#pattern_type ⇒ Symbol (readonly)
Returns The pattern type used for this freeze node.
245 246 247 |
# File 'lib/ast/merge/freeze_node_base.rb', line 245 def pattern_type @pattern_type end |
#start_line ⇒ Integer (readonly)
Returns Line number of freeze marker (1-based).
230 231 232 |
# File 'lib/ast/merge/freeze_node_base.rb', line 230 def start_line @start_line end |
#start_marker ⇒ String? (readonly)
Returns The freeze start marker text.
239 240 241 |
# File 'lib/ast/merge/freeze_node_base.rb', line 239 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
153 154 155 156 157 158 |
# File 'lib/ast/merge/freeze_node_base.rb', line 153 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
216 217 218 219 220 |
# File 'lib/ast/merge/freeze_node_base.rb', line 216 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
206 207 208 209 210 |
# File 'lib/ast/merge/freeze_node_base.rb', line 206 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.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/ast/merge/freeze_node_base.rb', line 178 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
224 225 226 |
# File 'lib/ast/merge/freeze_node_base.rb', line 224 def pattern_types MARKER_PATTERNS.keys end |
.register_pattern(name, start:, end_pattern:) ⇒ Hash{Symbol => Regexp}
Register a custom marker pattern
130 131 132 133 134 135 136 |
# File 'lib/ast/merge/freeze_node_base.rb', line 130 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
142 143 144 145 146 147 |
# File 'lib/ast/merge/freeze_node_base.rb', line 142 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)
355 356 357 |
# File 'lib/ast/merge/freeze_node_base.rb', line 355 def freeze_node? true end |
#inspect ⇒ String
String representation for debugging
368 369 370 |
# File 'lib/ast/merge/freeze_node_base.rb', line 368 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
310 311 312 |
# File 'lib/ast/merge/freeze_node_base.rb', line 310 def location @location ||= Location.new(@start_line, @end_line) 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.
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/ast/merge/freeze_node_base.rb', line 327 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.
362 363 364 |
# File 'lib/ast/merge/freeze_node_base.rb', line 362 def signature [:FreezeNode, @content&.strip] end |
#slice ⇒ String
Returns the freeze block content
349 350 351 |
# File 'lib/ast/merge/freeze_node_base.rb', line 349 def slice @content end |
#to_s ⇒ String
373 374 375 |
# File 'lib/ast/merge/freeze_node_base.rb', line 373 def to_s inspect end |