ast-merge 2.0.7 → 2.0.9

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: ac53c3bf60949d4ce02f751351d3f4be08368945140d94dd8c090b700c92a0dc
4
- data.tar.gz: 1eed56c786b9f3b9d86183dabe7aecaad8c989a68987abeb4133a12f20aea6ac
3
+ metadata.gz: b3751d56fac80906392c20aabf12c17f7dfc658b677a26866c42d842666e51f6
4
+ data.tar.gz: 41d59c958a7f0edae861bb0b3c5516f1b7120c85b4dc240880c5e9179017d210
5
5
  SHA512:
6
- metadata.gz: d693729fe0371f440fd8223c7896d1cbfc267229a384aa36484d891a3b5b9e59c872646a721f168e696382d23f7fa54d4dd80073615e45dfb30e3e065b9a85d5
7
- data.tar.gz: 487bc8bf2ad2c2031c37f0fccbce0aeffc06aa21d2e0d962b74a71586b99d60011177abc92fc1fddc6b0675cf4e6a1894d60743c23d6e3dcf7746d42f9fd9153
6
+ metadata.gz: 70ae22f513f5de125cbdea642483dbbc9c073f9133dd45b7b78d1a3dc0892757c3007fc252a9eb38b0d61e0423595976b1fb6f4581ff225792d5d12cf133138f
7
+ data.tar.gz: 802ed0bb804cbd92f1d85250726a573e953783e325debba0a87d5fc344cef48bac5b2f15d585e91d789a11f9d6fb02230d1d3971f40172aab5d01217a55c8138
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,43 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.0.9] - 2026-01-02
34
+
35
+ - TAG: [v2.0.9][2.0.9t]
36
+ - COVERAGE: 97.09% -- 2637/2716 lines in 48 files
37
+ - BRANCH COVERAGE: 89.64% -- 883/985 branches in 48 files
38
+ - 98.71% documented
39
+
40
+ ### Fixed
41
+
42
+ - **`NavigableStatement.find_matching` now returns empty array when no criteria specified** -
43
+ Previously, when both `type: nil` and `text: nil` and no block was given, the method would
44
+ match ALL statements (since no conditions filtered anything out). This caused
45
+ `PartialTemplateMerger` to incorrectly report `has_section: true` when `anchor: nil` was passed.
46
+ Now returns an empty array when no criteria are specified.
47
+
48
+ ## [2.0.8] - 2026-01-01
49
+
50
+ - TAG: [v2.0.8][2.0.8t]
51
+ - COVERAGE: 97.09% -- 2636/2715 lines in 48 files
52
+ - BRANCH COVERAGE: 89.73% -- 882/983 branches in 48 files
53
+ - 98.71% documented
54
+
55
+ ### Added
56
+
57
+ - `Ast::Merge::NodeWrapperBase` abstract base class for format-specific node wrappers
58
+ - Provides common functionality shared by `*::Merge::NodeWrapper` classes across gems
59
+ - Handles source context (lines, source string), line info, comments, content extraction
60
+ - Defines abstract `#compute_signature` that subclasses must implement
61
+ - Includes `#node_wrapper?` to distinguish from `NodeTyping::Wrapper`
62
+ - Includes `#underlying_node` to access the raw TreeHaver node (NOT `#unwrap` to avoid
63
+ conflict with `NodeTyping::Wrapper#unwrap` semantics in `FileAnalyzable`)
64
+ - Documents relationship between `NodeWrapperBase` and `NodeTyping::Wrapper`:
65
+ - `NodeWrapperBase`: Format-specific functionality (line info, signatures, type predicates)
66
+ - `NodeTyping::Wrapper`: Custom merge classification (`merge_type` attribute)
67
+ - Nodes can be double-wrapped: `NodeTyping::Wrapper(Format::Merge::NodeWrapper(tree_sitter_node))`
68
+ - Accepts `**options` in initialize for subclass-specific parameters (e.g., `backend`, `document_root`)
69
+
33
70
  ## [2.0.7] - 2026-01-01
34
71
 
