prism-merge 1.1.5 → 1.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34ca6071c4cec046f0e1b8b16298c1f11ac5164271ef37a51119d975e699aac6
4
- data.tar.gz: 8dcef50c1a8cd597cf471d148a15e2fec5412c6ecdcc20ba6788ea979ff23233
3
+ metadata.gz: 421f226a53add89905b9160c76fd3f18130bb7ab65a9b2da410e29237d0ee0d9
4
+ data.tar.gz: 35a0b3a827749be34717d4e9c58842ae589fc8a0028c50175465fd99504f0bc7
5
5
  SHA512:
6
- metadata.gz: d1faf2b8c9c7ee00902e65e6d559c931f934293764fcc8b85501b419cf2e2144e9f141d0feba0d4427018cc1f470ed5861ee9a1b06b5f2d0609a5ee39dceb94e
7
- data.tar.gz: 67fa40303034e480ce93b5ad4286d5061fbc77afa418364f2689f7667b08779a5394a898bf6df80ee00f165a8ef2ec95818759036b27eb10b5722a5d1fb39f50
6
+ metadata.gz: 31948037aa1cf05744a46649d0bddc73ed2920714c1c08d6f00c0bb337da25e479c77f80594b1a9ee0e2f32d16b58dcbd41c3dc1449408170d90095ef579081b
7
+ data.tar.gz: 8bdcc61811f3b70e338c3a34a1e2953b5eb81dd8186180c47d277dffe6cc5f944504ac2833c5d1f431593e9f221c726ed977b6120d43db7a744c6c1e07be06bf
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,20 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.1.6] - 2025-12-05
34
+
35
+ - TAG: [v1.1.6][1.1.6t]
36
+ - COVERAGE: 98.31% -- 929/945 lines in 9 files
37
+ - BRANCH COVERAGE: 87.08% -- 391/449 branches in 9 files
38
+ - 100.00% documented
39
+
40
+ ### Fixed
41
+
42
+ - **Fixed duplicate content when freeze blocks precede nodes with leading comments**: When a freeze block appeared before a node that had leading comments attached from earlier in the file, the merge would output duplicate content. Fixed by:
43
+ - Filtering out comments inside freeze blocks from being attached as leading comments to subsequent nodes
44
+ - Not including leading comments in anchor ranges when other nodes exist between the comments and the node
45
+ - Extending `extract_node_body` to include content after the last statement up to the closing line, ensuring freeze blocks at the end of block bodies are preserved
46
+
33
47
  ## [1.1.5] - 2025-12-04
34
48
 
35
49
  - TAG: [v1.1.5][1.1.5t]
@@ -223,7 +237,9 @@ Please file a bug if you notice a violation of semantic versioning.
223
237
 
224
238
  - Initial release
225
239
 
226
- [Unreleased]: https://github.com/kettle-rb/prism-merge/compare/v1.1.5...HEAD
240
+ [Unreleased]: https://github.com/kettle-rb/prism-merge/compare/v1.1.6...HEAD
241
+ [1.1.6]: https://github.com/kettle-rb/prism-merge/compare/v1.1.5...v1.1.6
242
+ [1.1.6t]: https://github.com/kettle-rb/prism-merge/releases/tag/v1.1.6
227
243
  [1.1.5]: https://github.com/kettle-rb/prism-merge/compare/v1.1.4...v1.1.5
228
244
  [1.1.5t]: https://github.com/kettle-rb/prism-merge/releases/tag/v1.1.5
229
245
  [1.1.4]: https://github.com/kettle-rb/prism-merge/compare/v1.1.3...v1.1.4
data/README.md CHANGED
@@ -1194,7 +1194,7 @@ Thanks for RTFM. ☺️
1194
1194
  [📌gitmoji]: https://gitmoji.dev
1195
1195
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1196
1196
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1197
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.922-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1197
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.945-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1198
1198
  [🔐security]: SECURITY.md
1199
1199
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1200
1200
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -254,17 +254,28 @@ module Prism
254
254
  d_node_info = dest_sig_map[sig].find { |d| !matched_dest.include?(d[:index]) }
255
255
  next unless d_node_info
256
256
 
257
- # Create anchor for this matched node (including its leading comments)
258
- t_start = t_node_info[:leading_comments].any? ? t_node_info[:leading_comments].first.location.start_line : t_node_info[:line_range].begin
257
+ # Create anchor for this matched node
258
+ # Only include leading comments if they are immediately before the node
259
+ # (no other nodes/content between comments and this node)
260
+ t_start = calculate_anchor_start(t_node_info, template_nodes)
259
261
  t_end = t_node_info[:line_range].end
