prism-merge 2.0.0 → 2.0.2
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 +62 -1
- data/CITATION.cff +2 -2
- data/README.md +2 -2
- data/lib/prism/merge/smart_merger.rb +96 -11
- data/lib/prism/merge/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f43aef831e7dd032d7607b541bcc542cb2899514150346632084148b167dc0a5
|
|
4
|
+
data.tar.gz: 104a80156b7b15d02c8b7f5b237998a4d4c225f22f7d108c46642ddea7e6c51e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 425c404b3e0396b5db166b0c4e2fca941af3f6b6d0727ece8b0cd19bc60f9901883d587dee4a90c43356b6fa4b6b98d841fbf791785fb410662d72df0e7c5d2b
|
|
7
|
+
data.tar.gz: eb666082134afb97479cd8dd020baa6b8ef45b489d0b8c3a4df8ad6e19e8100d53cba60d48c930d7ed43ce8830bf2ec5b309fc432a24dfb5440c1fa9eb9a9b7f
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,63 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [2.0.2] - 2026-02-22
|
|
34
|
+
|
|
35
|
+
- TAG: [v2.0.2][2.0.2t]
|
|
36
|
+
- COVERAGE: 98.80% -- 823/833 lines in 12 files
|
|
37
|
+
- BRANCH COVERAGE: 85.61% -- 452/528 branches in 12 files
|
|
38
|
+
- 93.51% documented
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Fix node duplication when merging files with inline trailing comments (e.g.,
|
|
43
|
+
gemspec `add_dependency` lines with `# ruby >= 3.2.0`). `add_node_to_result`
|
|
44
|
+
output the full source line (which already includes inline comments via
|
|
45
|
+
`analysis.line_at`), then also iterated `trailing_comments` and re-emitted any
|
|
46
|
+
comment on the same line — duplicating the entire line. Now skips trailing
|
|
47
|
+
comments whose `start_line` falls within the node's own line range. This was the
|
|
48
|
+
root cause of every `add_dependency` / `add_development_dependency` being
|
|
49
|
+
duplicated in gemspec and gemfile merges when inline comments were present.
|
|
50
|
+
- Prevent potential double-wrapping in `merge_node_body_recursively` — store the
|
|
51
|
+
raw (unwrapped) `signature_generator` as `@raw_signature_generator` and pass it
|
|
52
|
+
(instead of the already-effective generator) to inner `SmartMerger` instances.
|
|
53
|
+
This ensures `build_effective_signature_generator` wraps it only once when
|
|
54
|
+
`node_typing` is also configured.
|
|
55
|
+
|
|
56
|
+
## [2.0.1] - 2026-02-22
|
|
57
|
+
|
|
58
|
+
- TAG: [v2.0.1][2.0.1t]
|
|
59
|
+
- COVERAGE: 98.80% -- 820/830 lines in 12 files
|
|
60
|
+
- BRANCH COVERAGE: 85.55% -- 450/526 branches in 12 files
|
|
61
|
+
- 93.51% documented
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- `SmartMerger#emit_dest_prefix_lines` preserves magic comments (e.g., `# frozen_string_literal: true`)
|
|
66
|
+
and blank lines that appear before the first AST node in the destination file
|
|
67
|
+
- `SmartMerger#emit_dest_gap_lines` preserves blank lines between consecutive top-level blocks
|
|
68
|
+
in the destination, preventing them from being silently stripped during merge
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- `SmartMerger#merge_with_debug` now uses `merge_result` (returns `MergeResult` object)
|
|
73
|
+
instead of `merge` (returns `String`), so `statistics` and `decision_summary` are accessible
|
|
74
|
+
- `SmartMerger#build_result` now passes `template_analysis` and `dest_analysis` to
|
|
75
|
+
`MergeResult.new` for consistency with `SmartMergerBase` API
|
|
76
|
+
|
|
77
|
+
### Removed
|
|
78
|
+
|
|
79
|
+
- Removed redundant `attr_reader :node_typing` from `SmartMerger` — already provided
|
|
80
|
+
by `SmartMergerBase`
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Inter-node blank line stripping: blank lines between top-level blocks (e.g., between
|
|
85
|
+
`appraise` blocks in Appraisals, between `gem` calls in Gemfiles) are now preserved
|
|
86
|
+
from the destination source during merge
|
|
87
|
+
- Prefix line stripping: magic comments and blank lines before the first AST statement
|
|
88
|
+
(e.g., `# frozen_string_literal: true` in Appraisal.root.gemfile) are now preserved
|
|
89
|
+
|
|
33
90
|
## [2.0.0] - 2026-02-19
|
|
34
91
|
|
|
35
92
|
- TAG: [v2.0.0][2.0.0t]
|
|
@@ -281,7 +338,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
281
338
|
|
|
282
339
|
- Initial release
|
|
283
340
|
|
|
284
|
-
[Unreleased]: https://github.com/kettle-rb/prism-merge/compare/v2.0.
|
|
341
|
+
[Unreleased]: https://github.com/kettle-rb/prism-merge/compare/v2.0.2...HEAD
|
|
342
|
+
[2.0.2]: https://github.com/kettle-rb/prism-merge/compare/v2.0.1...v2.0.2
|
|
343
|
+
[2.0.2t]: https://github.com/kettle-rb/prism-merge/releases/tag/v2.0.2
|
|
344
|
+
[2.0.1]: https://github.com/kettle-rb/prism-merge/compare/v2.0.0...v2.0.1
|
|
345
|
+
[2.0.1t]: https://github.com/kettle-rb/prism-merge/releases/tag/v2.0.1
|
|
285
346
|
[2.0.0]: https://github.com/kettle-rb/prism-merge/compare/v1.1.6...v2.0.0
|
|
286
347
|
[2.0.0t]: https://github.com/kettle-rb/prism-merge/releases/tag/v2.0.0
|
|
287
348
|
[1.1.6]: https://github.com/kettle-rb/prism-merge/compare/v1.1.5...v1.1.6
|
data/CITATION.cff
CHANGED
|
@@ -7,8 +7,8 @@ type: software
|
|
|
7
7
|
authors:
|
|
8
8
|
- given-names: Peter Hurn
|
|
9
9
|
family-names: Boling
|
|
10
|
-
email:
|
|
11
|
-
affiliation:
|
|
10
|
+
email: floss@galtzo.com
|
|
11
|
+
affiliation: galtzo.com
|
|
12
12
|
orcid: 'https://orcid.org/0009-0008-8519-441X'
|
|
13
13
|
identifiers:
|
|
14
14
|
- type: url
|
data/README.md
CHANGED
|
@@ -80,7 +80,7 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
80
80
|
| [markly-merge][markly-merge] | [![Version][markly-merge-gem-i]][markly-merge-gem] <br/> [![CI][markly-merge-ci-i]][markly-merge-ci] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
|
|
81
81
|
| [prism-merge][prism-merge] | [![Version][prism-merge-gem-i]][prism-merge-gem] <br/> [![CI][prism-merge-ci-i]][prism-merge-ci] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
|
|
82
82
|
| [psych-merge][psych-merge] | [![Version][psych-merge-gem-i]][psych-merge-gem] <br/> [![CI][psych-merge-ci-i]][psych-merge-ci] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
|
|
83
|
-
| [rbs-merge][rbs-merge] | [![Version][rbs-merge-gem-i]][rbs-merge-gem] <br/> [![CI][rbs-merge-ci-i]][rbs-merge-ci] | RBS | [tree-sitter-
|
|
83
|
+
| [rbs-merge][rbs-merge] | [![Version][rbs-merge-gem-i]][rbs-merge-gem] <br/> [![CI][rbs-merge-ci-i]][rbs-merge-ci] | RBS | [tree-sitter-rbs][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
|
|
84
84
|
| [toml-merge][toml-merge] | [![Version][toml-merge-gem-i]][toml-merge-gem] <br/> [![CI][toml-merge-ci-i]][toml-merge-ci] | TOML | [Parslet + toml][toml], [Citrus + toml-rb][toml-rb], [tree-sitter-toml][ts-toml] (all via tree_haver) | Smart merge for TOML files |
|
|
85
85
|
|
|
86
86
|
#### Backend Platform Compatibility
|
|
@@ -1564,7 +1564,7 @@ Thanks for RTFM. ☺️
|
|
|
1564
1564
|
[📌gitmoji]: https://gitmoji.dev
|
|
1565
1565
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1566
1566
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1567
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.
|
|
1567
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.833-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1568
1568
|
[🔐security]: SECURITY.md
|
|
1569
1569
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1570
1570
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -46,9 +46,6 @@ module Prism
|
|
|
46
46
|
# result = merger.merge
|
|
47
47
|
#
|
|
48
48
|
class SmartMerger < ::Ast::Merge::SmartMergerBase
|
|
49
|
-
# @return [Hash{Symbol,String => #call}, nil] Node typing configuration
|
|
50
|
-
attr_reader :node_typing
|
|
51
|
-
|
|
52
49
|
# @return [Integer, Float] Maximum recursion depth for body merging
|
|
53
50
|
attr_reader :max_recursion_depth
|
|
54
51
|
|
|
@@ -97,6 +94,11 @@ module Prism
|
|
|
97
94
|
@current_depth = current_depth
|
|
98
95
|
@text_merger_options = text_merger_options
|
|
99
96
|
|
|
97
|
+
# Store the raw (unwrapped) signature_generator so that
|
|
98
|
+
# merge_node_body_recursively can pass it to inner SmartMergers
|
|
99
|
+
# without double-wrapping.
|
|
100
|
+
@raw_signature_generator = signature_generator
|
|
101
|
+
|
|
100
102
|
# Wrap signature_generator to include node_typing processing
|
|
101
103
|
effective_signature_generator = build_effective_signature_generator(signature_generator, node_typing)
|
|
102
104
|
|
|
@@ -139,9 +141,9 @@ module Prism
|
|
|
139
141
|
#
|
|
140
142
|
# @return [Hash] Hash with :content, :debug, and :statistics keys
|
|
141
143
|
def merge_with_debug
|
|
142
|
-
|
|
144
|
+
result_obj = merge_result
|
|
143
145
|
{
|
|
144
|
-
content:
|
|
146
|
+
content: result_obj.to_s,
|
|
145
147
|
debug: {
|
|
146
148
|
template_statements: @template_analysis&.statements&.size || 0,
|
|
147
149
|
dest_statements: @dest_analysis&.statements&.size || 0,
|
|
@@ -149,7 +151,7 @@ module Prism
|
|
|
149
151
|
add_template_only_nodes: @add_template_only_nodes,
|
|
150
152
|
freeze_token: @freeze_token,
|
|
151
153
|
},
|
|
152
|
-
statistics:
|
|
154
|
+
statistics: result_obj.respond_to?(:statistics) ? result_obj.statistics : result_obj.decision_summary,
|
|
153
155
|
}
|
|
154
156
|
end
|
|
155
157
|
|
|
@@ -180,9 +182,12 @@ module Prism
|
|
|
180
182
|
nil
|
|
181
183
|
end
|
|
182
184
|
|
|
183
|
-
# Build the result
|
|
185
|
+
# Build the result with analysis references
|
|
184
186
|
def build_result
|
|
185
|
-
MergeResult.new
|
|
187
|
+
MergeResult.new(
|
|
188
|
+
template_analysis: @template_analysis,
|
|
189
|
+
dest_analysis: @dest_analysis,
|
|
190
|
+
)
|
|
186
191
|
end
|
|
187
192
|
|
|
188
193
|
# @return [Class] The template parse error class for Ruby
|
|
@@ -240,6 +245,10 @@ module Prism
|
|
|
240
245
|
|
|
241
246
|
# Phase 2: Process dest nodes in their original order
|
|
242
247
|
# This preserves dest-only nodes in their original position relative to matched nodes
|
|
248
|
+
|
|
249
|
+
# Emit prefix lines from the dest source (magic comments, blank lines before first node)
|
|
250
|
+
last_output_dest_line = emit_dest_prefix_lines(@result, @dest_analysis)
|
|
251
|
+
|
|
243
252
|
@dest_analysis.statements.each do |dest_node|
|
|
244
253
|
dest_signature = @dest_analysis.generate_signature(dest_node)
|
|
245
254
|
|
|
@@ -250,6 +259,9 @@ module Prism
|
|
|
250
259
|
node_range = dest_node.location.start_line..dest_node.location.end_line
|
|
251
260
|
next if output_dest_line_ranges.any? { |range| range.cover?(node_range.begin) && range.cover?(node_range.end) }
|
|
252
261
|
|
|
262
|
+
# Emit inter-node gap lines from the dest source (blank lines between blocks)
|
|
263
|
+
last_output_dest_line = emit_dest_gap_lines(@result, @dest_analysis, last_output_dest_line, dest_node)
|
|
264
|
+
|
|
253
265
|
if dest_signature && template_by_signature.key?(dest_signature)
|
|
254
266
|
# Matched node - merge with template version
|
|
255
267
|
template_node = template_by_signature[dest_signature]
|
|
@@ -277,6 +289,12 @@ module Prism
|
|
|
277
289
|
output_dest_line_ranges << node_range
|
|
278
290
|
output_signatures << dest_signature if dest_signature
|
|
279
291
|
end
|
|
292
|
+
|
|
293
|
+
# Update last_output_dest_line to track trailing blank line from add_node_to_result
|
|
294
|
+
last_output_dest_line = dest_node.location.end_line
|
|
295
|
+
trailing_line = last_output_dest_line + 1
|
|
296
|
+
trailing_content = @dest_analysis.line_at(trailing_line)
|
|
297
|
+
last_output_dest_line = trailing_line if trailing_content && trailing_content.strip.empty?
|
|
280
298
|
end
|
|
281
299
|
|
|
282
300
|
@result
|
|
@@ -596,6 +614,64 @@ module Prism
|
|
|
596
614
|
}
|
|
597
615
|
end
|
|
598
616
|
|
|
617
|
+
# Emit prefix lines from the destination source that appear before the first node.
|
|
618
|
+
# This preserves magic comments (e.g., `# frozen_string_literal: true`) and blank
|
|
619
|
+
# lines that precede any AST statements.
|
|
620
|
+
#
|
|
621
|
+
# @param result [MergeResult] The merge result
|
|
622
|
+
# @param analysis [FileAnalysis] The destination file analysis
|
|
623
|
+
# @return [Integer] The last line number emitted (0 if none)
|
|
624
|
+
def emit_dest_prefix_lines(result, analysis)
|
|
625
|
+
return 0 if analysis.statements.empty?
|
|
626
|
+
|
|
627
|
+
first_node = analysis.statements.first
|
|
628
|
+
# Find the first line of content: either leading comment or node start
|
|
629
|
+
leading_comments = first_node.location.respond_to?(:leading_comments) ? first_node.location.leading_comments : []
|
|
630
|
+
first_content_line = leading_comments.any? ? leading_comments.first.location.start_line : first_node.location.start_line
|
|
631
|
+
|
|
632
|
+
return 0 if first_content_line <= 1
|
|
633
|
+
|
|
634
|
+
# Emit lines before the first node (magic comments, blank lines)
|
|
635
|
+
last_emitted = 0
|
|
636
|
+
(1...first_content_line).each do |line_num|
|
|
637
|
+
line = analysis.line_at(line_num)&.chomp || ""
|
|
638
|
+
result.add_line(line, decision: MergeResult::DECISION_KEPT_DEST, dest_line: line_num)
|
|
639
|
+
last_emitted = line_num
|
|
640
|
+
end
|
|
641
|
+
last_emitted
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
# Emit blank/gap lines from the destination source between the last output line
|
|
645
|
+
# and the next node (including its leading comments). This preserves blank lines
|
|
646
|
+
# that separate top-level blocks.
|
|
647
|
+
#
|
|
648
|
+
# @param result [MergeResult] The merge result
|
|
649
|
+
# @param analysis [FileAnalysis] The destination file analysis
|
|
650
|
+
# @param last_output_line [Integer] The last dest line number that was output
|
|
651
|
+
# @param next_node [Prism::Node] The next node about to be output
|
|
652
|
+
# @return [Integer] The updated last output line number
|
|
653
|
+
def emit_dest_gap_lines(result, analysis, last_output_line, next_node)
|
|
654
|
+
return last_output_line if last_output_line == 0
|
|
655
|
+
|
|
656
|
+
# Find where the next node's content starts (leading comment or node itself)
|
|
657
|
+
leading_comments = next_node.location.respond_to?(:leading_comments) ? next_node.location.leading_comments : []
|
|
658
|
+
next_start_line = leading_comments.any? ? leading_comments.first.location.start_line : next_node.location.start_line
|
|
659
|
+
|
|
660
|
+
# Emit gap lines (blank lines between last output and next node)
|
|
661
|
+
gap_start = last_output_line + 1
|
|
662
|
+
return last_output_line if gap_start >= next_start_line
|
|
663
|
+
|
|
664
|
+
(gap_start...next_start_line).each do |line_num|
|
|
665
|
+
line = analysis.line_at(line_num)&.chomp || ""
|
|
666
|
+
# Only emit blank lines in the gap (don't re-emit content)
|
|
667
|
+
next unless line.strip.empty?
|
|
668
|
+
|
|
669
|
+
result.add_line(line, decision: MergeResult::DECISION_KEPT_DEST, dest_line: line_num)
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
last_output_line
|
|
673
|
+
end
|
|
674
|
+
|
|
599
675
|
# Add a node to the result, including its leading and trailing comments.
|
|
600
676
|
#
|
|
601
677
|
# @param result [MergeResult] The merge result
|
|
@@ -680,10 +756,15 @@ module Prism
|
|
|
680
756
|
end
|
|
681
757
|
end
|
|
682
758
|
|
|
683
|
-
# Add trailing comments attached to the node (e.g., end-of-file comments)
|
|
759
|
+
# Add trailing comments attached to the node (e.g., end-of-file comments).
|
|
760
|
+
# Skip comments on the same line as the node — inline comments are already
|
|
761
|
+
# included when we output the node's source lines via analysis.line_at.
|
|
684
762
|
trailing_comments = node.location.respond_to?(:trailing_comments) ? node.location.trailing_comments : []
|
|
763
|
+
node_line_range = node.location.start_line..node.location.end_line
|
|
685
764
|
trailing_comments.each do |comment|
|
|
686
765
|
line_num = comment.location.start_line
|
|
766
|
+
next if node_line_range.cover?(line_num)
|
|
767
|
+
|
|
687
768
|
line = analysis.line_at(line_num)&.chomp || comment.slice.rstrip
|
|
688
769
|
|
|
689
770
|
if source == :template
|
|
@@ -804,11 +885,15 @@ module Prism
|
|
|
804
885
|
template_body = extract_node_body(actual_template, @template_analysis)
|
|
805
886
|
dest_body = extract_node_body(actual_dest, @dest_analysis)
|
|
806
887
|
|
|
807
|
-
# Recursively merge the bodies with incremented depth
|
|
888
|
+
# Recursively merge the bodies with incremented depth.
|
|
889
|
+
# Use the raw (unwrapped) signature_generator so the inner SmartMerger
|
|
890
|
+
# can wrap it fresh via build_effective_signature_generator. Using the
|
|
891
|
+
# already-effective generator would cause double-wrapping when
|
|
892
|
+
# node_typing is also passed, making is_a? checks fail.
|
|
808
893
|
body_merger = SmartMerger.new(
|
|
809
894
|
template_body,
|
|
810
895
|
dest_body,
|
|
811
|
-
signature_generator: @
|
|
896
|
+
signature_generator: @raw_signature_generator,
|
|
812
897
|
preference: @preference,
|
|
813
898
|
add_template_only_nodes: @add_template_only_nodes,
|
|
814
899
|
freeze_token: @freeze_token,
|
data/lib/prism/merge/version.rb
CHANGED
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: 2.0.
|
|
4
|
+
version: 2.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -314,10 +314,10 @@ licenses:
|
|
|
314
314
|
- MIT
|
|
315
315
|
metadata:
|
|
316
316
|
homepage_uri: https://prism-merge.galtzo.com/
|
|
317
|
-
source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v2.0.
|
|
318
|
-
changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v2.0.
|
|
317
|
+
source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v2.0.2
|
|
318
|
+
changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v2.0.2/CHANGELOG.md
|
|
319
319
|
bug_tracker_uri: https://github.com/kettle-rb/prism-merge/issues
|
|
320
|
-
documentation_uri: https://www.rubydoc.info/gems/prism-merge/2.0.
|
|
320
|
+
documentation_uri: https://www.rubydoc.info/gems/prism-merge/2.0.2
|
|
321
321
|
funding_uri: https://github.com/sponsors/pboling
|
|
322
322
|
wiki_uri: https://github.com/kettle-rb/prism-merge/wiki
|
|
323
323
|
news_uri: https://www.railsbling.com/tags/prism-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|