prism-merge 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +34 -1
- data/README.md +2 -1
- data/lib/prism/merge/conflict_resolver.rb +58 -26
- data/lib/prism/merge/debug_logger.rb +54 -0
- data/lib/prism/merge/file_aligner.rb +12 -11
- data/lib/prism/merge/file_analysis.rb +263 -192
- data/lib/prism/merge/freeze_node.rb +161 -0
- data/lib/prism/merge/smart_merger.rb +216 -6
- data/lib/prism/merge/version.rb +1 -1
- data/lib/prism/merge.rb +2 -0
- data.tar.gz.sig +0 -0
- metadata +6 -4
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prism
|
|
4
|
+
module Merge
|
|
5
|
+
# Wrapper to represent freeze blocks as first-class nodes.
|
|
6
|
+
# A freeze block is a section marked with freeze/unfreeze comment markers that
|
|
7
|
+
# should be preserved from the destination during merges.
|
|
8
|
+
#
|
|
9
|
+
# While freeze blocks are delineated by comment markers, they are conceptually
|
|
10
|
+
# different from CommentNode and do not inherit from it because:
|
|
11
|
+
# - FreezeNode is a *structural directive* that contains code and/or comments
|
|
12
|
+
# - CommentNode represents *pure documentation* with no structural significance
|
|
13
|
+
# - FreezeNode can contain Ruby code nodes (methods, constants, etc.)
|
|
14
|
+
# - CommentNode only contains comments
|
|
15
|
+
# - Their signatures need different semantics for merge matching
|
|
16
|
+
#
|
|
17
|
+
# Freeze blocks can contain other nodes (methods, classes, etc.) and those
|
|
18
|
+
# nodes remain as separate entities within the block for analysis purposes,
|
|
19
|
+
# but the entire freeze block is treated as an atomic unit during merging.
|
|
20
|
+
#
|
|
21
|
+
# @example Freeze block with mixed content
|
|
22
|
+
# # prism-merge:freeze
|
|
23
|
+
# # Custom documentation
|
|
24
|
+
# CUSTOM_CONFIG = { key: "secret" }
|
|
25
|
+
# def custom_method
|
|
26
|
+
# # ...
|
|
27
|
+
# end
|
|
28
|
+
# # prism-merge:unfreeze
|
|
29
|
+
class FreezeNode
|
|
30
|
+
# Error raised when a freeze block has invalid structure
|
|
31
|
+
class InvalidStructureError < StandardError
|
|
32
|
+
attr_reader :start_line, :end_line, :unclosed_nodes
|
|
33
|
+
|
|
34
|
+
def initialize(message, start_line: nil, end_line: nil, unclosed_nodes: [])
|
|
35
|
+
super(message)
|
|
36
|
+
@start_line = start_line
|
|
37
|
+
@end_line = end_line
|
|
38
|
+
@unclosed_nodes = unclosed_nodes
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attr_reader :start_line, :end_line, :content, :nodes, :start_marker, :end_marker
|
|
43
|
+
|
|
44
|
+
# @param start_line [Integer] Line number of freeze marker
|
|
45
|
+
# @param end_line [Integer] Line number of unfreeze marker
|
|
46
|
+
# @param analysis [FileAnalysis] The file analysis containing this block
|
|
47
|
+
# @param nodes [Array<Prism::Node>] Nodes fully contained within the freeze block
|
|
48
|
+
# @param overlapping_nodes [Array<Prism::Node>] All nodes that overlap with freeze block (for validation)
|
|
49
|
+
# @param start_marker [String, nil] The freeze start marker text
|
|
50
|
+
# @param end_marker [String, nil] The freeze end marker text
|
|
51
|
+
def initialize(start_line:, end_line:, analysis:, nodes: [], overlapping_nodes: nil, start_marker: nil, end_marker: nil)
|
|
52
|
+
@start_line = start_line
|
|
53
|
+
@end_line = end_line
|
|
54
|
+
@analysis = analysis
|
|
55
|
+
@nodes = nodes
|
|
56
|
+
@overlapping_nodes = overlapping_nodes || nodes
|
|
57
|
+
@start_marker = start_marker
|
|
58
|
+
@end_marker = end_marker
|
|
59
|
+
|
|
60
|
+
# Extract content for the entire block
|
|
61
|
+
@content = (start_line..end_line).map { |ln| analysis.line_at(ln) }.join
|
|
62
|
+
|
|
63
|
+
# Validate structure
|
|
64
|
+
validate_structure!
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns a location-like object for compatibility with Prism nodes
|
|
68
|
+
def location
|
|
69
|
+
@location ||= Location.new(@start_line, @end_line)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns the freeze block content
|
|
73
|
+
def slice
|
|
74
|
+
@content
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Simple location struct for compatibility
|
|
78
|
+
Location = Struct.new(:start_line, :end_line) do
|
|
79
|
+
def cover?(line)
|
|
80
|
+
(start_line..end_line).cover?(line)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns a stable signature for this freeze block
|
|
85
|
+
# Signature includes the normalized content to detect changes
|
|
86
|
+
def signature
|
|
87
|
+
normalized = (@start_line..@end_line).map do |ln|
|
|
88
|
+
@analysis.normalized_line(ln)
|
|
89
|
+
end.compact.join("\n")
|
|
90
|
+
|
|
91
|
+
[:FreezeNode, normalized]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check if this is a freeze node (always true for FreezeNode)
|
|
95
|
+
def freeze_node?
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# String representation for debugging
|
|
100
|
+
def inspect
|
|
101
|
+
"#<Prism::Merge::FreezeNode lines=#{@start_line}..#{@end_line} nodes=#{@nodes.length}>"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# Validate that the freeze block has proper structure:
|
|
107
|
+
# - All nodes must be either fully contained or fully outside
|
|
108
|
+
# - No partial overlaps allowed (a node cannot start before and end inside, or vice versa)
|
|
109
|
+
def validate_structure!
|
|
110
|
+
unclosed = []
|
|
111
|
+
|
|
112
|
+
# Check all overlapping nodes
|
|
113
|
+
@overlapping_nodes.each do |node|
|
|
114
|
+
node_start = node.location.start_line
|
|
115
|
+
node_end = node.location.end_line
|
|
116
|
+
|
|
117
|
+
# Check if node is fully contained (valid)
|
|
118
|
+
fully_contained = node_start >= @start_line && node_end <= @end_line
|
|
119
|
+
|
|
120
|
+
# Check if node completely encompasses the freeze block
|
|
121
|
+
# This is only valid for ClassNode/ModuleNode (freeze blocks at class/module body level)
|
|
122
|
+
# For other nodes (methods, etc.), this is invalid
|
|
123
|
+
encompasses = node_start < @start_line && node_end > @end_line
|
|
124
|
+
valid_encompass = encompasses && (node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode))
|
|
125
|
+
|
|
126
|
+
# Check if node partially overlaps (invalid - unclosed/incomplete structure)
|
|
127
|
+
partially_overlaps = !fully_contained && !encompasses &&
|
|
128
|
+
((node_start < @start_line && node_end >= @start_line) ||
|
|
129
|
+
(node_start <= @end_line && node_end > @end_line))
|
|
130
|
+
|
|
131
|
+
# Invalid if: partial overlap OR if a non-class/module node encompasses the freeze block
|
|
132
|
+
if partially_overlaps || (encompasses && !valid_encompass)
|
|
133
|
+
unclosed << node
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return if unclosed.empty?
|
|
138
|
+
|
|
139
|
+
# Build error message with details about unclosed/overlapping nodes
|
|
140
|
+
node_descriptions = unclosed.map do |node|
|
|
141
|
+
node_start = node.location.start_line
|
|
142
|
+
node_end = node.location.end_line
|
|
143
|
+
overlap_type = if node_start < @start_line
|
|
144
|
+
"starts before freeze block (line #{node_start}) and ends inside (line #{node_end})"
|
|
145
|
+
else
|
|
146
|
+
"starts inside freeze block (line #{node_start}) and ends after (line #{node_end})"
|
|
147
|
+
end
|
|
148
|
+
"#{node.class.name.split("::").last} at lines #{node_start}-#{node_end} (#{overlap_type})"
|
|
149
|
+
end.join(", ")
|
|
150
|
+
|
|
151
|
+
raise InvalidStructureError.new(
|
|
152
|
+
"Freeze block at lines #{@start_line}-#{@end_line} contains incomplete nodes: #{node_descriptions}. " \
|
|
153
|
+
"A freeze block must fully contain all nodes within it, or be placed between nodes.",
|
|
154
|
+
start_line: @start_line,
|
|
155
|
+
end_line: @end_line,
|
|
156
|
+
unclosed_nodes: unclosed,
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -5,7 +5,10 @@ module Prism
|
|
|
5
5
|
# Orchestrates the smart merge process using FileAnalysis, FileAligner,
|
|
6
6
|
# ConflictResolver, and MergeResult to merge two Ruby files intelligently.
|
|
7
7
|
#
|
|
8
|
-
# SmartMerger provides flexible configuration for different merge scenarios
|
|
8
|
+
# SmartMerger provides flexible configuration for different merge scenarios.
|
|
9
|
+
# When matching class or module definitions are found in both files, the merger
|
|
10
|
+
# automatically performs recursive merging of their bodies, intelligently combining
|
|
11
|
+
# nested methods, constants, and other definitions.
|
|
9
12
|
#
|
|
10
13
|
# @example Basic merge (destination customizations preserved)
|
|
11
14
|
# merger = SmartMerger.new(template_content, dest_content)
|
|
@@ -86,6 +89,10 @@ module Prism
|
|
|
86
89
|
# - `true` - Add template-only nodes to result.
|
|
87
90
|
# Use when template has new required constants/methods to add.
|
|
88
91
|
#
|
|
92
|
+
# @param freeze_token [String] Token to use for freeze block markers.
|
|
93
|
+
# Default: "prism-merge" (looks for # prism-merge:freeze / # prism-merge:unfreeze)
|
|
94
|
+
# Freeze blocks preserve destination content unchanged during merge.
|
|
95
|
+
#
|
|
89
96
|
# @raise [TemplateParseError] If template has syntax errors
|
|
90
97
|
# @raise [DestinationParseError] If destination has syntax errors
|
|
91
98
|
#
|
|
@@ -124,13 +131,14 @@ module Prism
|
|
|
124
131
|
# destination,
|
|
125
132
|
# signature_generator: sig_gen
|
|
126
133
|
# )
|
|
127
|
-
def initialize(template_content, dest_content, signature_generator: nil, signature_match_preference: :destination, add_template_only_nodes: false)
|
|
134
|
+
def initialize(template_content, dest_content, signature_generator: nil, signature_match_preference: :destination, add_template_only_nodes: false, freeze_token: FileAnalysis::DEFAULT_FREEZE_TOKEN)
|
|
128
135
|
@template_content = template_content
|
|
129
136
|
@dest_content = dest_content
|
|
130
137
|
@signature_match_preference = signature_match_preference
|
|
131
138
|
@add_template_only_nodes = add_template_only_nodes
|
|
132
|
-
@
|
|
133
|
-
@
|
|
139
|
+
@freeze_token = freeze_token
|
|
140
|
+
@template_analysis = FileAnalysis.new(template_content, signature_generator: signature_generator, freeze_token: freeze_token)
|
|
141
|
+
@dest_analysis = FileAnalysis.new(dest_content, signature_generator: signature_generator, freeze_token: freeze_token)
|
|
134
142
|
@aligner = FileAligner.new(@template_analysis, @dest_analysis)
|
|
135
143
|
@resolver = ConflictResolver.new(
|
|
136
144
|
@template_analysis,
|
|
@@ -304,8 +312,15 @@ module Prism
|
|
|
304
312
|
end
|
|
305
313
|
|
|
306
314
|
def add_signature_match_from_dest(anchor)
|
|
307
|
-
#
|
|
308
|
-
|
|
315
|
+
# Find the nodes corresponding to this anchor
|
|
316
|
+
# Look for nodes that overlap with the anchor range (not just at start line)
|
|
317
|
+
template_node = find_node_in_range(@template_analysis, anchor.template_start, anchor.template_end)
|
|
318
|
+
dest_node = find_node_in_range(@dest_analysis, anchor.dest_start, anchor.dest_end)
|
|
319
|
+
|
|
320
|
+
# Check if this is a class or module that should be recursively merged
|
|
321
|
+
if should_merge_recursively?(template_node, dest_node)
|
|
322
|
+
merge_node_body_recursively(template_node, dest_node, anchor)
|
|
323
|
+
elsif @signature_match_preference == :template
|
|
309
324
|
# Use template version (for updates/canonical values)
|
|
310
325
|
anchor.template_range.each do |line_num|
|
|
311
326
|
line = @template_analysis.line_at(line_num)
|
|
@@ -342,6 +357,201 @@ module Prism
|
|
|
342
357
|
def process_boundary(boundary)
|
|
343
358
|
@resolver.resolve(boundary, @result)
|
|
344
359
|
end
|
|
360
|
+
|
|
361
|
+
# Find a node that overlaps with the given line range.
|
|
362
|
+
# This handles cases where anchors include leading comments (e.g., magic comments).
|
|
363
|
+
#
|
|
364
|
+
# @param analysis [FileAnalysis] The file analysis to search
|
|
365
|
+
# @param start_line [Integer] Start line of the range
|
|
366
|
+
# @param end_line [Integer] End line of the range
|
|
367
|
+
# @return [Prism::Node, nil] The first node that overlaps with the range, or nil if none found
|
|
368
|
+
def find_node_in_range(analysis, start_line, end_line)
|
|
369
|
+
# Find a node that overlaps with the range
|
|
370
|
+
analysis.statements.find do |stmt|
|
|
371
|
+
# Check if node overlaps with the range
|
|
372
|
+
stmt.location.start_line <= end_line && stmt.location.end_line >= start_line
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Find the node at a specific line in the analysis (deprecated - use find_node_in_range)
|
|
377
|
+
# @deprecated Use {#find_node_in_range} instead for better handling of leading comments
|
|
378
|
+
# @param analysis [FileAnalysis] The file analysis to search
|
|
379
|
+
# @param line_num [Integer] The line number to find a node at
|
|
380
|
+
# @return [Prism::Node, nil] The node at that line, or nil if none found
|
|
381
|
+
def find_node_at_line(analysis, line_num)
|
|
382
|
+
analysis.statements.find do |stmt|
|
|
383
|
+
line_num.between?(stmt.location.start_line, stmt.location.end_line)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Determines if two matching nodes should be recursively merged.
|
|
388
|
+
#
|
|
389
|
+
# Recursive merge is performed for matching class/module definitions to intelligently
|
|
390
|
+
# combine their body contents (nested methods, constants, etc.). This allows template
|
|
391
|
+
# updates to class internals to be merged with destination customizations.
|
|
392
|
+
#
|
|
393
|
+
# @param template_node [Prism::Node, nil] Node from template file
|
|
394
|
+
# @param dest_node [Prism::Node, nil] Node from destination file
|
|
395
|
+
# @return [Boolean] true if nodes should be recursively merged
|
|
396
|
+
#
|
|
397
|
+
# @note Recursive merge is NOT performed for:
|
|
398
|
+
# - Conditional nodes (if/unless) - treated as atomic units
|
|
399
|
+
# - Classes/modules containing freeze blocks - frozen content would be lost
|
|
400
|
+
# - Nodes of different types
|
|
401
|
+
def should_merge_recursively?(template_node, dest_node)
|
|
402
|
+
return false unless template_node && dest_node
|
|
403
|
+
|
|
404
|
+
is_class_or_module = (template_node.is_a?(Prism::ClassNode) && dest_node.is_a?(Prism::ClassNode)) ||
|
|
405
|
+
(template_node.is_a?(Prism::ModuleNode) && dest_node.is_a?(Prism::ModuleNode))
|
|
406
|
+
|
|
407
|
+
return false unless is_class_or_module
|
|
408
|
+
|
|
409
|
+
# Don't recursively merge if either node contains freeze blocks
|
|
410
|
+
# (they would be lost in the nested merge since we pass freeze_token: nil)
|
|
411
|
+
return false if node_contains_freeze_blocks?(template_node)
|
|
412
|
+
return false if node_contains_freeze_blocks?(dest_node)
|
|
413
|
+
|
|
414
|
+
true
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Check if a node's body contains freeze block markers.
|
|
418
|
+
#
|
|
419
|
+
# @param node [Prism::Node] The node to check
|
|
420
|
+
# @return [Boolean] true if the node's body contains freeze block comments
|
|
421
|
+
# @api private
|
|
422
|
+
def node_contains_freeze_blocks?(node)
|
|
423
|
+
return false unless @freeze_token
|
|
424
|
+
return false unless node.body
|
|
425
|
+
|
|
426
|
+
# Check if any comments in the node's range contain freeze markers
|
|
427
|
+
freeze_pattern = /#\s*#{Regexp.escape(@freeze_token)}:(freeze|unfreeze)/i
|
|
428
|
+
|
|
429
|
+
node_start = node.location.start_line
|
|
430
|
+
node_end = node.location.end_line
|
|
431
|
+
|
|
432
|
+
@template_analysis.parse_result.comments.any? do |comment|
|
|
433
|
+
comment_line = comment.location.start_line
|
|
434
|
+
comment_line > node_start && comment_line < node_end && comment.slice.match?(freeze_pattern)
|
|
435
|
+
end || @dest_analysis.parse_result.comments.any? do |comment|
|
|
436
|
+
comment_line = comment.location.start_line
|
|
437
|
+
comment_line > node_start && comment_line < node_end && comment.slice.match?(freeze_pattern)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Recursively merges the body of matching class or module nodes.
|
|
442
|
+
#
|
|
443
|
+
# This method extracts the body content (everything between the class/module
|
|
444
|
+
# declaration and the closing 'end'), creates a new nested SmartMerger to merge
|
|
445
|
+
# those bodies, and then reassembles the complete class/module with the merged body.
|
|
446
|
+
#
|
|
447
|
+
# @param template_node [Prism::ClassNode, Prism::ModuleNode] Class/module from template
|
|
448
|
+
# @param dest_node [Prism::ClassNode, Prism::ModuleNode] Class/module from destination
|
|
449
|
+
# @param anchor [FileAligner::Anchor] The anchor representing this match
|
|
450
|
+
#
|
|
451
|
+
# @note The nested merger is configured with:
|
|
452
|
+
# - Same signature_generator, signature_match_preference, and add_template_only_nodes
|
|
453
|
+
# - freeze_token: nil (freeze blocks not processed in nested context)
|
|
454
|
+
#
|
|
455
|
+
# @api private
|
|
456
|
+
def merge_node_body_recursively(template_node, dest_node, anchor)
|
|
457
|
+
# Extract the body source for both nodes
|
|
458
|
+
template_body = extract_node_body(template_node, @template_analysis)
|
|
459
|
+
dest_body = extract_node_body(dest_node, @dest_analysis)
|
|
460
|
+
|
|
461
|
+
# Recursively merge the bodies
|
|
462
|
+
body_merger = SmartMerger.new(
|
|
463
|
+
template_body,
|
|
464
|
+
dest_body,
|
|
465
|
+
signature_generator: @template_analysis.instance_variable_get(:@signature_generator),
|
|
466
|
+
signature_match_preference: @signature_match_preference,
|
|
467
|
+
add_template_only_nodes: @add_template_only_nodes,
|
|
468
|
+
freeze_token: nil, # Don't process freeze blocks in nested context
|
|
469
|
+
)
|
|
470
|
+
merged_body = body_merger.merge.rstrip
|
|
471
|
+
|
|
472
|
+
# Add the opening line (class/module declaration) with leading comments
|
|
473
|
+
source_analysis = (@signature_match_preference == :template) ? @template_analysis : @dest_analysis
|
|
474
|
+
source_node = (@signature_match_preference == :template) ? template_node : dest_node
|
|
475
|
+
source_anchor_start = (@signature_match_preference == :template) ? anchor.template_start : anchor.dest_start
|
|
476
|
+
|
|
477
|
+
# Add leading comments
|
|
478
|
+
(source_anchor_start...source_node.location.start_line).each do |line_num|
|
|
479
|
+
line = source_analysis.line_at(line_num)
|
|
480
|
+
@result.add_line(
|
|
481
|
+
line.chomp,
|
|
482
|
+
decision: MergeResult::DECISION_REPLACED,
|
|
483
|
+
template_line: ((@signature_match_preference == :template) ? line_num : nil),
|
|
484
|
+
dest_line: ((@signature_match_preference == :destination) ? line_num : nil),
|
|
485
|
+
)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Add the class/module opening line
|
|
489
|
+
opening_line = source_analysis.line_at(source_node.location.start_line)
|
|
490
|
+
@result.add_line(
|
|
491
|
+
opening_line.chomp,
|
|
492
|
+
decision: MergeResult::DECISION_REPLACED,
|
|
493
|
+
template_line: ((@signature_match_preference == :template) ? source_node.location.start_line : nil),
|
|
494
|
+
dest_line: ((@signature_match_preference == :destination) ? source_node.location.start_line : nil),
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Add the merged body (indented appropriately)
|
|
498
|
+
merged_body.lines.each do |line|
|
|
499
|
+
@result.add_line(
|
|
500
|
+
line.chomp,
|
|
501
|
+
decision: MergeResult::DECISION_REPLACED,
|
|
502
|
+
template_line: nil,
|
|
503
|
+
dest_line: nil,
|
|
504
|
+
)
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# Add the closing 'end'
|
|
508
|
+
end_line = source_analysis.line_at(source_node.location.end_line)
|
|
509
|
+
@result.add_line(
|
|
510
|
+
end_line.chomp,
|
|
511
|
+
decision: MergeResult::DECISION_REPLACED,
|
|
512
|
+
template_line: ((@signature_match_preference == :template) ? source_node.location.end_line : nil),
|
|
513
|
+
dest_line: ((@signature_match_preference == :destination) ? source_node.location.end_line : nil),
|
|
514
|
+
)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Extracts the body content of a node (without declaration and closing 'end').
|
|
518
|
+
#
|
|
519
|
+
# For class/module nodes, extracts content between the declaration line and the
|
|
520
|
+
# closing 'end'. For conditional nodes, extracts the statements within the condition.
|
|
521
|
+
#
|
|
522
|
+
# @param node [Prism::Node] The node to extract body from
|
|
523
|
+
# @param analysis [FileAnalysis] The file analysis containing the node
|
|
524
|
+
# @return [String] The extracted body content
|
|
525
|
+
#
|
|
526
|
+
# @note Handles different node types:
|
|
527
|
+
# - ClassNode/ModuleNode: Uses node.body (StatementsNode)
|
|
528
|
+
# - IfNode/UnlessNode: Uses node.statements (StatementsNode)
|
|
529
|
+
#
|
|
530
|
+
# @api private
|
|
531
|
+
def extract_node_body(node, analysis)
|
|
532
|
+
# Get the statements node based on node type
|
|
533
|
+
statements_node = if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
|
|
534
|
+
node.body
|
|
535
|
+
elsif node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode)
|
|
536
|
+
node.statements
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
return "" unless statements_node&.is_a?(Prism::StatementsNode)
|
|
540
|
+
|
|
541
|
+
body_statements = statements_node.body
|
|
542
|
+
return "" if body_statements.empty?
|
|
543
|
+
|
|
544
|
+
# Get the line range of the body (between opening line and end)
|
|
545
|
+
first_stmt_line = body_statements.first.location.start_line
|
|
546
|
+
last_stmt_line = body_statements.last.location.end_line
|
|
547
|
+
|
|
548
|
+
# Extract the source lines for the body
|
|
549
|
+
lines = []
|
|
550
|
+
(first_stmt_line..last_stmt_line).each do |line_num|
|
|
551
|
+
lines << analysis.line_at(line_num).chomp
|
|
552
|
+
end
|
|
553
|
+
lines.join("\n") + "\n"
|
|
554
|
+
end
|
|
345
555
|
end
|
|
346
556
|
end
|
|
347
557
|
end
|
data/lib/prism/merge/version.rb
CHANGED
data/lib/prism/merge.rb
CHANGED
|
@@ -80,6 +80,8 @@ module Prism
|
|
|
80
80
|
# end
|
|
81
81
|
class DestinationParseError < ParseError; end
|
|
82
82
|
|
|
83
|
+
autoload :DebugLogger, "prism/merge/debug_logger"
|
|
84
|
+
autoload :FreezeNode, "prism/merge/freeze_node"
|
|
83
85
|
autoload :FileAnalysis, "prism/merge/file_analysis"
|
|
84
86
|
autoload :MergeResult, "prism/merge/merge_result"
|
|
85
87
|
autoload :FileAligner, "prism/merge/file_aligner"
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prism-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -252,8 +252,10 @@ files:
|
|
|
252
252
|
- lib/prism-merge.rb
|
|
253
253
|
- lib/prism/merge.rb
|
|
254
254
|
- lib/prism/merge/conflict_resolver.rb
|
|
255
|
+
- lib/prism/merge/debug_logger.rb
|
|
255
256
|
- lib/prism/merge/file_aligner.rb
|
|
256
257
|
- lib/prism/merge/file_analysis.rb
|
|
258
|
+
- lib/prism/merge/freeze_node.rb
|
|
257
259
|
- lib/prism/merge/merge_result.rb
|
|
258
260
|
- lib/prism/merge/smart_merger.rb
|
|
259
261
|
- lib/prism/merge/version.rb
|
|
@@ -263,10 +265,10 @@ licenses:
|
|
|
263
265
|
- MIT
|
|
264
266
|
metadata:
|
|
265
267
|
homepage_uri: https://prism-merge.galtzo.com/
|
|
266
|
-
source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v1.0
|
|
267
|
-
changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v1.0
|
|
268
|
+
source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v1.1.0
|
|
269
|
+
changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v1.1.0/CHANGELOG.md
|
|
268
270
|
bug_tracker_uri: https://github.com/kettle-rb/prism-merge/issues
|
|
269
|
-
documentation_uri: https://www.rubydoc.info/gems/prism-merge/1.0
|
|
271
|
+
documentation_uri: https://www.rubydoc.info/gems/prism-merge/1.1.0
|
|
270
272
|
funding_uri: https://github.com/sponsors/pboling
|
|
271
273
|
wiki_uri: https://github.com/kettle-rb/prism-merge/wiki
|
|
272
274
|
news_uri: https://www.railsbling.com/tags/prism-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|