260
- d_start = d_node_info[:leading_comments].any? ? d_node_info[:leading_comments].first.location.start_line : d_node_info[:line_range].begin
262
+ d_start = calculate_anchor_start(d_node_info, dest_nodes)
261
263
  d_end = d_node_info[:line_range].end
262
264
 
263
- # Check if this would completely overlap with existing anchors
264
- # Only skip if an anchor already covers the EXACT same range
265
+ # Check if this would overlap with existing anchors in BOTH template AND dest
266
+ # (same position in both files indicates a true overlap/conflict)
267
+ # Also check for exact match (already covered)
265
268
  overlaps = @anchors.any? do |a|
266
- a.template_start == t_start && a.template_end == t_end &&
269
+ # Exact match - already have this anchor
270
+ exact_match = a.template_start == t_start && a.template_end == t_end &&
267
271
  a.dest_start == d_start && a.dest_end == d_end
272
+
273
+ # Check if the proposed anchor's range overlaps with existing anchor
274
+ # AND it's the same relative position (both template ranges overlap AND both dest ranges overlap)
275
+ template_overlaps = ranges_overlap?(t_start..t_end, a.template_start..a.template_end)
276
+ dest_overlaps = ranges_overlap?(d_start..d_end, a.dest_start..a.dest_end)
277
+
278
+ exact_match || (template_overlaps && dest_overlaps)
268
279
  end
269
280
 
270
281
  unless overlaps
@@ -281,6 +292,33 @@ module Prism
281
292
  end
282
293
  end
283
294
 
295
+ # Calculate the start line for an anchor, considering leading comments
296
+ # Only include leading comments if they are immediately adjacent to the node
297
+ # (no other nodes between the comments and this node)
298
+ def calculate_anchor_start(node_info, all_nodes)
299
+ node_start = node_info[:line_range].begin
300
+ leading_comments = node_info[:leading_comments]
301
+
302
+ return node_start if leading_comments.empty?
303
+
304
+ first_comment_line = leading_comments.first.location.start_line
305
+
306
+ # Check if any other node exists between the first comment and this node
307
+ # If so, don't include the leading comments in the anchor
308
+ has_intervening_node = all_nodes.any? do |other|
309
+ next false if other[:index] == node_info[:index]
310
+ other_range = other[:line_range]
311
+ # Check if other node is between first comment and this node
312
+ other_range.begin > first_comment_line && other_range.end < node_start
313
+ end
314
+
315
+ if has_intervening_node
316
+ node_start
317
+ else
318
+ first_comment_line
319
+ end
320
+ end
321
+
284
322
  def add_freeze_block_anchors
285
323
  # Freeze blocks in destination should always be preserved as anchors
286
324
  # Match freeze blocks by their index/order in the file
@@ -319,9 +319,24 @@ module Prism
319
319
  end
320
320
  end
321
321
 
322
- # Extract nodes with their comments and metadata
323
- # Uses Prism's native comment attachment via node.location
324
- # @return [Array<Hash>]
322
+ # Extract nodes with their comments and metadata.
323
+ #
324
+ # Uses Prism's native comment attachment via node.location. Leading comments
325
+ # are filtered to exclude:
326
+ # 1. Freeze/unfreeze marker comments (they belong to FreezeNode boundaries)
327
+ # 2. Comments inside freeze blocks (they belong to FreezeNode content)
328
+ #
329
+ # This filtering prevents duplicate content when freeze blocks precede other
330
+ # nodes, as Prism attaches ALL preceding comments to a node's leading_comments.
331
+ #
332
+ # @return [Array<Hash>] Array of node info hashes with keys:
333
+ # - :node [Prism::Node, FreezeNode] The AST node
334
+ # - :index [Integer] Position in statements array
335
+ # - :leading_comments [Array<Prism::Comment>] Filtered leading comments
336
+ # - :inline_comments [Array<Prism::Comment>] Trailing/inline comments
337
+ # - :signature [Array, nil] Structural signature for matching
338
+ # - :line_range [Range] Line range covered by the node
339
+ # @api private
325
340
  def extract_nodes_with_comments
326
341
  return [] unless valid?
327
342
 
@@ -330,6 +345,13 @@ module Prism
330
345
  /#\s*#{Regexp.escape(@freeze_token)}:(freeze|unfreeze)/i
331
346
  end
332
347
 
