Class: Prism::Merge::TopLevelMergeRunner
- Inherits:
-
Object
- Object
- Prism::Merge::TopLevelMergeRunner
- Includes:
- Ast::Merge::JaccardSimilarity, Ast::Merge::TrailingGroups::DestIterate
- Defined in:
- lib/prism/merge/top_level_merge_runner.rb
Overview
Orchestrates the top-level merge of two Ruby files parsed by Prism.
Uses a three-phase matching strategy to pair template nodes with destination nodes before emitting the merged result:
-
Phase 1 — Exact signature match. Pairs nodes whose structural signatures (method name + params, call name + args, etc.) are identical. This is the fastest phase and handles the vast majority of nodes.
-
Phase 2 — Similarity match at the same depth. For nodes left unmatched after Phase 1, computes body-text Jaccard similarity (Ast::Merge::JaccardSimilarity) to pair probable renames or minor refactors. Only operates on the residual unmatched sets, keeping cost proportional to orphan count.
-
Phase 3 — Cross-depth match. For nodes still unmatched after Phase 2, searches recursively into destination subtrees (conditionals, begin/rescue, call blocks) to detect "moved" nodes — e.g. a template top-level
eval_gemfilethat the destination wrapped inside anifblock. Only runs on the tiny residual set surviving both previous phases.
The phase ordering is critical: exact matches are locked in first so that fuzzy Phase 2 scoring cannot accidentally consume a node that belongs to a later exact match at a different position.
Constant Summary collapse
- SIMILARITY_THRESHOLD =
Minimum Jaccard score for Phase 2 body-text matching. Below this threshold, two nodes are too dissimilar to pair.
0.6- MIN_BODY_TOKENS =
Minimum token count for meaningful Jaccard comparison. Nodes with fewer body tokens than this are skipped in Phase 2 to avoid spurious matches on trivially short bodies.
3
Instance Attribute Summary collapse
-
#merger ⇒ SmartMerger
readonly
The merger instance driving this run.
Instance Method Summary collapse
-
#initialize(merger:) ⇒ TopLevelMergeRunner
constructor
A new instance of TopLevelMergeRunner.
-
#merge ⇒ MergeResult
Execute the three-phase merge and return the result.
Constructor Details
#initialize(merger:) ⇒ TopLevelMergeRunner
Returns a new instance of TopLevelMergeRunner.
59 60 61 |
# File 'lib/prism/merge/top_level_merge_runner.rb', line 59 def initialize(merger:) @merger = merger end |
Instance Attribute Details
#merger ⇒ SmartMerger (readonly)
Returns The merger instance driving this run.
56 57 58 |
# File 'lib/prism/merge/top_level_merge_runner.rb', line 56 def merger @merger end |
Instance Method Details
#merge ⇒ MergeResult
Execute the three-phase merge and return the result.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/prism/merge/top_level_merge_runner.rb', line 66 def merge root_operation = start_runtime_session! merge_result = if comment_only_merge? merger.send(:comment_only_file_merger).merge else template_by_signature = merger.send(:build_signature_map, merger.template_analysis) dest_by_signature = merger.send(:build_signature_map, merger.dest_analysis) prepare_comment_augmenters!(template_by_signature: template_by_signature, dest_by_signature: dest_by_signature) consumed_template_indices = Set.new sig_cursor = Hash.new(0) output_dest_line_ranges = [] last_output_dest_line = merger.send(:emit_dest_prefix_lines, merger.result, merger.dest_analysis) # Phase 1: exact signature match (existing behavior). dest_sigs = ::Set.new(dest_by_signature.keys) # Phase 2: compute similarity-matched pairs from residual orphans. @similarity_pairs = compute_similarity_pairs(template_by_signature, dest_by_signature) # Phase 3: cross-depth search, but only for orphans surviving Phases 1+2. @deep_dest_sigs = compute_deep_sigs_for_orphans( template_by_signature, dest_sigs ) trailing_groups, _matched_indices = build_dest_iterate_trailing_groups( template_nodes: merger.template_analysis.statements, dest_sigs: dest_sigs, signature_for: ->(node) { merger.template_analysis.generate_signature(node) }, add_template_only_nodes: merger.add_template_only_nodes ) destination_template_indices = destination_template_index_sequence( template_by_signature: template_by_signature ) destination_signatures = merger.dest_analysis.statements.map do |dest_node| merger.dest_analysis.generate_signature(dest_node) end unless destination_tail_template_only_placement? emit_prefix_trailing_group(trailing_groups, consumed_template_indices) do |info| emit_template_only_node(info, consumed_template_indices) end end merger.dest_analysis.statements.each_with_index do |dest_node, dest_position| last_output_dest_line = process_dest_node( dest_node: dest_node, template_by_signature: template_by_signature, consumed_template_indices: consumed_template_indices, sig_cursor: sig_cursor, output_dest_line_ranges: output_dest_line_ranges, last_output_dest_line: last_output_dest_line, dest_position: dest_position, destination_signatures: destination_signatures, destination_template_indices: destination_template_indices ) next if destination_tail_template_only_placement? emit_available_trailing_groups( trailing_groups: trailing_groups, consumed_indices: consumed_template_indices, dest_position: dest_position, destination_template_indices: destination_template_indices ) end # Safety net: emit any trailing groups whose anchor was never consumed unless destination_tail_template_only_placement? emit_remaining_trailing_groups( trailing_groups: trailing_groups, consumed_indices: consumed_template_indices ) do |info| emit_template_only_node(info, consumed_template_indices) end end if destination_tail_template_only_placement? emit_tail_template_only_nodes(consumed_template_indices) end emit_dest_postlude_lines(last_output_dest_line) normalize_layout_blank_runs(merger.result) merger.result end complete_runtime_session!(root_operation, merge_result) merge_result rescue StandardError => e fail_runtime_session!(root_operation, e) raise end |