Class: Ast::Merge::FreezeNodeBase
- Inherits:
-
Object
- Object
- Ast::Merge::FreezeNodeBase
- Includes:
- BlockDirective, 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
-
#children ⇒ Array
AST nodes contained within the freeze block.
-
#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.
-
#kind ⇒ Symbol
Always :freeze for FreezeNodeBase.
-
#location ⇒ Location
Returns a location-like object for compatibility with AST nodes.
-
#merge_policy ⇒ Symbol
Freeze blocks are user customizations; dest always wins.
-
#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 BlockDirective
#block_directive?, #covers_line?, #freeze_directive?, #line_range, #nocov_directive?
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)
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/ast/merge/freeze_node_base.rb', line 288 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).
259 260 261 |
# File 'lib/ast/merge/freeze_node_base.rb', line 259 def analysis @analysis end |
#content ⇒ String (readonly)
Returns Content of the freeze block.
244 245 246 |
# File 'lib/ast/merge/freeze_node_base.rb', line 244 def content @content end |
#end_line ⇒ Integer (readonly)
Returns Line number of unfreeze marker (1-based).
241 242 243 |
# File 'lib/ast/merge/freeze_node_base.rb', line 241 def end_line @end_line end |
#end_marker ⇒ String? (readonly)
Returns The freeze end marker text.
250 251 252 |
# File 'lib/ast/merge/freeze_node_base.rb', line 250 def end_marker @end_marker end |
#lines ⇒ Array<String>? (readonly)
Returns Lines within the freeze block.
256 257 258 |
# File 'lib/ast/merge/freeze_node_base.rb', line 256 def lines @lines end |
#nodes ⇒ Array (readonly)
Returns AST nodes contained within the freeze block.
262 263 264 |
# File 'lib/ast/merge/freeze_node_base.rb', line 262 def nodes @nodes end |
#overlapping_nodes ⇒ Array? (readonly)
Returns Nodes that overlap with the freeze block boundaries.
265 266 267 |
# File 'lib/ast/merge/freeze_node_base.rb', line 265 def overlapping_nodes @overlapping_nodes end |
#pattern_type ⇒ Symbol (readonly)
Returns The pattern type used for this freeze node.
253 254 255 |
# File 'lib/ast/merge/freeze_node_base.rb', line 253 def pattern_type @pattern_type end |
#start_line ⇒ Integer (readonly)
Returns Line number of freeze marker (1-based).
238 239 240 |
# File 'lib/ast/merge/freeze_node_base.rb', line 238 def start_line @start_line end |
#start_marker ⇒ String? (readonly)
Returns The freeze start marker text.
247 248 249 |
# File 'lib/ast/merge/freeze_node_base.rb', line 247 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
161 162 163 164 165 166 |
# File 'lib/ast/merge/freeze_node_base.rb', line 161 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
224 225 226 227 228 |
# File 'lib/ast/merge/freeze_node_base.rb', line 224 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
214 215 216 217 218 |
# File 'lib/ast/merge/freeze_node_base.rb', line 214 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.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/ast/merge/freeze_node_base.rb', line 186 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
232 233 234 |
# File 'lib/ast/merge/freeze_node_base.rb', line 232 def pattern_types MARKER_PATTERNS.keys end |
.register_pattern(name, start:, end_pattern:) ⇒ Hash{Symbol => Regexp}
Register a custom marker pattern
138 139 140 141 142 143 144 |
# File 'lib/ast/merge/freeze_node_base.rb', line 138 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
150 151 152 153 154 155 |
# File 'lib/ast/merge/freeze_node_base.rb', line 150 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
#children ⇒ Array
Returns AST nodes contained within the freeze block.
67 |
# File 'lib/ast/merge/freeze_node_base.rb', line 67 def children = @nodes |
#freeze_node? ⇒ Boolean
Check if this is a freeze node (always true for FreezeNode)
363 364 365 |
# File 'lib/ast/merge/freeze_node_base.rb', line 363 def freeze_node? true end |
#inspect ⇒ String
String representation for debugging
385 386 387 |
# File 'lib/ast/merge/freeze_node_base.rb', line 385 def inspect "#<#{self.class.name} lines=#{start_line}..#{end_line} pattern=#{pattern_type}>" end |
#kind ⇒ Symbol
Returns Always :freeze for FreezeNodeBase.
64 |
# File 'lib/ast/merge/freeze_node_base.rb', line 64 def kind = :freeze |
#location ⇒ Location
Returns a location-like object for compatibility with AST nodes
318 319 320 |
# File 'lib/ast/merge/freeze_node_base.rb', line 318 def location @location ||= Location.new(@start_line, @end_line) end |
#merge_policy ⇒ Symbol
Returns Freeze blocks are user customizations; dest always wins.
70 |
# File 'lib/ast/merge/freeze_node_base.rb', line 70 def merge_policy = :destination |
#merge_type ⇒ Symbol Also known as: type
Node type for merge classification
369 370 371 |
# File 'lib/ast/merge/freeze_node_base.rb', line 369 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.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/ast/merge/freeze_node_base.rb', line 335 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 && 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.
379 380 381 |
# File 'lib/ast/merge/freeze_node_base.rb', line 379 def signature [:FreezeNode, @content&.strip] end |
#slice ⇒ String
Returns the freeze block content
357 358 359 |
# File 'lib/ast/merge/freeze_node_base.rb', line 357 def slice @content end |
#to_s ⇒ String
390 391 392 |
# File 'lib/ast/merge/freeze_node_base.rb', line 390 def to_s inspect end |