348
+ # Build a set of line numbers that are inside freeze blocks
349
+ # Comments on these lines should not be attached as leading comments to other nodes
350
+ freeze_block_lines = Set.new
351
+ freeze_blocks.each do |fb|
352
+ (fb.start_line..fb.end_line).each { |line| freeze_block_lines << line }
353
+ end
354
+
333
355
  statements.map.with_index do |stmt, idx|
334
356
  # FreezeNode doesn't have Prism location with comments
335
357
  # It's a wrapper with custom Location struct
@@ -343,12 +365,17 @@ module Prism
343
365
  line_range: stmt.location.start_line..stmt.location.end_line,
344
366
  }
345
367
  else
346
- # Filter out freeze/unfreeze marker comments from leading comments
347
- # These markers are part of FreezeNode boundaries and should not be
348
- # attached to subsequent nodes
368
+ # Filter out comments that are:
369
+ # 1. Freeze/unfreeze markers (part of FreezeNode boundaries)
370
+ # 2. Inside freeze blocks (belong to FreezeNode content, not this node)
349
371
  leading = stmt.location.leading_comments
350
- if freeze_marker_pattern
351
- leading = leading.reject { |c| c.slice.match?(freeze_marker_pattern) }
372
+ if freeze_marker_pattern || freeze_block_lines.any?
373
+ leading = leading.reject do |c|
374
+ comment_line = c.location.start_line
375
+ # Reject if it's a freeze marker OR if it's inside a freeze block
376
+ (freeze_marker_pattern && c.slice.match?(freeze_marker_pattern)) ||
377
+ freeze_block_lines.include?(comment_line)
378
+ end
352
379
  end
353
380
 
354
381
  {
@@ -708,7 +708,7 @@ module Prism
708
708
 
709
709
  # Get the line range of the body
710
710
  # Start from line after node opening (to include any leading comments/freeze markers)
711
- # For nodes with blocks, the body starts after the block opening
711
+ # End at the line before the closing `end` (to include trailing comments/freeze markers)
712
712
  body_start_line = case node
713
713
  when Prism::CallNode
714
714
  # Block body starts on line after the `do` or `{`
@@ -720,11 +720,22 @@ module Prism
720
720
  body_statements.first.location.start_line
721
721
  end
722
722
 
723
- last_stmt_line = body_statements.last.location.end_line
723
+ # End line should be the line before the closing keyword (end, }, etc.)
724
+ # This ensures we capture trailing comments and freeze blocks after the last statement
725
+ body_end_line = case node
726
+ when Prism::CallNode
727
+ # Block ends at the closing `end` or `}`
728
+ node.block.closing_loc ? node.block.closing_loc.start_line - 1 : body_statements.last.location.end_line
729
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode
730
+ # Body ends at the line before `end`
731
+ node.end_keyword_loc ? node.end_keyword_loc.start_line - 1 : body_statements.last.location.end_line
732
+ else
733
+ body_statements.last.location.end_line
734
+ end
724
735
 
725
736
  # Extract the source lines for the body
726
737
  lines = []
727
- (body_start_line..last_stmt_line).each do |line_num|
738
+ (body_start_line..body_end_line).each do |line_num|
728
739
  lines << analysis.line_at(line_num).chomp
729
740
  end
730
741
  lines.join("\n") + "\n"
@@ -5,7 +5,7 @@ module Prism
5
5
  # Version information for Prism::Merge
6
6
  module Version
7
7
  # Current version of the prism-merge gem
8
- VERSION = "1.1.5"
8
+ VERSION = "1.1.6"
9
9
  end
10
10
  VERSION = Version::VERSION # traditional location
11
11
  end
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.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -265,10 +265,10 @@ licenses:
265
265
  - MIT
266
266
  metadata:
267
267
  homepage_uri: https://prism-merge.galtzo.com/
268
- source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v1.1.5
269
- changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v1.1.5/CHANGELOG.md
268
+ source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v1.1.6
269
+ changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v1.1.6/CHANGELOG.md
270
270
  bug_tracker_uri: https://github.com/kettle-rb/prism-merge/issues
271
- documentation_uri: https://www.rubydoc.info/gems/prism-merge/1.1.5
271
+ documentation_uri: https://www.rubydoc.info/gems/prism-merge/1.1.6
272
272
  funding_uri: https://github.com/sponsors/pboling
273
273
  wiki_uri: https://github.com/kettle-rb/prism-merge/wiki
274
274
  news_uri: https://www.railsbling.com/tags/prism-merge
metadata.gz.sig CHANGED
Binary file