35
72
  - TAG: [v2.0.7][2.0.7t]
@@ -407,7 +444,11 @@ Please file a bug if you notice a violation of semantic versioning.
407
444
 
408
445
  - Initial release
409
446
 
410
- [Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v2.0.7...HEAD
447
+ [Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v2.0.9...HEAD
448
+ [2.0.9]: https://github.com/kettle-rb/ast-merge/compare/v2.0.8...v2.0.9
449
+ [2.0.9t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.9
450
+ [2.0.8]: https://github.com/kettle-rb/ast-merge/compare/v2.0.7...v2.0.8
451
+ [2.0.8t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.8
411
452
  [2.0.7]: https://github.com/kettle-rb/ast-merge/compare/v2.0.6...v2.0.7
412
453
  [2.0.7t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.7
413
454
  [2.0.6]: https://github.com/kettle-rb/ast-merge/compare/v2.0.5...v2.0.6
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 Peter H. Boling
3
+ Copyright (c) 2025-2026 Peter H. Boling
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -842,7 +842,7 @@ See [LICENSE.txt](LICENSE.txt) for the official [Copyright Notice](https://opens
842
842
 
843
843
  <ul>
844
844
  <li>
845
- Copyright (c) 2025 Peter H. Boling, of
845
+ Copyright (c) 2025-2026 Peter H. Boling, of
846
846
  <a href="https://discord.gg/3qme4XHNKN">
847
847
  Galtzo.com
848
848
  <picture>
@@ -1032,7 +1032,7 @@ Thanks for RTFM. ☺️
1032
1032
  [📌gitmoji]: https://gitmoji.dev
1033
1033
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1034
1034
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1035
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.640-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1035
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.716-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1036
1036
  [🔐security]: SECURITY.md
1037
1037
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1038
1038
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -93,6 +93,9 @@ module Ast
93
93
  # @yield [NavigableStatement] Optional block for custom matching
94
94
  # @return [Array<NavigableStatement>] Matching statements
95
95
  def find_matching(statements, type: nil, text: nil, &block)
96
+ # If no criteria specified, return empty array (nothing to match)
97
+ return [] if type.nil? && text.nil? && !block_given?
98
+
96
99
  statements.select do |stmt|
97
100
  matches = true
98
101
  matches &&= stmt.type.to_s == type.to_s if type
@@ -57,9 +57,11 @@ module Ast
57
57
  # Sets up the mutex and backend mappings storage.
58
58
  #
59
59
  # @param base [Module] The module extending this one
60
- def self.extended(base)
61
- base.instance_variable_set(:@normalizer_mutex, Mutex.new)
62
- base.instance_variable_set(:@backend_mappings, {})
60
+ class << self
61
+ def extended(base)
62
+ base.instance_variable_set(:@normalizer_mutex, Mutex.new)
63
+ base.instance_variable_set(:@backend_mappings, {})
64
+ end
63
65
  end
64
66
 
65
67
  # Configure the normalizer with initial backend mappings.
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ast
4
+ module Merge
5
+ # Base class for format-specific node wrappers used in *-merge gems.
6
+ #
7
+ # This provides common functionality for wrapping TreeHaver nodes with:
8
+ # - Source context (lines, source string)
9
+ # - Line information (start_line, end_line)
10
+ # - Comment associations (leading_comments, inline_comment)
11
+ # - Content extraction (text, content)
12
+ # - Signature generation (abstract)
13
+ #
14
+ # ## Relationship to NodeTyping::Wrapper
15
+ #
16
+ # This class is DIFFERENT from `Ast::Merge::NodeTyping::Wrapper`:
17
+ #
18
+ # - **NodeWrapperBase**: Provides format-specific functionality (line info,
19
+ # signatures, comments, type predicates). Used to wrap raw TreeHaver nodes
20
+ # with rich context needed for merging.
21
+ #
22
+ # - **NodeTyping::Wrapper**: Adds a custom `merge_type` attribute for merge
23
+ # classification. Used by SmartMergerBase to apply custom typing rules.
24
+ #
25
+ # A node CAN be wrapped by both:
26
+ # ```
27
+ # NodeTyping::Wrapper(Toml::Merge::NodeWrapper(tree_sitter_node))
28
+ # ```
29
+ #
30
+ # The `NodeTyping.unwrap` method handles unwrapping `NodeTyping::Wrapper`,
31
+ # while `NodeWrapperBase#node` provides access to the underlying TreeHaver node.
32
+ #
33
+ # ## Subclass Responsibilities
34
+ #
35
+ # Subclasses MUST implement:
36
+ # - `#compute_signature(node)` - Generate a signature for node matching
37
+ #
38
+ # Subclasses SHOULD implement format-specific type predicates:
39
+ # - TOML: `#table?`, `#pair?`, `#array_of_tables?`, etc.
40
+ # - JSON: `#object?`, `#array?`, `#pair?`, etc.
41
+ # - Bash: `#function_definition?`, `#variable_assignment?`, etc.
42
+ #
43
+ # @example Creating a format-specific wrapper
44
+ # class NodeWrapper < Ast::Merge::NodeWrapperBase
45
+ # def table?
46
+ # type == :table
47
+ # end
48
+ #
49
+ # private
50
+ #
51
+ # def compute_signature(node)
52
+ # case node.type.to_sym
53
+ # when :table
54
+ # [:table, table_name]
55
+ # else
56
+ # [node.type.to_sym]
57
+ # end
58
+ # end
59
+ # end
60
+ #
61
+ # @abstract Subclass and implement `#compute_signature`
62
+ class NodeWrapperBase
63
+ # @return [Object] The wrapped TreeHaver node
64
+ attr_reader :node
65
+
66
+ # @return [Array<String>] Source lines for content extraction
67
+ attr_reader :lines
68
+
69
+ # @return [String] The original source string
70
+ attr_reader :source
71
+
72
+ # @return [Array<Hash>] Leading comments associated with this node
73
+ attr_reader :leading_comments
74
+
75
+ # @return [Hash, nil] Inline/trailing comment on the same line
76
+ attr_reader :inline_comment
77
+
78
+ # @return [Integer, nil] Start line (1-based)
79
+ attr_reader :start_line
80
+
81
+ # @return [Integer, nil] End line (1-based)
82
+ attr_reader :end_line
83
+
84
+ # Initialize the node wrapper with source context.
85
+ #
86
+ # @param node [Object] TreeHaver node to wrap
87
+ # @param lines [Array<String>] Source lines for content extraction
88
+ # @param source [String, nil] Original source string for byte-based text extraction
89
+ # @param leading_comments [Array<Hash>] Comments before this node
90
+ # @param inline_comment [Hash, nil] Inline comment on the node's line
91
+ # @param options [Hash] Additional options for subclasses (forward compatibility)
92
+ def initialize(node, lines:, source: nil, leading_comments: [], inline_comment: nil, **options)
93
+ @node = node
94
+ @lines = lines
95
+ @source = source || lines.join("\n")
96
+ @leading_comments = leading_comments
97
+ @inline_comment = inline_comment
98
+
99
+ # Store additional options for subclasses to use
100
+ process_additional_options(options)
101
+
102
+ # Extract line information from the node (0-indexed to 1-indexed)
103
+ extract_line_info(node)
104
+
105
+ # Handle edge case where end_line might be before start_line
106
+ @end_line = @start_line if @start_line && @end_line && @end_line < @start_line
107
+ end
108
+
109
+ # Process additional options. Override in subclasses to handle format-specific options.
110
+ # @param options [Hash] Additional options
111
+ def process_additional_options(options)
112
+ # Default: no-op. Subclasses can override to process options like :backend, :document_root
113
+ end
114
+
115
+ # Generate a signature for this node for matching purposes.
116
+ # Signatures are used to identify corresponding nodes between template and destination.
117
+ #
118
+ # @return [Array, nil] Signature array or nil if not signaturable
119
+ def signature
120
+ compute_signature(@node)
121
+ end
122
+
123
+ # Get the node type as a symbol.
124
+ # @return [Symbol]
125
+ def type
126
+ @node.type.to_sym
127
+ end
128
+
129
+ # Check if this node has a specific type.
130
+ # @param type_name [Symbol, String] Type to check
131
+ # @return [Boolean]
132
+ def type?(type_name)
133
+ @node.type.to_s == type_name.to_s
134
+ end
135
+
136
+ # Check if this is a freeze node.
137
+ # Override in subclasses if freeze node detection differs.
138
+ # @return [Boolean]
139
+ def freeze_node?
140
+ false
141
+ end
142
+
143
+ # Get the text content for this node by extracting from source using byte positions.
144
+ # @return [String]
145
+ def text
146
+ node_text(@node)
147
+ end
148
+
149
+ # Extract text from a node using byte positions.
150
+ # @param ts_node [Object] The TreeHaver node
151
+ # @return [String]
152
+ def node_text(ts_node)
153
+ return "" unless ts_node.respond_to?(:start_byte) && ts_node.respond_to?(:end_byte)
154
+
155
+ @source[ts_node.start_byte...ts_node.end_byte] || ""
156
+ end
157
+
158
+ # Get the content for this node from source lines.
159
+ # @return [String]
160
+ def content
161
+ return "" unless @start_line && @end_line
162
+
163
+ (@start_line..@end_line).map { |ln| @lines[ln - 1] }.compact.join("\n")
164
+ end
165
+
166
+ # Check if this node is a container (has children for merging).
167
+ # Override in subclasses to define container types.
168
+ # @return [Boolean]
169
+ def container?
170
+ false
171
+ end
172
+
173
+ # Check if this node is a leaf (no mergeable children).
174
+ # @return [Boolean]
175
+ def leaf?
176
+ !container?
177
+ end
178
+
179
+ # Get children wrapped as NodeWrappers.
180
+ # Override in subclasses to return wrapped children.
181
+ # @return [Array<NodeWrapperBase>]
182
+ def children
183
+ return [] unless @node.respond_to?(:each)
184
+
185
+ result = []
186
+ @node.each do |child|
187
+ result << wrap_child(child)
188
+ end
189
+ result
190
+ end
191
+
192
+ # Get mergeable children - the semantically meaningful children for tree merging.
193
+ # Override in subclasses to return format-specific mergeable children.
194
+ # @return [Array<NodeWrapperBase>]
195
+ def mergeable_children
196
+ children
197
+ end
198
+
199
+ # String representation for debugging.
200
+ # @return [String]
201
+ def inspect
202
+ "#<#{self.class.name} type=#{@node.type} lines=#{@start_line}..#{@end_line}>"
203
+ end
204
+
205
+ # Returns true to indicate this is a node wrapper.
206
+ # Used to distinguish from NodeTyping::Wrapper.
207
+ # @return [Boolean]
208
+ def node_wrapper?
209
+ true
210
+ end
211
+
212
+ # Get the underlying TreeHaver node.
213
+ # Note: This is NOT the same as NodeTyping::Wrapper#unwrap which removes
214
+ # the typing wrapper. This method provides access to the raw parser node.
215
+ # @return [Object] The underlying TreeHaver node
216
+ def underlying_node
217
+ @node
218
+ end
219
+
220
+ protected
221
+
222
+ # Wrap a child node. Override in subclasses to use the specific wrapper class.
223
+ # @param child [Object] Child node to wrap
224
+ # @return [NodeWrapperBase]
225
+ def wrap_child(child)
226
+ self.class.new(child, lines: @lines, source: @source)
227
+ end
228
+
229
+ # Compute signature for a node. Subclasses MUST implement this.
230
+ # @param node [Object] The node to compute signature for
231
+ # @return [Array, nil] Signature array
232
+ # @abstract
233
+ def compute_signature(node)
234
+ raise NotImplementedError, "#{self.class} must implement #compute_signature"
235
+ end
236
+
237
+ private
238
+
239
+ # Extract line information from the node.
240
+ # @param node [Object] The node to extract line info from
241
+ def extract_line_info(node)
242
+ if node.respond_to?(:start_point)
243
+ point = node.start_point
244
+ @start_line = extract_row(point) + 1
245
+ end
246
+
247
+ if node.respond_to?(:end_point)
248
+ point = node.end_point
249
+ @end_line = extract_row(point) + 1
250
+ end
251
+ end
252
+
253
+ # Extract row from a point, handling different point implementations.
254
+ # @param point [Object] The point object
255
+ # @return [Integer]
256
+ def extract_row(point)
257
+ if point.respond_to?(:row)
258
+ point.row
259
+ elsif point.is_a?(Hash)
260
+ point[:row]
261
+ else
262
+ 0
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
@@ -5,7 +5,7 @@ module Ast
5
5
  # Version information for Ast::Merge
6
6
  module Version
7
7
  # Current version of the ast-merge gem
8
- VERSION = "2.0.7"
8
+ VERSION = "2.0.9"
9
9
  end
10
10
  VERSION = Version::VERSION # traditional location
11
11
  end
data/lib/ast/merge.rb CHANGED
@@ -153,6 +153,7 @@ module Ast
153
153
  autoload :MergerConfig, "ast/merge/merger_config"
154
154
  autoload :NavigableStatement, "ast/merge/navigable_statement"
155
155
  autoload :NodeTyping, "ast/merge/node_typing"
156
+ autoload :NodeWrapperBase, "ast/merge/node_wrapper_base"
156
157
  autoload :PartialTemplateMerger, "ast/merge/partial_template_merger"
157
158
  autoload :SectionTyping, "ast/merge/section_typing"
158
159
  autoload :SmartMergerBase, "ast/merge/smart_merger_base"
data/sig/ast/merge.rbs CHANGED
@@ -595,7 +595,8 @@ module Ast
595
595
  def self.extended: (Module base) -> void
596
596
 
597
597
  # Configure initial backend mappings
598
- def configure_normalizer: (**Hash[Symbol, Symbol] mappings) -> void
598
+ # @param mappings Keyword args where keys are backend symbols and values are type mapping hashes
599
+ def configure_normalizer: (**untyped) -> void
599
600
 
600
601
  # Register type mappings for a new backend (thread-safe)
601
602
  def register_backend: (Symbol backend, Hash[Symbol, Symbol] mappings) -> void
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ast-merge
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.7
4
+ version: 2.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: '3.2'
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 3.2.2
69
+ version: 3.2.3
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: '3.2'
77
77
  - - ">="
78
78
  - !ruby/object:Gem::Version
79
- version: 3.2.2
79
+ version: 3.2.3
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: kettle-dev
82
82
  requirement: !ruby/object:Gem::Requirement
@@ -321,6 +321,7 @@ files:
321
321
  - lib/ast/merge/node_typing/frozen_wrapper.rb
322
322
  - lib/ast/merge/node_typing/normalizer.rb
323
323
  - lib/ast/merge/node_typing/wrapper.rb
324
+ - lib/ast/merge/node_wrapper_base.rb
324
325
  - lib/ast/merge/partial_template_merger.rb
325
326
  - lib/ast/merge/recipe.rb
326
327
  - lib/ast/merge/recipe/config.rb
@@ -355,10 +356,10 @@ licenses:
355
356
  - MIT
356
357
  metadata:
357
358
  homepage_uri: https://ast-merge.galtzo.com/
358
- source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v2.0.7
359
- changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v2.0.7/CHANGELOG.md
359
+ source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v2.0.9
360
+ changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v2.0.9/CHANGELOG.md
360
361
  bug_tracker_uri: https://github.com/kettle-rb/ast-merge/issues
361
- documentation_uri: https://www.rubydoc.info/gems/ast-merge/2.0.7
362
+ documentation_uri: https://www.rubydoc.info/gems/ast-merge/2.0.9
362
363
  funding_uri: https://github.com/sponsors/pboling
363
364
  wiki_uri: https://github.com/kettle-rb/ast-merge/wiki
364
365
  news_uri: https://www.railsbling.com/tags/ast-merge
metadata.gz.sig CHANGED
Binary file