Module: Ast::Merge::FileAnalyzable Abstract
- Included in:
- Text::FileAnalysis
- Defined in:
- lib/ast/merge/file_analyzable.rb
Overview
Include this module and implement #compute_node_signature and optionally #fallthrough_node?
Mixin module for file analysis classes across all *-merge gems.
This module provides common functionality for analyzing source files, including freeze block detection, line access, and signature generation. Include this module in your FileAnalysis class and implement the required abstract methods.
Class Method Summary collapse
-
.included(base) ⇒ void
Install the shared attr_reader interface on including analyses.
Instance Method Summary collapse
-
#comment_attachment_for(owner, **options) ⇒ Comment::Attachment
Return a passive shared attachment for a structural owner.
-
#comment_attachment_strategy ⇒ Symbol
Describe the shared attachment strategy used by this analysis.
-
#comment_augmenter(owners: nil, **options) ⇒ Comment::Augmenter
Build a passive shared comment augmenter for the current analysis.
-
#comment_capability ⇒ Comment::Capability
Describe the level of comment support available from this analysis.
-
#comment_node_at(_line_num) ⇒ Ast::Merge::Comment::Line?
Return the shared comment node at a specific line.
-
#comment_nodes ⇒ Array<Ast::Merge::Comment::Line>
Return all shared comment nodes known to this analysis.
-
#comment_region_for_range(range, kind:, **options) ⇒ Comment::Region
Return a shared comment region spanning a requested line range.
-
#comment_support_style ⇒ Comment::SupportStyle
Describe how the merge pipeline will own and emit comments for this analysis.
-
#compute_node_signature(node) ⇒ Array?
abstract
Compute default signature for a node.
-
#fallthrough_node?(value) ⇒ Boolean
Check if a value represents a fallthrough node that should be used for default signature computation.
-
#feature_profile ⇒ Ruleset::FeatureProfile
Describe the current analysis using spec-aligned merge feature terms.
-
#freeze_block_at(line_num) ⇒ FreezeNodeBase?
Get the freeze block containing the given line, if any.
-
#freeze_blocks ⇒ Array<Freezable>
Get all freeze blocks/nodes from statements.
-
#generate_signature(node) ⇒ Array?
Generate signature for a node.
-
#in_freeze_block?(line_num) ⇒ Boolean
Check if a line is within a freeze block.
-
#layout_attachment_for(owner, **options) ⇒ Layout::Attachment
Return a passive shared layout attachment for a structural owner.
-
#layout_augmenter(owners: nil, **options) ⇒ Layout::Augmenter
Build a passive shared layout augmenter for the current analysis.
- #layout_owner_supported?(owner) ⇒ Boolean
-
#line_at(line_num) ⇒ String?
Get a specific line (1-indexed).
-
#merge_augmented_comment_attachment_with_layout(owner, tracker_attachment:, **options) ⇒ Comment::Attachment
Prefer an augmenter-built attachment when available, then merge the result with shared layout ownership data.
-
#merge_comment_attachment_with_layout(owner, comment_attachment, **options) ⇒ Comment::Attachment
Merge a format-specific comment attachment with shared inferred layout gaps.
-
#normalize_layout_owned_comment_attachment(attachment) ⇒ Comment::Attachment?
Mark a leading region as floating when shared layout already proves it is gap-owned, without changing the underlying region content.
-
#normalize_tracked_comment_attachment_with_layout(owner, tracker_attachment:, **options) ⇒ Comment::Attachment
Merge a tracker-built attachment with shared layout ownership and then normalize any layout-owned leading region metadata.
-
#normalized_line(line_num) ⇒ String?
Get a normalized line (whitespace-trimmed, for comparison).
-
#owner_leading_comment_freeze?(owner, freeze_token: self.freeze_token, **options) ⇒ Boolean
Check whether an owner's leading normalized comment attachment contains a freeze directive.
-
#owner_leading_comment_unfreeze?(owner, freeze_token: self.freeze_token, **options) ⇒ Boolean
Check whether an owner's leading normalized comment attachment contains an unfreeze directive.
- #ruleset_attachment_strategy ⇒ Object
- #ruleset_capabilities ⇒ Object
- #ruleset_comment_style ⇒ Object
- #ruleset_delegation_policies ⇒ Object
- #ruleset_logical_owners ⇒ Object
- #ruleset_match_key ⇒ Object
- #ruleset_owner_selector ⇒ Object
- #ruleset_read_strategy ⇒ Object
- #ruleset_render_family ⇒ Object
- #ruleset_repair_policies ⇒ Object
- #ruleset_surfaces ⇒ Object
-
#shared_comment_attachment_for(owner, tracker_attachment: nil, strategy: comment_attachment_strategy, **options) ⇒ Comment::Attachment
Build a comment attachment through one of the shared runtime strategies.
-
#shared_comment_support_style(source:, style:, read_strategy:, capability: comment_capability.level) ⇒ Comment::SupportStyle
Build a shared support-style declaration for analyses that already know their read/write model.
-
#signature_at(index) ⇒ Array?
Get structural signature for a statement at given index.
-
#statements ⇒ Array
Get all top-level statements (nodes and freeze blocks).
Class Method Details
.included(base) ⇒ void
This method returns an undefined value.
Install the shared attr_reader interface on including analyses.
54 55 56 57 58 |
# File 'lib/ast/merge/file_analyzable.rb', line 54 def included(base) base.class_eval do attr_reader(:source, :lines, :freeze_token, :signature_generator) end end |
Instance Method Details
#comment_attachment_for(owner, **options) ⇒ Comment::Attachment
Return a passive shared attachment for a structural owner.
Analyses with comment ownership data should override this to return meaningful leading/inline/trailing regions. The default implementation returns an empty attachment so callers can migrate incrementally.
309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/ast/merge/file_analyzable.rb', line 309 def (owner, **) = layout_owner_supported?(owner) ? (owner, **) : nil Comment::Attachment.new( owner: owner, leading_gap: &.leading_gap, trailing_gap: &.trailing_gap, metadata: { source: :file_analyzable_default }.merge() ) end |
#comment_attachment_strategy ⇒ Symbol
Describe the shared attachment strategy used by this analysis.
Analyses that rely on one of the common tracked/augmented attachment
shapes can override this and delegate comment_attachment_for through
#shared_comment_attachment_for instead of open-coding the merge path.
329 330 331 |
# File 'lib/ast/merge/file_analyzable.rb', line 329 def :layout_only end |
#comment_augmenter(owners: nil, **options) ⇒ Comment::Augmenter
Build a passive shared comment augmenter for the current analysis.
Format-specific analyses should override this when they can provide native or tracked comment data. The default implementation preserves the shared augmenter interface while reporting the analysis capability.
461 462 463 464 465 466 467 468 469 |
# File 'lib/ast/merge/file_analyzable.rb', line 461 def comment_augmenter(owners: nil, **) Comment::Augmenter.new( lines: lines, comments: [], owners: owners || comment_augmenter_default_owners, capability: comment_capability, ** ) end |
#comment_capability ⇒ Comment::Capability
Describe the level of comment support available from this analysis.
Analyses with native or augmented comment support should override this. The default implementation advertises no comment support while still providing the shared hook surface used by merge gems.
130 131 132 |
# File 'lib/ast/merge/file_analyzable.rb', line 130 def comment_capability Comment::Capability.none(source: :file_analyzable_default) end |
#comment_node_at(_line_num) ⇒ Ast::Merge::Comment::Line?
Return the shared comment node at a specific line.
275 276 277 |
# File 'lib/ast/merge/file_analyzable.rb', line 275 def comment_node_at(_line_num) nil end |
#comment_nodes ⇒ Array<Ast::Merge::Comment::Line>
Return all shared comment nodes known to this analysis.
137 138 139 |
# File 'lib/ast/merge/file_analyzable.rb', line 137 def comment_nodes [] end |
#comment_region_for_range(range, kind:, **options) ⇒ Comment::Region
Return a shared comment region spanning a requested line range.
Analyses with comment support should override this to return attached or tracked comment content. The default implementation returns an empty region of the requested kind so callers can rely on the hook surface.
289 290 291 292 293 294 295 296 297 298 |
# File 'lib/ast/merge/file_analyzable.rb', line 289 def comment_region_for_range(range, kind:, **) Comment::Region.new( kind: kind, nodes: [], metadata: { source: :file_analyzable_default, range: range }.merge() ) end |
#comment_support_style ⇒ Comment::SupportStyle
Describe how the merge pipeline will own and emit comments for this analysis.
Analyses with comment support should override this to advertise their intended read/write model independently from raw parser capability.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/ast/merge/file_analyzable.rb', line 148 def comment_support_style capability = comment_capability details = { source: :file_analyzable_default, capability: capability.level } if capability.none? Comment::SupportStyle.unavailable(**details) elsif capability.source_augmented? Ruleset::SupportStyleResolver.call( read: :source_augmented_portable_write, source: details[:source], capability: details[:capability], style: details[:style] ) else Ruleset::SupportStyleResolver.call( read: :native_read_portable_write, source: details[:source], capability: details[:capability], style: details[:style] ) end end |
#compute_node_signature(node) ⇒ Array?
Compute default signature for a node. This method must be implemented by including classes.
744 745 746 |
# File 'lib/ast/merge/file_analyzable.rb', line 744 def compute_node_signature(node) raise NotImplementedError, "#{self.class} must implement #compute_node_signature" end |
#fallthrough_node?(value) ⇒ Boolean
Check if a value represents a fallthrough node that should be used for default signature computation.
When a signature_generator returns a non-Array/nil value, we check if it's a "fallthrough" node that should be passed to compute_node_signature. This includes:
- AstNode instances (custom AST nodes like Comment::Line)
- Freezable nodes (frozen wrappers)
- FreezeNodeBase instances
- NodeTyping::Wrapper instances (unwrapped to get the underlying node)
Override this method to add custom node type detection for your parser.
730 731 732 733 734 735 736 |
# File 'lib/ast/merge/file_analyzable.rb', line 730 def fallthrough_node?(value) value.is_a?(AstNode) || value.is_a?(Freezable) || value.is_a?(FreezeNodeBase) || value.is_a?(NodeTyping::Wrapper) || value.is_a?(BlockDirective) end |
#feature_profile ⇒ Ruleset::FeatureProfile
Describe the current analysis using spec-aligned merge feature terms.
Analyses can override the individual ruleset_* hooks below to make this
surface more specific without replacing the whole profile object.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/ast/merge/file_analyzable.rb', line 202 def feature_profile Ruleset::FeatureProfile.new( owner_selector: ruleset_owner_selector, match_key: ruleset_match_key, read_strategy: ruleset_read_strategy, attachment_strategy: , comment_style: ruleset_comment_style, render_family: ruleset_render_family, comment_capability: comment_capability, support_style: comment_support_style, capabilities: ruleset_capabilities, logical_owners: ruleset_logical_owners, repair_policies: ruleset_repair_policies, surfaces: ruleset_surfaces, delegation_policies: ruleset_delegation_policies, metadata: { source: :file_analyzable_default } ) end |
#freeze_block_at(line_num) ⇒ FreezeNodeBase?
Get the freeze block containing the given line, if any.
90 91 92 |
# File 'lib/ast/merge/file_analyzable.rb', line 90 def freeze_block_at(line_num) freeze_blocks.find { |fb| fb.location.cover?(line_num) } end |
#freeze_blocks ⇒ Array<Freezable>
Get all freeze blocks/nodes from statements. Includes both traditional FreezeNodeBase blocks and Freezable-wrapped nodes.
74 75 76 |
# File 'lib/ast/merge/file_analyzable.rb', line 74 def freeze_blocks statements.select { |node| node.is_a?(Freezable) } end |
#generate_signature(node) ⇒ Array?
Generate signature for a node.
Signatures are used to match nodes between template and destination files. Two nodes with the same signature are considered "the same" for merge purposes, allowing the merger to decide which version to keep based on preference settings.
Signature Generation Flow
-
FreezeNodeBase (explicit freeze blocks like
# token:freeze ... # token:unfreeze): Uses content-based signature viafreeze_signature. This ensures explicit freeze blocks match between files based on their actual content. -
FrozenWrapper (AST nodes with freeze markers in leading comments): The wrapper is unwrapped first to get the underlying AST node. The signature is then generated from the underlying node, NOT the wrapper. This is critical because the freeze marker only affects merge preference (destination wins), not matching. Two nodes should match by their structural identity even if their content differs slightly.
-
Custom signature_generator: If provided, receives the unwrapped node and can:
- Return an Array signature (e.g.,
[:gem, "foo"]) - used directly - Return
nil- node gets no signature, won't be matched - Return the node (fallthrough) - default signature computation is used
- Return an Array signature (e.g.,
-
Default computation: Falls through to
compute_node_signaturefor parser-specific default signature generation.
Why FrozenWrapper Must Be Unwrapped
Consider a gemspec with a frozen gem_version variable:
Template: Destination:
# kettle-dev:freeze # kettle-dev:freeze
# Comment # Comment
# kettle-dev:unfreeze # More comments
gem_version = "1.0" # kettle-dev:unfreeze
gem_version = "1.0"
Both have a gem_version assignment with a freeze marker in leading comments.
The assignments are wrapped in FrozenWrapper, but their CONTENT differs
(template has fewer comments in the freeze block).
If we generated signatures from the wrapper (which delegates slice to the
full node content), they would NOT match and both would be output - duplicating
the freeze block!
By unwrapping first, we generate signatures from the underlying
LocalVariableWriteNode, which matches by variable name (gem_version),
ensuring only ONE version is output (the destination version, since it's frozen).
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 |
# File 'lib/ast/merge/file_analyzable.rb', line 614 def generate_signature(node) # ========================================================================== # CASE 1: FreezeNodeBase (explicit freeze blocks) # ========================================================================== # FreezeNodeBase represents an explicit freeze block delimited by markers: # # token:freeze # ... content ... # # token:unfreeze # # These are standalone structural elements (not attached to AST nodes). # They use content-based signatures so identical freeze blocks match. # This is different from FrozenWrapper which wraps AST nodes. return node.freeze_signature if node.is_a?(FreezeNodeBase) # ========================================================================== # CASE 2: Unwrap FrozenWrapper (and other wrappers) # ========================================================================== # FrozenWrapper wraps AST nodes that have freeze markers in their leading # comments. The wrapper marks the node as "frozen" (prefer destination), # but for MATCHING purposes, we need the underlying node's identity. # # Example: A `gem_version = ...` assignment wrapped in FrozenWrapper should # match another `gem_version = ...` assignment by variable name, not by # the full content of the assignment (which may differ). # # CRITICAL: We must unwrap BEFORE calling the signature_generator so it # receives the actual AST node type (e.g., Prism::LocalVariableWriteNode) # rather than the wrapper (FrozenWrapper). Otherwise, type-based signature # generators (like kettle-jem's gemspec generator) won't recognize the node # and will fall through to default handling incorrectly. actual_node = node.respond_to?(:unwrap) ? node.unwrap : node result = if signature_generator # ========================================================================== # CASE 3: Custom signature generator # ========================================================================== # Pass the UNWRAPPED node to the custom generator. This ensures: # - Type checks work (e.g., `node.is_a?(Prism::CallNode)`) # - The generator sees the real AST structure # - Frozen nodes match by their underlying identity # # NOTE: For TreeHaver-based backends, the node already has a unified API # with #text, #type, #source_position methods. For other backends, they # must conform to the same API (either via TreeHaver or equivalent adapter). custom_result = signature_generator.call(actual_node) case custom_result when Array, nil # Generator returned a final signature or nil - use as-is custom_result else # Generator returned a node (fallthrough) - compute default signature. # # Two conditions indicate the generator is deferring to default handling: # 1. Identity equality: the generator returned the exact same object it # received (classic "I don't handle this type" passthrough pattern). # 2. Known node type: the result is a recognised wrapper/node class. # # If neither applies, treat the return value as a final custom signature # (e.g. a String, Symbol, or other non-node key). if custom_result.equal?(actual_node) || fallthrough_node?(custom_result) # Special case: if fallthrough result is Freezable, use freeze_signature # This handles cases where the generator wraps a node in Freezable if custom_result.is_a?(Freezable) custom_result.freeze_signature else # Unwrap any wrapper and compute default signature unwrapped = custom_result.respond_to?(:unwrap) ? custom_result.unwrap : custom_result compute_node_signature(unwrapped) end else # Non-node return value - pass through (allows arbitrary signature types) custom_result end end else # ========================================================================== # CASE 4: No custom generator - use default computation # ========================================================================== # Pass the UNWRAPPED node to compute_node_signature. This is critical # because compute_node_signature uses type checking (e.g., case statements # matching Prism::DefNode, Prism::CallNode, etc.). If we pass a # FrozenWrapper, it won't match any of those types and will fall through # to a generic handler, producing incorrect signatures. # # For FrozenWrapper nodes, the underlying AST node determines the signature # (e.g., method name for DefNode, gem name for CallNode). The wrapper only # affects merge preference (destination wins), not matching. compute_node_signature(actual_node) end if result DebugLogger.debug('Generated signature', { node_type: node.class.name.split('::').last, signature: result, generator: signature_generator ? 'custom' : 'default' }) end result end |
#in_freeze_block?(line_num) ⇒ Boolean
Check if a line is within a freeze block.
82 83 84 |
# File 'lib/ast/merge/file_analyzable.rb', line 82 def in_freeze_block?(line_num) freeze_blocks.any? { |fb| fb.location.cover?(line_num) } end |
#layout_attachment_for(owner, **options) ⇒ Layout::Attachment
Return a passive shared layout attachment for a structural owner.
Analyses with explicit blank-line ownership data should override this to return meaningful leading/trailing gap attachments. The default implementation returns an empty attachment so callers can migrate incrementally.
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 |
# File 'lib/ast/merge/file_analyzable.rb', line 481 def (owner, **) owners = layout_augmenter_default_owners augmenter = if owners.any? { |candidate| candidate.equal?(owner) } layout_augmenter(**) else layout_augmenter(owners: [owner], **) end = augmenter.(owner) Layout::Attachment.new( owner: owner, leading_gap: &.leading_gap, trailing_gap: &.trailing_gap, metadata: { source: :file_analyzable_default }.merge() ) end |
#layout_augmenter(owners: nil, **options) ⇒ Layout::Augmenter
Build a passive shared layout augmenter for the current analysis.
Format-specific analyses should override this when they can provide a stronger ownership model for blank-line gaps. The default implementation preserves the shared hook surface while inferring only directly adjacent blank-line runs from source lines and owner ranges.
511 512 513 514 515 516 517 |
# File 'lib/ast/merge/file_analyzable.rb', line 511 def layout_augmenter(owners: nil, **) Layout::Augmenter.new( lines: lines, owners: owners || layout_augmenter_default_owners, ** ) end |
#layout_owner_supported?(owner) ⇒ Boolean
519 520 521 522 523 524 |
# File 'lib/ast/merge/file_analyzable.rb', line 519 def layout_owner_supported?(owner) owner.respond_to?(:start_line) && owner.respond_to?(:end_line) && !owner.start_line.nil? && !owner.end_line.nil? end |
#line_at(line_num) ⇒ String?
Get a specific line (1-indexed).
108 109 110 111 112 |
# File 'lib/ast/merge/file_analyzable.rb', line 108 def line_at(line_num) return if line_num < 1 lines[line_num - 1] end |
#merge_augmented_comment_attachment_with_layout(owner, tracker_attachment:, **options) ⇒ Comment::Attachment
Prefer an augmenter-built attachment when available, then merge the result with shared layout ownership data.
This lets analyses use richer augmenter ownership (for example floating leading regions) while preserving tracker-based fallback behavior.
394 395 396 397 398 399 400 401 402 |
# File 'lib/ast/merge/file_analyzable.rb', line 394 def (owner, tracker_attachment:, **) = comment_augmenter(owners: [owner]).(owner) ( owner, || , ** ) end |
#merge_comment_attachment_with_layout(owner, comment_attachment, **options) ⇒ Comment::Attachment
Merge a format-specific comment attachment with shared inferred layout gaps.
This lets analyses keep custom comment-region logic while still exposing the shared gap ownership model through the returned comment attachment.
369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/ast/merge/file_analyzable.rb', line 369 def (owner, , **) = layout_owner_supported?(owner) ? (owner, **) : nil Comment::Attachment.new( owner: &.owner || owner, leading_region: &.leading_region, inline_region: &.inline_region, trailing_region: &.trailing_region, orphan_regions: &.orphan_regions || [], leading_gap: &.leading_gap, trailing_gap: &.trailing_gap, metadata: (&. || {}).merge() ) end |
#normalize_layout_owned_comment_attachment(attachment) ⇒ Comment::Attachment?
Mark a leading region as floating when shared layout already proves it is gap-owned, without changing the underlying region content.
Some format analyzers still build attachments from tracker-level comment
regions that do not set floating: true even though the shared layout
augmenter has already established a leading gap. This helper preserves the
attachment shape while normalizing that metadata in one place.
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/ast/merge/file_analyzable.rb', line 431 def () return unless &.leading_region return if .leading_region.floating? return unless .leading_gap Comment::Attachment.new( owner: .owner, leading_region: Comment::Region.new( kind: .leading_region.kind, nodes: .leading_region.nodes, metadata: .leading_region..merge(floating: true) ), inline_region: .inline_region, trailing_region: .trailing_region, orphan_regions: .orphan_regions, leading_gap: .leading_gap, trailing_gap: .trailing_gap, metadata: . ) end |
#normalize_tracked_comment_attachment_with_layout(owner, tracker_attachment:, **options) ⇒ Comment::Attachment
Merge a tracker-built attachment with shared layout ownership and then normalize any layout-owned leading region metadata.
411 412 413 414 415 416 417 418 419 |
# File 'lib/ast/merge/file_analyzable.rb', line 411 def (owner, tracker_attachment:, **) ( ( owner, , ** ) ) end |
#normalized_line(line_num) ⇒ String?
Get a normalized line (whitespace-trimmed, for comparison).
118 119 120 121 |
# File 'lib/ast/merge/file_analyzable.rb', line 118 def normalized_line(line_num) line = line_at(line_num) line&.strip end |
#owner_leading_comment_freeze?(owner, freeze_token: self.freeze_token, **options) ⇒ Boolean
Check whether an owner's leading normalized comment attachment contains a freeze directive.
532 533 534 535 |
# File 'lib/ast/merge/file_analyzable.rb', line 532 def owner_leading_comment_freeze?(owner, freeze_token: self.freeze_token, **) = (owner, **) .respond_to?(:leading_freeze?) && .leading_freeze?(freeze_token) end |
#owner_leading_comment_unfreeze?(owner, freeze_token: self.freeze_token, **options) ⇒ Boolean
Check whether an owner's leading normalized comment attachment contains an unfreeze directive.
543 544 545 546 |
# File 'lib/ast/merge/file_analyzable.rb', line 543 def owner_leading_comment_unfreeze?(owner, freeze_token: self.freeze_token, **) = (owner, **) .respond_to?(:leading_unfreeze?) && .leading_unfreeze?(freeze_token) end |
#ruleset_attachment_strategy ⇒ Object
236 237 238 |
# File 'lib/ast/merge/file_analyzable.rb', line 236 def end |
#ruleset_capabilities ⇒ Object
248 249 250 251 252 253 |
# File 'lib/ast/merge/file_analyzable.rb', line 248 def ruleset_capabilities { layout_aware: true, logical_owner: ruleset_logical_owners.any? } end |
#ruleset_comment_style ⇒ Object
240 241 242 |
# File 'lib/ast/merge/file_analyzable.rb', line 240 def ruleset_comment_style comment_support_style.details[:style] || comment_capability.details[:style] end |
#ruleset_delegation_policies ⇒ Object
267 268 269 |
# File 'lib/ast/merge/file_analyzable.rb', line 267 def ruleset_delegation_policies [] end |
#ruleset_logical_owners ⇒ Object
255 256 257 |
# File 'lib/ast/merge/file_analyzable.rb', line 255 def ruleset_logical_owners {} end |
#ruleset_match_key ⇒ Object
225 226 227 |
# File 'lib/ast/merge/file_analyzable.rb', line 225 def ruleset_match_key :signature end |
#ruleset_owner_selector ⇒ Object
221 222 223 |
# File 'lib/ast/merge/file_analyzable.rb', line 221 def ruleset_owner_selector :shared_default end |
#ruleset_read_strategy ⇒ Object
229 230 231 232 233 234 |
# File 'lib/ast/merge/file_analyzable.rb', line 229 def ruleset_read_strategy support_style = comment_support_style return unless support_style.respond_to?(:available?) && support_style.available? support_style.style end |
#ruleset_render_family ⇒ Object
244 245 246 |
# File 'lib/ast/merge/file_analyzable.rb', line 244 def ruleset_render_family nil end |
#ruleset_repair_policies ⇒ Object
259 260 261 |
# File 'lib/ast/merge/file_analyzable.rb', line 259 def ruleset_repair_policies [] end |
#ruleset_surfaces ⇒ Object
263 264 265 |
# File 'lib/ast/merge/file_analyzable.rb', line 263 def ruleset_surfaces [] end |
#shared_comment_attachment_for(owner, tracker_attachment: nil, strategy: comment_attachment_strategy, **options) ⇒ Comment::Attachment
Build a comment attachment through one of the shared runtime strategies.
This surfaces the current repeated runtime seam explicitly so format-specific analyses can declare which ownership path they use while still preserving any custom tracker attachment selection they need.
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/ast/merge/file_analyzable.rb', line 344 def (owner, tracker_attachment: nil, strategy: , **) case strategy when :layout_only (owner, nil, **) when :tracker_layout_merge (owner, , **) when :augmenter_preferred_tracker_layout (owner, tracker_attachment: , **) when :normalize_tracked_layout_merge (owner, tracker_attachment: , **) else raise ArgumentError, "Unknown comment attachment strategy: #{strategy.inspect}" end end |
#shared_comment_support_style(source:, style:, read_strategy:, capability: comment_capability.level) ⇒ Comment::SupportStyle
Build a shared support-style declaration for analyses that already know their read/write model.
182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/ast/merge/file_analyzable.rb', line 182 def shared_comment_support_style(source:, style:, read_strategy:, capability: comment_capability.level) details = { source: source, capability: capability, style: style } Ruleset::SupportStyleResolver.call(read: read_strategy, **details) rescue ArgumentError => e raise unless e..start_with?('Unknown ruleset read strategy:') raise ArgumentError, "Unknown comment support read strategy: #{read_strategy.inspect}" end |
#signature_at(index) ⇒ Array?
Get structural signature for a statement at given index.
98 99 100 101 102 |
# File 'lib/ast/merge/file_analyzable.rb', line 98 def signature_at(index) return if index.negative? || index >= statements.length generate_signature(statements[index]) end |
#statements ⇒ Array
Get all top-level statements (nodes and freeze blocks). Override this method in including classes to return the appropriate collection. The default implementation returns @statements if set, otherwise an empty array.
66 67 68 |
# File 'lib/ast/merge/file_analyzable.rb', line 66 def statements @statements ||= [] end |