ast-merge 2.0.6 → 2.0.7
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 +30 -1
- data/README.md +1 -1
- data/lib/ast/merge/node_typing/frozen_wrapper.rb +99 -0
- data/lib/ast/merge/node_typing/normalizer.rb +195 -0
- data/lib/ast/merge/node_typing/wrapper.rb +77 -0
- data/lib/ast/merge/node_typing.rb +3 -161
- data/lib/ast/merge/version.rb +1 -1
- data/sig/ast/merge.rbs +52 -2
- data.tar.gz.sig +0 -0
- metadata +7 -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: ac53c3bf60949d4ce02f751351d3f4be08368945140d94dd8c090b700c92a0dc
|
|
4
|
+
data.tar.gz: 1eed56c786b9f3b9d86183dabe7aecaad8c989a68987abeb4133a12f20aea6ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d693729fe0371f440fd8223c7896d1cbfc267229a384aa36484d891a3b5b9e59c872646a721f168e696382d23f7fa54d4dd80073615e45dfb30e3e065b9a85d5
|
|
7
|
+
data.tar.gz: 487bc8bf2ad2c2031c37f0fccbce0aeffc06aa21d2e0d962b74a71586b99d60011177abc92fc1fddc6b0675cf4e6a1894d60743c23d6e3dcf7746d42f9fd9153
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,33 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [2.0.7] - 2026-01-01
|
|
34
|
+
|
|
35
|
+
- TAG: [v2.0.7][2.0.7t]
|
|
36
|
+
- COVERAGE: 97.31% -- 2569/2640 lines in 47 files
|
|
37
|
+
- BRANCH COVERAGE: 89.87% -- 869/967 branches in 47 files
|
|
38
|
+
- 98.84% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `Ast::Merge::NodeTyping::Normalizer` module for thread-safe backend type normalization
|
|
43
|
+
- Provides shared infrastructure for format-specific normalizers (toml-merge, markdown-merge)
|
|
44
|
+
- Thread-safe backend registration via mutex-protected operations
|
|
45
|
+
- `configure_normalizer` for initial backend mappings configuration
|
|
46
|
+
- `register_backend` for runtime registration of new backends
|
|
47
|
+
- `canonical_type` for mapping backend-specific types to canonical types
|
|
48
|
+
- `wrap` for wrapping nodes with canonical merge_type
|
|
49
|
+
- `registered_backends`, `backend_registered?`, `mappings_for`, `canonical_types` query methods
|
|
50
|
+
- `Ast::Merge::NodeTyping::FrozenWrapper` class for frozen AST nodes
|
|
51
|
+
- Includes `Freezable` behavior for freeze marker support
|
|
52
|
+
- `frozen_node?`, `slice`, `signature` methods
|
|
53
|
+
- Split `NodeTyping` module into separate files following autoload pattern:
|
|
54
|
+
- `ast/merge/node_typing/wrapper.rb`
|
|
55
|
+
- `ast/merge/node_typing/frozen_wrapper.rb`
|
|
56
|
+
- `ast/merge/node_typing/normalizer.rb`
|
|
57
|
+
- Comprehensive specs for `NodeTyping::Normalizer` including thread-safety tests
|
|
58
|
+
- RBS type signatures for `NodeTyping::Normalizer` and `NodeTyping::FrozenWrapper`
|
|
59
|
+
|
|
33
60
|
## [2.0.6] - 2026-01-01
|
|
34
61
|
|
|
35
62
|
- TAG: [v2.0.6][2.0.6t]
|
|
@@ -380,7 +407,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
380
407
|
|
|
381
408
|
- Initial release
|
|
382
409
|
|
|
383
|
-
[Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v2.0.
|
|
410
|
+
[Unreleased]: https://github.com/kettle-rb/ast-merge/compare/v2.0.7...HEAD
|
|
411
|
+
[2.0.7]: https://github.com/kettle-rb/ast-merge/compare/v2.0.6...v2.0.7
|
|
412
|
+
[2.0.7t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.7
|
|
384
413
|
[2.0.6]: https://github.com/kettle-rb/ast-merge/compare/v2.0.5...v2.0.6
|
|
385
414
|
[2.0.6t]: https://github.com/kettle-rb/ast-merge/releases/tag/v2.0.6
|
|
386
415
|
[2.0.5]: https://github.com/kettle-rb/ast-merge/compare/v2.0.4...v2.0.5
|
data/README.md
CHANGED
|
@@ -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.
|
|
1035
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-2.640-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
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ast
|
|
4
|
+
module Merge
|
|
5
|
+
module NodeTyping
|
|
6
|
+
# Wrapper for frozen AST nodes that includes Freezable behavior.
|
|
7
|
+
#
|
|
8
|
+
# FrozenWrapper extends Wrapper to add freeze node semantics, making the
|
|
9
|
+
# wrapped node satisfy both the NodeTyping API and the Freezable API.
|
|
10
|
+
# This enables composition where frozen nodes are:
|
|
11
|
+
# - Wrapped AST nodes (can unwrap to get original)
|
|
12
|
+
# - Typed nodes (have merge_type)
|
|
13
|
+
# - Freeze nodes (satisfy is_a?(Freezable) and freeze_node?)
|
|
14
|
+
#
|
|
15
|
+
# ## Key Distinction from FreezeNodeBase
|
|
16
|
+
#
|
|
17
|
+
# FrozenWrapper and FreezeNodeBase both include Freezable, but they represent
|
|
18
|
+
# fundamentally different concepts:
|
|
19
|
+
#
|
|
20
|
+
# ### FrozenWrapper (this class)
|
|
21
|
+
# - Wraps an AST node that has a freeze marker in its leading comments
|
|
22
|
+
# - The node is still a structural AST node (e.g., a `gem` call in a gemspec)
|
|
23
|
+
# - During matching, we want to match by the underlying node's IDENTITY
|
|
24
|
+
# (e.g., the gem name), NOT by the full content
|
|
25
|
+
# - Signature generation should unwrap and use the underlying node's structure
|
|
26
|
+
# - Example: `# token:freeze\ngem "example_gem", "~> 1.0"` wraps a CallNode
|
|
27
|
+
#
|
|
28
|
+
# ### FreezeNodeBase
|
|
29
|
+
# - Represents an explicit freeze block with `# token:freeze ... # token:unfreeze`
|
|
30
|
+
# - The entire block is opaque content that should be preserved verbatim
|
|
31
|
+
# - During matching, we match by the full CONTENT of the block
|
|
32
|
+
# - Signature generation uses freeze_signature (content-based)
|
|
33
|
+
# - Example: A multi-line comment block with custom formatting
|
|
34
|
+
#
|
|
35
|
+
# ## Signature Generation Behavior
|
|
36
|
+
#
|
|
37
|
+
# When FileAnalyzable#generate_signature encounters a FrozenWrapper:
|
|
38
|
+
# 1. It unwraps to get the underlying AST node
|
|
39
|
+
# 2. Passes the unwrapped node to the signature_generator
|
|
40
|
+
# 3. This allows the signature generator to recognize the node type
|
|
41
|
+
# (e.g., Prism::CallNode) and generate appropriate signatures
|
|
42
|
+
#
|
|
43
|
+
# This is critical because signature generators check for specific AST types.
|
|
44
|
+
# If we passed the wrapper, the generator wouldn't recognize it as a CallNode
|
|
45
|
+
# and would fall back to a generic signature, breaking matching.
|
|
46
|
+
#
|
|
47
|
+
# @example Creating a frozen wrapper
|
|
48
|
+
# frozen = NodeTyping::FrozenWrapper.new(prism_node, :frozen)
|
|
49
|
+
# frozen.freeze_node? # => true
|
|
50
|
+
# frozen.is_a?(Ast::Merge::Freezable) # => true
|
|
51
|
+
# frozen.unwrap # => prism_node
|
|
52
|
+
#
|
|
53
|
+
# @see Wrapper
|
|
54
|
+
# @see Ast::Merge::Freezable
|
|
55
|
+
# @see FreezeNodeBase
|
|
56
|
+
# @see FileAnalyzable#generate_signature
|
|
57
|
+
class FrozenWrapper < Wrapper
|
|
58
|
+
include Ast::Merge::Freezable
|
|
59
|
+
|
|
60
|
+
# Create a frozen wrapper for an AST node.
|
|
61
|
+
#
|
|
62
|
+
# @param node [Object] The AST node to wrap
|
|
63
|
+
# @param merge_type [Symbol] The merge type (defaults to :frozen)
|
|
64
|
+
def initialize(node, merge_type = :frozen)
|
|
65
|
+
super(node, merge_type)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns true to indicate this is a frozen node.
|
|
69
|
+
# Overrides both Wrapper#typed_node? context and provides freeze_node? from Freezable.
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean] true
|
|
72
|
+
def frozen_node?
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns the content of this frozen node.
|
|
77
|
+
# Delegates to the wrapped node's slice method.
|
|
78
|
+
#
|
|
79
|
+
# @return [String] The node content
|
|
80
|
+
def slice
|
|
81
|
+
@node.slice
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns the signature for this frozen node.
|
|
85
|
+
# Uses the freeze_signature from Freezable module.
|
|
86
|
+
#
|
|
87
|
+
# @return [Array] Signature in the form [:FreezeNode, content]
|
|
88
|
+
def signature
|
|
89
|
+
freeze_signature
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Forward inspect to show frozen status.
|
|
93
|
+
def inspect
|
|
94
|
+
"#<NodeTyping::FrozenWrapper merge_type=#{@merge_type.inspect} node=#{@node.inspect}>"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ast
|
|
4
|
+
module Merge
|
|
5
|
+
module NodeTyping
|
|
6
|
+
# Thread-safe backend registration and type normalization for AST merge libraries.
|
|
7
|
+
#
|
|
8
|
+
# Normalizer provides a shared, thread-safe mechanism for registering backend-specific
|
|
9
|
+
# node type mappings and normalizing them to canonical types. This enables portable
|
|
10
|
+
# merge rules across different parsers/backends for the same file format.
|
|
11
|
+
#
|
|
12
|
+
# ## Thread Safety
|
|
13
|
+
#
|
|
14
|
+
# All registration and lookup operations are protected by a mutex to ensure
|
|
15
|
+
# thread-safe access to the backend mappings. This follows the same pattern
|
|
16
|
+
# used in TreeHaver::LanguageRegistry and TreeHaver::PathValidator.
|
|
17
|
+
#
|
|
18
|
+
# ## Usage Pattern
|
|
19
|
+
#
|
|
20
|
+
# File-format-specific merge libraries (e.g., toml-merge, markdown-merge) should:
|
|
21
|
+
# 1. Create their own NodeTypeNormalizer module
|
|
22
|
+
# 2. Include or extend Ast::Merge::NodeTyping::Normalizer
|
|
23
|
+
# 3. Define their canonical types and default backend mappings
|
|
24
|
+
# 4. Call `configure_normalizer` with their initial mappings
|
|
25
|
+
#
|
|
26
|
+
# @example Creating a format-specific normalizer
|
|
27
|
+
# module Toml
|
|
28
|
+
# module Merge
|
|
29
|
+
# module NodeTypeNormalizer
|
|
30
|
+
# extend Ast::Merge::NodeTyping::Normalizer
|
|
31
|
+
#
|
|
32
|
+
# configure_normalizer(
|
|
33
|
+
# tree_sitter_toml: {
|
|
34
|
+
# table_array_element: :array_of_tables,
|
|
35
|
+
# pair: :pair,
|
|
36
|
+
# # ...
|
|
37
|
+
# }.freeze,
|
|
38
|
+
# citrus_toml: {
|
|
39
|
+
# table_array_element: :array_of_tables,
|
|
40
|
+
# pair: :pair,
|
|
41
|
+
# # ...
|
|
42
|
+
# }.freeze
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
# # Optional: Add format-specific helper methods
|
|
46
|
+
# def self.table_type?(type)
|
|
47
|
+
# %i[table array_of_tables].include?(type.to_sym)
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# @see TreeHaver::LanguageRegistry
|
|
54
|
+
# @see TreeHaver::PathValidator
|
|
55
|
+
module Normalizer
|
|
56
|
+
# Called when this module is extended into another module.
|
|
57
|
+
# Sets up the mutex and backend mappings storage.
|
|
58
|
+
#
|
|
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, {})
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Configure the normalizer with initial backend mappings.
|
|
66
|
+
#
|
|
67
|
+
# This should be called once when defining the format-specific normalizer,
|
|
68
|
+
# providing the default backend mappings. Additional backends can be
|
|
69
|
+
# registered later via `register_backend`.
|
|
70
|
+
#
|
|
71
|
+
# @param mappings [Hash{Symbol => Hash{Symbol => Symbol}}] Initial backend mappings
|
|
72
|
+
# Keys are backend identifiers, values are hashes mapping backend types to canonical types
|
|
73
|
+
# @return [void]
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# configure_normalizer(
|
|
77
|
+
# tree_sitter_toml: { table_array_element: :array_of_tables }.freeze,
|
|
78
|
+
# citrus_toml: { table_array_element: :array_of_tables }.freeze
|
|
79
|
+
# )
|
|
80
|
+
def configure_normalizer(**mappings)
|
|
81
|
+
@normalizer_mutex.synchronize do
|
|
82
|
+
mappings.each do |backend, type_mappings|
|
|
83
|
+
@backend_mappings[backend.to_sym] = type_mappings.frozen? ? type_mappings : type_mappings.freeze
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Register type mappings for a new backend.
|
|
90
|
+
#
|
|
91
|
+
# This allows extending the normalizer to support additional parsers
|
|
92
|
+
# beyond those configured initially. Thread-safe for runtime registration.
|
|
93
|
+
#
|
|
94
|
+
# @param backend [Symbol] Backend identifier (e.g., :my_parser)
|
|
95
|
+
# @param mappings [Hash{Symbol => Symbol}] Backend type → canonical type mappings
|
|
96
|
+
# @return [void]
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
# NodeTypeNormalizer.register_backend(:my_parser, {
|
|
100
|
+
# my_table: :table,
|
|
101
|
+
# my_pair: :pair,
|
|
102
|
+
# })
|
|
103
|
+
def register_backend(backend, mappings)
|
|
104
|
+
@normalizer_mutex.synchronize do
|
|
105
|
+
@backend_mappings[backend.to_sym] = mappings.frozen? ? mappings : mappings.freeze
|
|
106
|
+
end
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get the canonical type for a backend-specific type.
|
|
111
|
+
#
|
|
112
|
+
# If no mapping exists, returns the original type unchanged (passthrough).
|
|
113
|
+
# This allows backend-specific types to pass through for backend-specific
|
|
114
|
+
# merge rules.
|
|
115
|
+
#
|
|
116
|
+
# @param backend_type [Symbol, String, nil] The backend's node type
|
|
117
|
+
# @param backend [Symbol] The backend identifier
|
|
118
|
+
# @return [Symbol, nil] Canonical type (or original if no mapping), nil if input was nil
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# NodeTypeNormalizer.canonical_type(:table_array_element, :tree_sitter_toml)
|
|
122
|
+
# # => :array_of_tables
|
|
123
|
+
#
|
|
124
|
+
# NodeTypeNormalizer.canonical_type(:unknown_type, :tree_sitter_toml)
|
|
125
|
+
# # => :unknown_type (passthrough)
|
|
126
|
+
def canonical_type(backend_type, backend = nil)
|
|
127
|
+
return backend_type if backend_type.nil?
|
|
128
|
+
|
|
129
|
+
type_sym = backend_type.to_sym
|
|
130
|
+
@normalizer_mutex.synchronize do
|
|
131
|
+
@backend_mappings.dig(backend, type_sym) || type_sym
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Wrap a node with its canonical type as merge_type.
|
|
136
|
+
#
|
|
137
|
+
# Uses Ast::Merge::NodeTyping.with_merge_type to create a wrapper
|
|
138
|
+
# that delegates all methods to the underlying node while adding
|
|
139
|
+
# a canonical merge_type attribute.
|
|
140
|
+
#
|
|
141
|
+
# @param node [Object] The backend node to wrap (must respond to #type)
|
|
142
|
+
# @param backend [Symbol] The backend identifier
|
|
143
|
+
# @return [Ast::Merge::NodeTyping::Wrapper] Wrapped node with canonical merge_type
|
|
144
|
+
#
|
|
145
|
+
# @example
|
|
146
|
+
# wrapped = NodeTypeNormalizer.wrap(node, :tree_sitter_toml)
|
|
147
|
+
# wrapped.type # => :table_array_element (original)
|
|
148
|
+
# wrapped.merge_type # => :array_of_tables (canonical)
|
|
149
|
+
# wrapped.unwrap # => node (original node)
|
|
150
|
+
def wrap(node, backend)
|
|
151
|
+
canonical = canonical_type(node.type, backend)
|
|
152
|
+
Ast::Merge::NodeTyping.with_merge_type(node, canonical)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Get all registered backends.
|
|
156
|
+
#
|
|
157
|
+
# @return [Array<Symbol>] Backend identifiers
|
|
158
|
+
def registered_backends
|
|
159
|
+
@normalizer_mutex.synchronize do
|
|
160
|
+
@backend_mappings.keys
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Check if a backend is registered.
|
|
165
|
+
#
|
|
166
|
+
# @param backend [Symbol] Backend identifier
|
|
167
|
+
# @return [Boolean]
|
|
168
|
+
def backend_registered?(backend)
|
|
169
|
+
@normalizer_mutex.synchronize do
|
|
170
|
+
@backend_mappings.key?(backend.to_sym)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Get the mappings for a specific backend.
|
|
175
|
+
#
|
|
176
|
+
# @param backend [Symbol] Backend identifier
|
|
177
|
+
# @return [Hash{Symbol => Symbol}, nil] The mappings or nil if not registered
|
|
178
|
+
def mappings_for(backend)
|
|
179
|
+
@normalizer_mutex.synchronize do
|
|
180
|
+
@backend_mappings[backend.to_sym]
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Get all canonical types across all backends.
|
|
185
|
+
#
|
|
186
|
+
# @return [Array<Symbol>] Unique canonical type symbols
|
|
187
|
+
def canonical_types
|
|
188
|
+
@normalizer_mutex.synchronize do
|
|
189
|
+
@backend_mappings.values.flat_map(&:values).uniq
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ast
|
|
4
|
+
module Merge
|
|
5
|
+
module NodeTyping
|
|
6
|
+
# Node wrapper that adds a merge_type attribute to an existing node.
|
|
7
|
+
# This uses a simple delegation pattern to preserve all original node
|
|
8
|
+
# behavior while adding the merge_type.
|
|
9
|
+
class Wrapper
|
|
10
|
+
# @return [Object] The original node being wrapped
|
|
11
|
+
attr_reader :node
|
|
12
|
+
|
|
13
|
+
# @return [Symbol] The custom merge type for this node
|
|
14
|
+
attr_reader :merge_type
|
|
15
|
+
|
|
16
|
+
# Create a new node type wrapper.
|
|
17
|
+
#
|
|
18
|
+
# @param node [Object] The original node to wrap
|
|
19
|
+
# @param merge_type [Symbol] The custom merge type
|
|
20
|
+
def initialize(node, merge_type)
|
|
21
|
+
@node = node
|
|
22
|
+
@merge_type = merge_type
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Delegate all unknown methods to the wrapped node.
|
|
26
|
+
# This allows the wrapper to be used transparently in place of the node.
|
|
27
|
+
def method_missing(method, *args, &block)
|
|
28
|
+
if @node.respond_to?(method)
|
|
29
|
+
@node.send(method, *args, &block)
|
|
30
|
+
else
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Check if the wrapped node responds to a method.
|
|
36
|
+
def respond_to_missing?(method, include_private = false)
|
|
37
|
+
@node.respond_to?(method, include_private) || super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns true to indicate this is a node type wrapper.
|
|
41
|
+
def typed_node?
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Unwrap to get the original node.
|
|
46
|
+
# @return [Object] The original unwrapped node
|
|
47
|
+
def unwrap
|
|
48
|
+
@node
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Forward equality check to the wrapped node.
|
|
52
|
+
def ==(other)
|
|
53
|
+
if other.is_a?(Wrapper)
|
|
54
|
+
@node == other.node && @merge_type == other.merge_type
|
|
55
|
+
else
|
|
56
|
+
@node == other
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Forward hash to the wrapped node.
|
|
61
|
+
def hash
|
|
62
|
+
[@node, @merge_type].hash
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Forward eql? to the wrapped node.
|
|
66
|
+
def eql?(other)
|
|
67
|
+
self == other
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Forward inspect to show both the type and node.
|
|
71
|
+
def inspect
|
|
72
|
+
"#<NodeTyping::Wrapper merge_type=#{@merge_type.inspect} node=#{@node.inspect}>"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -46,167 +46,9 @@ module Ast
|
|
|
46
46
|
# @see MergerConfig
|
|
47
47
|
# @see ConflictResolverBase
|
|
48
48
|
module NodeTyping
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class Wrapper
|
|
53
|
-
# @return [Object] The original node being wrapped
|
|
54
|
-
attr_reader :node
|
|
55
|
-
|
|
56
|
-
# @return [Symbol] The custom merge type for this node
|
|
57
|
-
attr_reader :merge_type
|
|
58
|
-
|
|
59
|
-
# Create a new node type wrapper.
|
|
60
|
-
#
|
|
61
|
-
# @param node [Object] The original node to wrap
|
|
62
|
-
# @param merge_type [Symbol] The custom merge type
|
|
63
|
-
def initialize(node, merge_type)
|
|
64
|
-
@node = node
|
|
65
|
-
@merge_type = merge_type
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Delegate all unknown methods to the wrapped node.
|
|
69
|
-
# This allows the wrapper to be used transparently in place of the node.
|
|
70
|
-
def method_missing(method, *args, &block)
|
|
71
|
-
if @node.respond_to?(method)
|
|
72
|
-
@node.send(method, *args, &block)
|
|
73
|
-
else
|
|
74
|
-
super
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Check if the wrapped node responds to a method.
|
|
79
|
-
def respond_to_missing?(method, include_private = false)
|
|
80
|
-
@node.respond_to?(method, include_private) || super
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Returns true to indicate this is a node type wrapper.
|
|
84
|
-
def typed_node?
|
|
85
|
-
true
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Unwrap to get the original node.
|
|
89
|
-
# @return [Object] The original unwrapped node
|
|
90
|
-
def unwrap
|
|
91
|
-
@node
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Forward equality check to the wrapped node.
|
|
95
|
-
def ==(other)
|
|
96
|
-
if other.is_a?(Wrapper)
|
|
97
|
-
@node == other.node && @merge_type == other.merge_type
|
|
98
|
-
else
|
|
99
|
-
@node == other
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Forward hash to the wrapped node.
|
|
104
|
-
def hash
|
|
105
|
-
[@node, @merge_type].hash
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Forward eql? to the wrapped node.
|
|
109
|
-
def eql?(other)
|
|
110
|
-
self == other
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Forward inspect to show both the type and node.
|
|
114
|
-
def inspect
|
|
115
|
-
"#<NodeTyping::Wrapper merge_type=#{@merge_type.inspect} node=#{@node.inspect}>"
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Wrapper for frozen AST nodes that includes Freezable behavior.
|
|
120
|
-
#
|
|
121
|
-
# FrozenWrapper extends Wrapper to add freeze node semantics, making the
|
|
122
|
-
# wrapped node satisfy both the NodeTyping API and the Freezable API.
|
|
123
|
-
# This enables composition where frozen nodes are:
|
|
124
|
-
# - Wrapped AST nodes (can unwrap to get original)
|
|
125
|
-
# - Typed nodes (have merge_type)
|
|
126
|
-
# - Freeze nodes (satisfy is_a?(Freezable) and freeze_node?)
|
|
127
|
-
#
|
|
128
|
-
# ## Key Distinction from FreezeNodeBase
|
|
129
|
-
#
|
|
130
|
-
# FrozenWrapper and FreezeNodeBase both include Freezable, but they represent
|
|
131
|
-
# fundamentally different concepts:
|
|
132
|
-
#
|
|
133
|
-
# ### FrozenWrapper (this class)
|
|
134
|
-
# - Wraps an AST node that has a freeze marker in its leading comments
|
|
135
|
-
# - The node is still a structural AST node (e.g., a `gem` call in a gemspec)
|
|
136
|
-
# - During matching, we want to match by the underlying node's IDENTITY
|
|
137
|
-
# (e.g., the gem name), NOT by the full content
|
|
138
|
-
# - Signature generation should unwrap and use the underlying node's structure
|
|
139
|
-
# - Example: `# token:freeze\ngem "example_gem", "~> 1.0"` wraps a CallNode
|
|
140
|
-
#
|
|
141
|
-
# ### FreezeNodeBase
|
|
142
|
-
# - Represents an explicit freeze block with `# token:freeze ... # token:unfreeze`
|
|
143
|
-
# - The entire block is opaque content that should be preserved verbatim
|
|
144
|
-
# - During matching, we match by the full CONTENT of the block
|
|
145
|
-
# - Signature generation uses freeze_signature (content-based)
|
|
146
|
-
# - Example: A multi-line comment block with custom formatting
|
|
147
|
-
#
|
|
148
|
-
# ## Signature Generation Behavior
|
|
149
|
-
#
|
|
150
|
-
# When FileAnalyzable#generate_signature encounters a FrozenWrapper:
|
|
151
|
-
# 1. It unwraps to get the underlying AST node
|
|
152
|
-
# 2. Passes the unwrapped node to the signature_generator
|
|
153
|
-
# 3. This allows the signature generator to recognize the node type
|
|
154
|
-
# (e.g., Prism::CallNode) and generate appropriate signatures
|
|
155
|
-
#
|
|
156
|
-
# This is critical because signature generators check for specific AST types.
|
|
157
|
-
# If we passed the wrapper, the generator wouldn't recognize it as a CallNode
|
|
158
|
-
# and would fall back to a generic signature, breaking matching.
|
|
159
|
-
#
|
|
160
|
-
# @example Creating a frozen wrapper
|
|
161
|
-
# frozen = NodeTyping::FrozenWrapper.new(prism_node, :frozen)
|
|
162
|
-
# frozen.freeze_node? # => true
|
|
163
|
-
# frozen.is_a?(Ast::Merge::Freezable) # => true
|
|
164
|
-
# frozen.unwrap # => prism_node
|
|
165
|
-
#
|
|
166
|
-
# @see Wrapper
|
|
167
|
-
# @see Ast::Merge::Freezable
|
|
168
|
-
# @see FreezeNodeBase
|
|
169
|
-
# @see FileAnalyzable#generate_signature
|
|
170
|
-
class FrozenWrapper < Wrapper
|
|
171
|
-
include Ast::Merge::Freezable
|
|
172
|
-
|
|
173
|
-
# Create a frozen wrapper for an AST node.
|
|
174
|
-
#
|
|
175
|
-
# @param node [Object] The AST node to wrap
|
|
176
|
-
# @param merge_type [Symbol] The merge type (defaults to :frozen)
|
|
177
|
-
def initialize(node, merge_type = :frozen)
|
|
178
|
-
super(node, merge_type)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Returns true to indicate this is a frozen node.
|
|
182
|
-
# Overrides both Wrapper#typed_node? context and provides freeze_node? from Freezable.
|
|
183
|
-
#
|
|
184
|
-
# @return [Boolean] true
|
|
185
|
-
def frozen_node?
|
|
186
|
-
true
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# Returns the content of this frozen node.
|
|
190
|
-
# Delegates to the wrapped node's slice method.
|
|
191
|
-
#
|
|
192
|
-
# @return [String] The node content
|
|
193
|
-
def slice
|
|
194
|
-
@node.slice
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Returns the signature for this frozen node.
|
|
198
|
-
# Uses the freeze_signature from Freezable module.
|
|
199
|
-
#
|
|
200
|
-
# @return [Array] Signature in the form [:FreezeNode, content]
|
|
201
|
-
def signature
|
|
202
|
-
freeze_signature
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Forward inspect to show frozen status.
|
|
206
|
-
def inspect
|
|
207
|
-
"#<NodeTyping::FrozenWrapper merge_type=#{@merge_type.inspect} node=#{@node.inspect}>"
|
|
208
|
-
end
|
|
209
|
-
end
|
|
49
|
+
autoload :FrozenWrapper, "ast/merge/node_typing/frozen_wrapper"
|
|
50
|
+
autoload :Normalizer, "ast/merge/node_typing/normalizer"
|
|
51
|
+
autoload :Wrapper, "ast/merge/node_typing/wrapper"
|
|
210
52
|
|
|
211
53
|
class << self
|
|
212
54
|
# Wrap a node with a custom merge_type.
|
data/lib/ast/merge/version.rb
CHANGED
data/sig/ast/merge.rbs
CHANGED
|
@@ -574,9 +574,59 @@ module Ast
|
|
|
574
574
|
def inspect: () -> String
|
|
575
575
|
end
|
|
576
576
|
|
|
577
|
+
# Wrapper for frozen AST nodes that includes Freezable behavior
|
|
578
|
+
class FrozenWrapper < Wrapper
|
|
579
|
+
include Freezable
|
|
580
|
+
|
|
581
|
+
def initialize: (untyped node, ?Symbol merge_type) -> void
|
|
582
|
+
def frozen_node?: () -> bool
|
|
583
|
+
def slice: () -> String
|
|
584
|
+
def signature: () -> Array[untyped]
|
|
585
|
+
def inspect: () -> String
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Thread-safe backend registration and type normalization module.
|
|
589
|
+
# Extended by format-specific normalizers (e.g., Toml::Merge::NodeTypeNormalizer).
|
|
590
|
+
module Normalizer
|
|
591
|
+
@normalizer_mutex: Mutex
|
|
592
|
+
@backend_mappings: Hash[Symbol, Hash[Symbol, Symbol]]
|
|
593
|
+
|
|
594
|
+
# Called when this module is extended into another module
|
|
595
|
+
def self.extended: (Module base) -> void
|
|
596
|
+
|
|
597
|
+
# Configure initial backend mappings
|
|
598
|
+
def configure_normalizer: (**Hash[Symbol, Symbol] mappings) -> void
|
|
599
|
+
|
|
600
|
+
# Register type mappings for a new backend (thread-safe)
|
|
601
|
+
def register_backend: (Symbol backend, Hash[Symbol, Symbol] mappings) -> void
|
|
602
|
+
|
|
603
|
+
# Get the canonical type for a backend-specific type
|
|
604
|
+
def canonical_type: (Symbol | String | nil backend_type, ?Symbol? backend) -> Symbol?
|
|
605
|
+
|
|
606
|
+
# Wrap a node with its canonical type as merge_type
|
|
607
|
+
def wrap: (untyped node, Symbol backend) -> Wrapper
|
|
608
|
+
|
|
609
|
+
# Get all registered backends
|
|
610
|
+
def registered_backends: () -> Array[Symbol]
|
|
611
|
+
|
|
612
|
+
# Check if a backend is registered
|
|
613
|
+
def backend_registered?: (Symbol | String backend) -> bool
|
|
614
|
+
|
|
615
|
+
# Get the mappings for a specific backend
|
|
616
|
+
def mappings_for: (Symbol backend) -> Hash[Symbol, Symbol]?
|
|
617
|
+
|
|
618
|
+
# Get all canonical types across all backends
|
|
619
|
+
def canonical_types: () -> Array[Symbol]
|
|
620
|
+
end
|
|
621
|
+
|
|
577
622
|
def self.with_merge_type: (untyped node, Symbol merge_type) -> Wrapper
|
|
578
|
-
def self.
|
|
579
|
-
def self.
|
|
623
|
+
def self.frozen: (untyped node, ?Symbol merge_type) -> FrozenWrapper
|
|
624
|
+
def self.frozen_node?: (untyped node) -> bool
|
|
625
|
+
def self.typed_node?: (untyped node) -> bool
|
|
626
|
+
def self.merge_type_for: (untyped node) -> Symbol?
|
|
627
|
+
def self.unwrap: (untyped node) -> untyped
|
|
628
|
+
def self.process: (untyped node, node_typing_hash? typing_config) -> untyped?
|
|
629
|
+
def self.validate!: (node_typing_hash? typing_config) -> void
|
|
580
630
|
end
|
|
581
631
|
end
|
|
582
632
|
end
|
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.
|
|
4
|
+
version: 2.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -318,6 +318,9 @@ files:
|
|
|
318
318
|
- lib/ast/merge/merger_config.rb
|
|
319
319
|
- lib/ast/merge/navigable_statement.rb
|
|
320
320
|
- lib/ast/merge/node_typing.rb
|
|
321
|
+
- lib/ast/merge/node_typing/frozen_wrapper.rb
|
|
322
|
+
- lib/ast/merge/node_typing/normalizer.rb
|
|
323
|
+
- lib/ast/merge/node_typing/wrapper.rb
|
|
321
324
|
- lib/ast/merge/partial_template_merger.rb
|
|
322
325
|
- lib/ast/merge/recipe.rb
|
|
323
326
|
- lib/ast/merge/recipe/config.rb
|
|
@@ -352,10 +355,10 @@ licenses:
|
|
|
352
355
|
- MIT
|
|
353
356
|
metadata:
|
|
354
357
|
homepage_uri: https://ast-merge.galtzo.com/
|
|
355
|
-
source_code_uri: https://github.com/kettle-rb/ast-merge/tree/v2.0.
|
|
356
|
-
changelog_uri: https://github.com/kettle-rb/ast-merge/blob/v2.0.
|
|
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
|
|
357
360
|
bug_tracker_uri: https://github.com/kettle-rb/ast-merge/issues
|
|
358
|
-
documentation_uri: https://www.rubydoc.info/gems/ast-merge/2.0.
|
|
361
|
+
documentation_uri: https://www.rubydoc.info/gems/ast-merge/2.0.7
|
|
359
362
|
funding_uri: https://github.com/sponsors/pboling
|
|
360
363
|
wiki_uri: https://github.com/kettle-rb/ast-merge/wiki
|
|
361
364
|
news_uri: https://www.railsbling.com/tags/ast-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|