json-merge 1.0.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f5ae754dce8048a02861e616c3468c3a8411da18deb9140addd8433c476914f
4
- data.tar.gz: '093aa8f13467096d683b3cc7bf4a5ffc88dfd4f3bc81e3f8cde5a3e2eaf5974c'
3
+ metadata.gz: db4707dba4ede9ef1ecab447fafe24788891d076c69714fc700f17a35d8f5bae
4
+ data.tar.gz: 5f88a2d90a1695c3c7ffe4d4f08c83090fae455f6cd1810a29b331045f636f31
5
5
  SHA512:
6
- metadata.gz: bbd7f9c51d2f32b6495ff656701ecc142826e8b4aa4aa2ec56b3fa0f700025ea1921bab8aa7ab9393a5cd8d808bd206cd980976a9d2864dbc1abaf02edd6f8db
7
- data.tar.gz: ee0e3f3d5d71a00e13a7b070b9c28bf912fcb1340efdca88bed060ab6aa1316b4e434d880b5da8ff77b73be4e0c9d80731941095173922da2c90c2c1e62ea046
6
+ metadata.gz: d4810ca402f5af373769e6a69a73ace769f6d6f11e61c3d7a2a1b1186672387bc41f92b40fc74a327648a856d38547a60b120583fddd3329b17941b0e0ea51e9
7
+ data.tar.gz: 1e04ca747b1f07f3177626e351947c9dd40cd78f810d1b285925a3c0bee0bbeecfca2891872b53b288d33ab66fb171b0f4ad3d84dc919f2ddf75b0d181b3fbb4
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,60 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.1.0] - 2026-01-12
34
+
35
+ - TAG: [v1.1.0][1.1.0t]
36
+ - COVERAGE: 95.77% -- 589/615 lines in 10 files
37
+ - BRANCH COVERAGE: 78.82% -- 227/288 branches in 10 files
38
+ - 96.63% documented
39
+
40
+ ### Added
41
+
42
+ - bin/rspec-ffi to run FFI isolated specs
43
+ - Also bin/rake ffi_specs
44
+ - FFI backend isolation for test suite
45
+ - Added `bin/rspec-ffi` script to run FFI specs in isolation (before MRI backend loads)
46
+ - Added `spec/spec_ffi_helper.rb` for FFI-specific test configuration
47
+ - Updated Rakefile with `ffi_specs` and `remaining_specs` tasks
48
+ - The `:test` task now runs FFI specs first, then remaining specs
49
+
50
+ ### Changed
51
+
52
+ - ast-merge v4.0.2
53
+ - Includes Ast::Merge::EmitterBase
54
+ - tree_haver v5.0.1
55
+ - Many Backend improvements
56
+ - Many error handling improvements
57
+ - **Simplified dependency_tags.rb**: Removed redundant debug code
58
+ - Removed `JSON_MERGE_DEBUG` env var handling (use `TREE_HAVER_DEBUG` instead)
59
+ - tree_haver's debug output now respects blocked backends via `compute_blocked_backends`
60
+ - Avoids accidentally loading MRI backend during FFI-only test runs
61
+
62
+ ### Removed
63
+
64
+ - **Obsolete Tests**: Removed 3 obsolete integration tests
65
+ - Tests for `add_node_to_result` and `add_wrapper_to_result` methods
66
+ - These methods don't exist in the `:batch` strategy (ConflictResolver now uses Emitter)
67
+ - Tests were for old `:node` strategy pattern
68
+
69
+ ### Fixed
70
+
71
+ - **NodeWrapper signature tests**: Updated tests to expect `:root_object`/`:root_array` for root-level containers
72
+ - Root-level objects now correctly return `[:root_object, ...]` instead of `[:object, ...]`
73
+ - Root-level arrays now correctly return `[:root_array]` instead of `[:array, count]`
74
+ - Added `:parent` method stubs to mock node tests for `root_level_container?` compatibility
75
+ - **ConflictResolver#emit_node**: Fixed handling of pair nodes with object values
76
+ - When emitting a pair like `"features": {...}`, the value was treated as raw text
77
+ - Now correctly detects when a pair's value is an object container
78
+ - Recursively emits object structure using `emit_nested_object_start/end`
79
+ - Treats arrays as atomic values (emits as raw text)
80
+ - Prevents double key emission and invalid JSON output in nested merges
81
+ - **ConflictResolver#merge_matched_nodes_to_emitter**: Fixed array handling in merge logic
82
+ - Arrays are now treated atomically and replaced based on preference setting
83
+ - Only objects (not arrays) are recursively merged
84
+ - Fixes potential "expected object key, got number" errors when merging arrays
85
+ - Arrays like `[1,2,3]` are now correctly replaced with `[4,5]` based on preference
86
+
33
87
  ## [1.0.0] - 2026-01-06
34
88
 
35
89
  - TAG: [v1.0.0][1.0.0t]
@@ -43,6 +97,8 @@ Please file a bug if you notice a violation of semantic versioning.
43
97
 
44
98
  ### Security
45
99
 
46
- [Unreleased]: https://github.com/kettle-rb/json-merge/compare/v1.0.0...HEAD
100
+ [Unreleased]: https://github.com/kettle-rb/json-merge/compare/v1.1.0...HEAD
101
+ [1.1.0]: https://github.com/kettle-rb/json-merge/compare/v1.0.0...v1.1.0
102
+ [1.1.0t]: https://github.com/kettle-rb/json-merge/releases/tag/v1.1.0
47
103
  [1.0.0]: https://github.com/kettle-rb/json-merge/compare/f1cc25b1d9b79c598270e3aa203fa56787e6c6fc...v1.0.0
48
104
  [1.0.0t]: https://github.com/kettle-rb/json-merge/tags/v1.0.0
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  | 📍 NOTE |
2
2
  | --- |
3
- | RubyGems (the [GitHub org](https://github.com/rubygems/), not the website) [suffered](https://joel.drapper.me/p/ruby-central-security-measures/) a [hostile takeover](https://pup-e.com/blog/goodbye-rubygems/) in September 2025. |
4
- | Ultimately [4 maintainers](https://www.reddit.com/r/ruby/s/gOk42POCaV) were [hard removed](https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q) and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
- | It is a [complicated story](https://joel.drapper.me/p/ruby-central-takeover/) which is difficult to [parse quickly](https://joel.drapper.me/p/ruby-central-fact-check/). |
6
- | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/rubygems/POLICIES.md?plain=1#L187-L196) and [bundler](https://github.com/ruby/rubygems/blob/b1ab33a3d52310a84d16b193991af07f5a6a07c0/doc/bundler/playbooks/TEAM_CHANGES.md), and those [policies were not followed](https://www.reddit.com/r/ruby/comments/1ove9vp/rubycentral_hates_this_one_fact/). |
7
- | I'm adding notes like this to gems because I [don't condone theft](https://joel.drapper.me/p/ruby-central/) of repositories or gems from their rightful owners. |
3
+ | RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. |
4
+ | Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
+ | It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. |
6
+ | Simply put - there was active policy for adding or removing maintainers/owners of [rubygems][rubygems-maint-policy] and [bundler][bundler-maint-policy], and those [policies were not followed][policy-fail]. |
7
+ | I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. |
8
8
  | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
9
- | Disenfranchised former-maintainers have started [gem.coop](https://gem.coop). |
9
+ | Disenfranchised former-maintainers have started [gem.coop][gem-coop]. |
10
10
  | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
11
- | The ["Technology for Humans: Joel Draper"](https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6) podcast episode by [reinteractive](https://reinteractive.com/ruby-on-rails) is the most cogent summary I'm aware of. |
12
- | See [here](https://github.com/gem-coop/gem.coop/issues/12), [here](https://gem.coop) and [here](https://martinemde.com/2025/10/05/announcing-gem-coop.html) for more info on what comes next. |
13
- | What I'm doing: A (WIP) proposal for [bundler/gem scopes](https://github.com/galtzo-floss/bundle-namespace), and a (WIP) proposal for a federated [gem server](https://github.com/galtzo-floss/gem-server). |
11
+ | The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. |
12
+ | See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. |
13
+ | What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. |
14
14
 
15
15
  [rubygems-org]: https://github.com/rubygems/
16
16
  [draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
@@ -99,7 +99,7 @@ File.write("merged.json", result.to_json)
99
99
 
100
100
  ### The `*-merge` Gem Family
101
101
 
102
- This gem is part of a family of gems that provide intelligent merging for various file formats:
102
+ The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
103
103
 
104
104
  | Gem | Language<br>/ Format | Parser Backend(s) | Description |
105
105
  |------------------------------------------|----------------------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
@@ -117,12 +117,36 @@ This gem is part of a family of gems that provide intelligent merging for variou
117
117
  | [rbs-merge][rbs-merge] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
118
118
  | [toml-merge][toml-merge] | TOML | [Citrus + toml-rb][toml-rb] (default, via tree_haver), [tree-sitter-toml][ts-toml] (via tree_haver) | Smart merge for TOML files |
119
119
 
120
+ #### Backend Platform Compatibility
121
+
122
+ tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
123
+
124
+ | Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
125
+ |------------------------------------------------|:---:|:-----:|:-----------:|-----------------------------------------------------|
126
+ | **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
127
+ | **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
128
+ | **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
129
+ | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
130
+ | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
131
+ | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
132
+ | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
133
+ | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
134
+ | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
135
+
136
+ **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
137
+
138
+ **Why some backends don't work on certain platforms**:
139
+
140
+ - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
141
+ - **TruffleRuby**: Has C API emulation via Sulong/LLVM, but it doesn't expose all MRI internals that native extensions require (e.g., `RBasic.flags`, `rb_gc_writebarrier`)
142
+ - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
143
+
120
144
  **Example implementations** for the gem templating use case:
121
145
 
122
- | Gem | Purpose | Description |
123
- | --- | --- | --- |
124
- | [kettle-dev](https://github.com/kettle-rb/kettle-dev) | Gem Development | Gem templating tool using `*-merge` gems |
125
- | [kettle-jem](https://github.com/kettle-rb/kettle-jem) | Gem Templating | Gem template library with smart merge support |
146
+ | Gem | Purpose | Description |
147
+ |--------------------------|-----------------|-----------------------------------------------|
148
+ | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
149
+ | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
126
150
 
127
151
  [tree_haver]: https://github.com/kettle-rb/tree_haver
128
152
  [ast-merge]: https://github.com/kettle-rb/ast-merge
@@ -139,22 +163,82 @@ This gem is part of a family of gems that provide intelligent merging for variou
139
163
  [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
140
164
  [kettle-dev]: https://github.com/kettle-rb/kettle-dev
141
165
  [kettle-jem]: https://github.com/kettle-rb/kettle-jem
166
+ [tree_haver-gem]: https://bestgems.org/gems/tree_haver
167
+ [ast-merge-gem]: https://bestgems.org/gems/ast-merge
168
+ [prism-merge-gem]: https://bestgems.org/gems/prism-merge
169
+ [psych-merge-gem]: https://bestgems.org/gems/psych-merge
170
+ [json-merge-gem]: https://bestgems.org/gems/json-merge
171
+ [jsonc-merge-gem]: https://bestgems.org/gems/jsonc-merge
172
+ [bash-merge-gem]: https://bestgems.org/gems/bash-merge
173
+ [rbs-merge-gem]: https://bestgems.org/gems/rbs-merge
174
+ [dotenv-merge-gem]: https://bestgems.org/gems/dotenv-merge
175
+ [toml-merge-gem]: https://bestgems.org/gems/toml-merge
176
+ [markdown-merge-gem]: https://bestgems.org/gems/markdown-merge
177
+ [markly-merge-gem]: https://bestgems.org/gems/markly-merge
178
+ [commonmarker-merge-gem]: https://bestgems.org/gems/commonmarker-merge
179
+ [kettle-dev-gem]: https://bestgems.org/gems/kettle-dev
180
+ [kettle-jem-gem]: https://bestgems.org/gems/kettle-jem
181
+ [tree_haver-gem-i]: https://img.shields.io/gem/v/tree_haver.svg
182
+ [ast-merge-gem-i]: https://img.shields.io/gem/v/ast-merge.svg
183
+ [prism-merge-gem-i]: https://img.shields.io/gem/v/prism-merge.svg
184
+ [psych-merge-gem-i]: https://img.shields.io/gem/v/psych-merge.svg
185
+ [json-merge-gem-i]: https://img.shields.io/gem/v/json-merge.svg
186
+ [jsonc-merge-gem-i]: https://img.shields.io/gem/v/jsonc-merge.svg
187
+ [bash-merge-gem-i]: https://img.shields.io/gem/v/bash-merge.svg
188
+ [rbs-merge-gem-i]: https://img.shields.io/gem/v/rbs-merge.svg
189
+ [dotenv-merge-gem-i]: https://img.shields.io/gem/v/dotenv-merge.svg
190
+ [toml-merge-gem-i]: https://img.shields.io/gem/v/toml-merge.svg
191
+ [markdown-merge-gem-i]: https://img.shields.io/gem/v/markdown-merge.svg
192
+ [markly-merge-gem-i]: https://img.shields.io/gem/v/markly-merge.svg
193
+ [commonmarker-merge-gem-i]: https://img.shields.io/gem/v/commonmarker-merge.svg
194
+ [kettle-dev-gem-i]: https://img.shields.io/gem/v/kettle-dev.svg
195
+ [kettle-jem-gem-i]: https://img.shields.io/gem/v/kettle-jem.svg
196
+ [tree_haver-ci-i]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
197
+ [ast-merge-ci-i]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg
198
+ [prism-merge-ci-i]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
199
+ [psych-merge-ci-i]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml/badge.svg
200
+ [json-merge-ci-i]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
201
+ [jsonc-merge-ci-i]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml/badge.svg
202
+ [bash-merge-ci-i]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
203
+ [rbs-merge-ci-i]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml/badge.svg
204
+ [dotenv-merge-ci-i]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml/badge.svg
205
+ [toml-merge-ci-i]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml/badge.svg
206
+ [markdown-merge-ci-i]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml/badge.svg
207
+ [markly-merge-ci-i]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml/badge.svg
208
+ [commonmarker-merge-ci-i]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml/badge.svg
209
+ [kettle-dev-ci-i]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml/badge.svg
210
+ [kettle-jem-ci-i]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml/badge.svg
211
+ [tree_haver-ci]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
212
+ [ast-merge-ci]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml
213
+ [prism-merge-ci]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
214
+ [psych-merge-ci]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml
215
+ [json-merge-ci]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
216
+ [jsonc-merge-ci]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml
217
+ [bash-merge-ci]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
218
+ [rbs-merge-ci]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml
219
+ [dotenv-merge-ci]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml
220
+ [toml-merge-ci]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml
221
+ [markdown-merge-ci]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml
222
+ [markly-merge-ci]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml
223
+ [commonmarker-merge-ci]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml
224
+ [kettle-dev-ci]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml
225
+ [kettle-jem-ci]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml
142
226
  [prism]: https://github.com/ruby/prism
143
227
  [psych]: https://github.com/ruby/psych
144
228
  [ts-json]: https://github.com/tree-sitter/tree-sitter-json
229
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
145
230
  [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
231
+ [ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
146
232
  [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
233
+ [dotenv]: https://github.com/bkeepers/dotenv
147
234
  [rbs]: https://github.com/ruby/rbs
148
235
  [toml-rb]: https://github.com/emancu/toml-rb
236
+ [toml]: https://github.com/jm/toml
149
237
  [markly]: https://github.com/ioquatix/markly
150
238
  [commonmarker]: https://github.com/gjtorikian/commonmarker
151
-
152
-
153
- The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
154
-
155
-
156
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
157
- [dotenv]: https://github.com/bkeepers/dotenv
239
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
240
+ [tree_stump]: https://github.com/joker1007/tree_stump
241
+ [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
158
242
 
159
243
  ## 💡 Info you can shake a stick at
160
244
 
@@ -939,7 +1023,7 @@ Thanks for RTFM. ☺️
939
1023
  [📌gitmoji]: https://gitmoji.dev
940
1024
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
941
1025
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
942
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.194-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1026
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.615-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
943
1027
  [🔐security]: SECURITY.md
944
1028
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
945
1029
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -959,8 +1043,3 @@ Thanks for RTFM. ☺️
959
1043
  [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
960
1044
  [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
961
1045
  [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
962
-
963
- The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree\_haver](https://github.com/kettle-rb/tree_haver), which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
964
-
965
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
966
- [dotenv]: https://github.com/bkeepers/dotenv
@@ -30,6 +30,7 @@ module Json
30
30
  match_refiner: match_refiner,
31
31
  **options
32
32
  )
33
+ @emitter = Emitter.new
33
34
  end
34
35
 
35
36
  protected
@@ -42,15 +43,25 @@ module Json
42
43
  template_statements = @template_analysis.statements
43
44
  dest_statements = @dest_analysis.statements
44
45
 
45
- # Merge root-level statements (typically just the root object)
46
- merge_node_lists(
46
+ # Clear emitter for fresh merge
47
+ @emitter.clear
48
+
49
+ # Merge root-level statements via emitter
50
+ merge_node_lists_to_emitter(
47
51
  template_statements,
48
52
  dest_statements,
49
53
  @template_analysis,
50
54
  @dest_analysis,
51
- result,
52
55
  )
53
56
 
57
+ # Transfer emitter output to result
58
+ emitted_content = @emitter.to_s
59
+ unless emitted_content.empty?
60
+ emitted_content.lines.each do |line|
61
+ result.add_line(line.chomp, decision: MergeResult::DECISION_MERGED, source: :merged)
62
+ end
63
+ end
64
+
54
65
  DebugLogger.debug("Conflict resolution complete", {
55
66
  template_statements: template_statements.size,
56
67
  dest_statements: dest_statements.size,
@@ -61,13 +72,12 @@ module Json
61
72
 
62
73
  private
63
74
 
64
- # Recursively merge two lists of nodes (tree-based merge)
75
+ # Recursively merge two lists of nodes, emitting to emitter
65
76
  # @param template_nodes [Array<NodeWrapper>] Template nodes
66
77
  # @param dest_nodes [Array<NodeWrapper>] Destination nodes
67
78
  # @param template_analysis [FileAnalysis] Template analysis for line access
68
79
  # @param dest_analysis [FileAnalysis] Destination analysis for line access
69
- # @param result [MergeResult] Result to populate
70
- def merge_node_lists(template_nodes, dest_nodes, template_analysis, dest_analysis, result)
80
+ def merge_node_lists_to_emitter(template_nodes, dest_nodes, template_analysis, dest_analysis)
71
81
  # Build signature maps for matching
72
82
  template_by_sig = build_signature_map(template_nodes, template_analysis)
73
83
  dest_by_sig = build_signature_map(dest_nodes, dest_analysis)
@@ -84,20 +94,13 @@ module Json
84
94
  dest_nodes.each do |dest_node|
85
95
  dest_sig = dest_analysis.generate_signature(dest_node)
86
96
 
87
- # Freeze blocks from destination are always preserved
88
- if freeze_node?(dest_node)
89
- add_node_to_result(dest_node, result, :destination, MergeResult::DECISION_FREEZE_BLOCK, dest_analysis)
90
- processed_dest_sigs << dest_sig if dest_sig
91
- next
92
- end
93
-
94
97
  # Check for signature match
95
98
  if dest_sig && template_by_sig[dest_sig]
96
99
  template_info = template_by_sig[dest_sig].first
97
100
  template_node = template_info[:node]
98
101
 
99
102
  # Both have this node - merge them (recursively if containers)
100
- merge_matched_nodes(template_node, dest_node, template_analysis, dest_analysis, result)
103
+ merge_matched_nodes_to_emitter(template_node, dest_node, template_analysis, dest_analysis)
101
104
 
102
105
  processed_dest_sigs << dest_sig
103
106
  processed_template_sigs << dest_sig
@@ -107,13 +110,13 @@ module Json
107
110
  template_sig = template_analysis.generate_signature(template_node)
108
111
 
109
112
  # Merge matched nodes
110
- merge_matched_nodes(template_node, dest_node, template_analysis, dest_analysis, result)
113
+ merge_matched_nodes_to_emitter(template_node, dest_node, template_analysis, dest_analysis)
111
114
 
112
115
  processed_dest_sigs << dest_sig if dest_sig
113
116
  processed_template_sigs << template_sig if template_sig
114
117
  else
115
118
  # Destination-only node - always keep
116
- add_node_to_result(dest_node, result, :destination, MergeResult::DECISION_KEPT_DEST, dest_analysis)
119
+ emit_node(dest_node, dest_analysis)
117
120
  processed_dest_sigs << dest_sig if dest_sig
118
121
  end
119
122
  end
@@ -127,35 +130,146 @@ module Json
127
130
  # Skip if already processed
128
131
  next if template_sig && processed_template_sigs.include?(template_sig)
129
132
 
130
- # Skip freeze blocks from template
131
- next if freeze_node?(template_node)
132
-
133
133
  # Add template-only node
134
- add_node_to_result(template_node, result, :template, MergeResult::DECISION_ADDED, template_analysis)
134
+ emit_node(template_node, template_analysis)
135
135
  processed_template_sigs << template_sig if template_sig
136
136
  end
137
137
  end
138
138
 
139
139
  # Merge two matched nodes - for containers, recursively merge children
140
+ # Emits to emitter instead of result
140
141
  # @param template_node [NodeWrapper] Template node
141
142
  # @param dest_node [NodeWrapper] Destination node
142
143
  # @param template_analysis [FileAnalysis] Template analysis
143
144
  # @param dest_analysis [FileAnalysis] Destination analysis
144
- # @param result [MergeResult] Result to populate
145
- def merge_matched_nodes(template_node, dest_node, template_analysis, dest_analysis, result)
145
+ def merge_matched_nodes_to_emitter(template_node, dest_node, template_analysis, dest_analysis)
146
146
  if dest_node.container? && template_node.container?
147
147
  # Both are containers - recursively merge their children
148
- merge_container_nodes(template_node, dest_node, template_analysis, dest_analysis, result)
148
+ merge_container_to_emitter(template_node, dest_node, template_analysis, dest_analysis)
149
+ elsif dest_node.pair? && template_node.pair?
150
+ # Both are pairs - check if their values are OBJECTS (not arrays) that need recursive merge
151
+ template_value = template_node.value_node
152
+ dest_value = dest_node.value_node
153
+
154
+ # Only recursively merge if BOTH values are objects (not arrays)
155
+ # Arrays are replaced atomically based on preference
156
+ if template_value&.type == :object && dest_value&.type == :object &&
157
+ template_value.container? && dest_value.container?
158
+ # Both values are objects - recursively merge
159
+ @emitter.emit_nested_object_start(dest_node.key_name)
160
+
161
+ # Recursively merge the value objects
162
+ merge_node_lists_to_emitter(
163
+ template_value.mergeable_children,
164
+ dest_value.mergeable_children,
165
+ template_analysis,
166
+ dest_analysis,
167
+ )
168
+
169
+ # Emit closing brace
170
+ @emitter.emit_nested_object_end
171
+ elsif @preference == :destination
172
+ # Values are not both objects, or one/both are arrays - use preference and emit
173
+ # Arrays are always replaced, not merged
174
+ emit_node(dest_node, dest_analysis)
175
+ else
176
+ emit_node(template_node, template_analysis)
177
+ end
149
178
  elsif @preference == :destination
150
179
  # Leaf nodes or mismatched types - use preference
151
- add_node_to_result(dest_node, result, :destination, MergeResult::DECISION_KEPT_DEST, dest_analysis)
180
+ emit_node(dest_node, dest_analysis)
152
181
  else
153
- add_node_to_result(template_node, result, :template, MergeResult::DECISION_KEPT_TEMPLATE, template_analysis)
182
+ emit_node(template_node, template_analysis)
183
+ end
184
+ end
185
+
186
+ # Merge container nodes by emitting via emitter
187
+ # @param template_node [NodeWrapper] Template container node
188
+ # @param dest_node [NodeWrapper] Destination container node
189
+ # @param template_analysis [FileAnalysis] Template analysis
190
+ # @param dest_analysis [FileAnalysis] Destination analysis
191
+ def merge_container_to_emitter(template_node, dest_node, template_analysis, dest_analysis)
192
+ # Emit opening bracket
193
+ if dest_node.object?
194
+ @emitter.emit_object_start
195
+ elsif dest_node.array?
196
+ @emitter.emit_array_start
197
+ end
198
+
199
+ # Recursively merge the children
200
+ template_children = template_node.mergeable_children
201
+ dest_children = dest_node.mergeable_children
202
+
203
+ merge_node_lists_to_emitter(
204
+ template_children,
205
+ dest_children,
206
+ template_analysis,
207
+ dest_analysis,
208
+ )
209
+
210
+ # Emit closing bracket
211
+ if dest_node.object?
212
+ @emitter.emit_object_end
213
+ elsif dest_node.array?
214
+ @emitter.emit_array_end
215
+ end
216
+ end
217
+
218
+ # Emit a single node to the emitter
219
+ # @param node [NodeWrapper] Node to emit
220
+ # @param analysis [FileAnalysis] Analysis for accessing source
221
+ def emit_node(node, analysis)
222
+ # Emit the node content
223
+ if node.pair?
224
+ # Emit as pair
225
+ key = node.key_name
226
+ value_node = node.value_node
227
+
228
+ if value_node
229
+ # Check if value is an object (not array) and needs recursive emission
230
+ if value_node.type == :object && value_node.container?
231
+ # Object value - emit structure recursively
232
+ @emitter.emit_nested_object_start(key)
233
+ # Recursively emit object children
234
+ value_node.mergeable_children.each do |child|
235
+ emit_node(child, analysis)
236
+ end
237
+ @emitter.emit_nested_object_end
238
+ else
239
+ # Leaf value or array - get its text and emit as simple pair
240
+ # Arrays are emitted as raw text (not recursively)
241
+ value_text = if value_node.start_line == value_node.end_line
242
+ value_node.text
243
+ else
244
+ # Multi-line value - get all lines
245
+ lines = []
246
+ (value_node.start_line..value_node.end_line).each do |ln|
247
+ lines << analysis.line_at(ln)
248
+ end
249
+ lines.join("\n")
250
+ end
251
+
252
+ @emitter.emit_pair(key, value_text) if key && value_text
253
+ end
254
+ end
255
+ elsif node.start_line && node.end_line
256
+ # Emit raw content for non-pair nodes
257
+ if node.start_line == node.end_line
258
+ # Single line - add directly
259
+ @emitter.lines << node.text
260
+ else
261
+ # Multi-line - collect and emit
262
+ lines = []
263
+ (node.start_line..node.end_line).each do |ln|
264
+ line = analysis.line_at(ln)
265
+ lines << line if line
266
+ end
267
+ @emitter.emit_raw_lines(lines)
268
+ end
154
269
  end
155
270
  end
156
271
 
157
- # Build a map of refined matches from template node to destination node.
158
- # Uses the match_refiner to find additional pairings for nodes that didn't match by signature.
272
+ # Build a map of refined matches from template node to destination node
159
273
  # @param template_nodes [Array<NodeWrapper>] Template nodes
160
274
  # @param dest_nodes [Array<NodeWrapper>] Destination nodes
161
275
  # @param template_by_sig [Hash] Template signature map
@@ -188,62 +302,6 @@ module Json
188
302
  h[match.template_node] = match.dest_node
189
303
  end
190
304
  end
191
-
192
- # Merge two container nodes by emitting opening, recursively merging children, then closing
193
- # @param template_node [NodeWrapper] Template container node
194
- # @param dest_node [NodeWrapper] Destination container node
195
- # @param template_analysis [FileAnalysis] Template analysis
196
- # @param dest_analysis [FileAnalysis] Destination analysis
197
- # @param result [MergeResult] Result to populate
198
- def merge_container_nodes(template_node, dest_node, template_analysis, dest_analysis, result)
199
- # Use destination's opening line (or template if dest doesn't have one)
200
- opening = dest_node.opening_line || template_node.opening_line
201
- result.add_line(opening, decision: MergeResult::DECISION_MERGED, source: :merged) if opening
202
-
203
- # Recursively merge the children
204
- template_children = template_node.mergeable_children
205
- dest_children = dest_node.mergeable_children
206
-
207
- merge_node_lists(
208
- template_children,
209
- dest_children,
210
- template_analysis,
211
- dest_analysis,
212
- result,
213
- )
214
-
215
- # Use destination's closing line (or template if dest doesn't have one)
216
- closing = dest_node.closing_line || template_node.closing_line
217
- result.add_line(closing, decision: MergeResult::DECISION_MERGED, source: :merged) if closing
218
- end
219
-
220
- # Add a node to the result (non-container or leaf node)
221
- # @param node [NodeWrapper] Node to add
222
- # @param result [MergeResult] Result to populate
223
- # @param source [Symbol] :template or :destination
224
- # @param decision [String] Decision constant
225
- # @param analysis [FileAnalysis] Analysis for line access
226
- def add_node_to_result(node, result, source, decision, analysis)
227
- if freeze_node?(node)
228
- result.add_freeze_block(node)
229
- elsif node.is_a?(NodeWrapper)
230
- add_wrapper_to_result(node, result, source, decision, analysis)
231
- else
232
- DebugLogger.debug("Unknown node type", {node_type: node.class.name})
233
- end
234
- end
235
-
236
- def add_wrapper_to_result(wrapper, result, source, decision, analysis)
237
- return unless wrapper.start_line && wrapper.end_line
238
-
239
- # Add the node content line by line
240
- (wrapper.start_line..wrapper.end_line).each do |line_num|
241
- line = analysis.line_at(line_num)
242
- next unless line
243
-
244
- result.add_line(line.chomp, decision: decision, source: source, original_line: line_num)
245
- end
246
- end
247
305
  end
248
306
  end
249
307
  end
@@ -6,31 +6,38 @@ module Json
6
6
  # This class provides utilities for emitting JSON while maintaining
7
7
  # the original structure, comments, and style choices.
8
8
  #
9
+ # Inherits common emitter functionality from Ast::Merge::EmitterBase.
10
+ #
9
11
  # @example Basic usage
10
12
  # emitter = Emitter.new
11
13
  # emitter.emit_object_start
12
14
  # emitter.emit_pair("key", '"value"')
13
15
  # emitter.emit_object_end
14
- class Emitter
15
- # @return [Array<String>] Output lines
16
- attr_reader :lines
17
-
18
- # @return [Integer] Current indentation level
19
- attr_reader :indent_level
16
+ class Emitter < Ast::Merge::EmitterBase
17
+ # @return [Boolean] Whether next item needs a comma
18
+ attr_reader :needs_comma
20
19
 
21
- # @return [Integer] Spaces per indent level
22
- attr_reader :indent_size
20
+ # Initialize subclass-specific state (comma tracking for JSON)
21
+ def initialize_subclass_state(**options)
22
+ @needs_comma = false
23
+ end
23
24
 
24
- # Initialize a new emitter
25
- #
26
- # @param indent_size [Integer] Number of spaces per indent level
27
- def initialize(indent_size: 2)
28
- @lines = []
29
- @indent_level = 0
30
- @indent_size = indent_size
25
+ # Clear subclass-specific state
26
+ def clear_subclass_state
31
27
  @needs_comma = false
32
28
  end
33
29
 
30
+ # Emit a tracked comment from CommentTracker
31
+ # @param comment [Hash] Comment with :text, :indent, :block
32
+ def emit_tracked_comment(comment)
33
+ indent = " " * (comment[:indent] || 0)
34
+ @lines << if comment[:block]
35
+ "#{indent}/* #{comment[:text]} */"
36
+ else
37
+ "#{indent}// #{comment[:text]}"
38
+ end
39
+ end
40
+
34
41
  # Emit a single-line comment
35
42
  #
36
43
  # @param text [String] Comment text (without //)
@@ -53,36 +60,17 @@ module Json
53
60
  @lines << "#{current_indent}/* #{text} */"
54
61
  end
55
62
 
56
- # Emit leading comments
57
- #
58
- # @param comments [Array<Hash>] Comment hashes from CommentTracker
59
- def emit_leading_comments(comments)
60
- comments.each do |comment|
61
- indent = " " * (comment[:indent] || 0)
62
- @lines << if comment[:block]
63
- "#{indent}/* #{comment[:text]} */"
64
- else
65
- "#{indent}// #{comment[:text]}"
66
- end
67
- end
68
- end
69
-
70
- # Emit a blank line
71
- def emit_blank_line
72
- @lines << ""
73
- end
74
-
75
63
  # Emit object start
76
64
  def emit_object_start
77
65
  add_comma_if_needed
78
66
  @lines << "#{current_indent}{"
79
- @indent_level += 1
67
+ indent
80
68
  @needs_comma = false
81
69
  end
82
70
 
83
71
  # Emit object end
84
72
  def emit_object_end
85
- @indent_level -= 1 if @indent_level > 0
73
+ dedent
86
74
  @lines << "#{current_indent}}"
87
75
  @needs_comma = true
88
76
  end
@@ -97,13 +85,13 @@ module Json
97
85
  else
98
86
  "#{current_indent}["
99
87
  end
100
- @indent_level += 1
88
+ indent
101
89
  @needs_comma = false
102
90
  end
103
91
 
104
92
  # Emit array end
105
93
  def emit_array_end
106
- @indent_level -= 1 if @indent_level > 0
94
+ dedent
107
95
  @lines << "#{current_indent}]"
108
96
  @needs_comma = true
109
97
  end
@@ -133,39 +121,31 @@ module Json
133
121
  @needs_comma = true
134
122
  end
135
123
 
136
- # Emit raw lines (for preserving existing content)
137
- #
138
- # @param raw_lines [Array<String>] Lines to emit as-is
139
- def emit_raw_lines(raw_lines)
140
- raw_lines.each { |line| @lines << line.chomp }
124
+ # Emit a key with opening brace for nested object
125
+ # @param key [String] Key name
126
+ def emit_nested_object_start(key)
127
+ add_comma_if_needed
128
+ @lines << "#{current_indent}\"#{key}\": {"
129
+ indent
130
+ @needs_comma = false
141
131
  end
142
132
 
143
- # Get the output as a single string
144
- #
145
- # @return [String]
146
- def to_json
147
- content = @lines.join("\n")
148
- content += "\n" unless content.empty? || content.end_with?("\n")
149
- content
133
+ # Emit closing brace for nested object
134
+ def emit_nested_object_end
135
+ dedent
136
+ @lines << "#{current_indent}}"
137
+ @needs_comma = true
150
138
  end
151
139
 
152
- # Alias for consistency
140
+ # Get the output as a JSON string
141
+ #
153
142
  # @return [String]
154
- alias_method :to_s, :to_json
155
-
156
- # Clear the output
157
- def clear
158
- @lines = []
159
- @indent_level = 0
160
- @needs_comma = false
143
+ def to_json
144
+ to_s
161
145
  end
162
146
 
163
147
  private
164
148
 
165
- def current_indent
166
- " " * (@indent_level * @indent_size)
167
- end
168
-
169
149
  def add_comma_if_needed
170
150
  return unless @needs_comma && @lines.any?
171
151
 
@@ -74,6 +74,7 @@ module Json
74
74
 
75
75
  # In JSON tree-sitter, pair has key and value children
76
76
  key_node = find_child_by_field("key")
77
+
77
78
  return unless key_node
78
79
 
79
80
  # Key is typically a string, extract its content without quotes using byte positions
@@ -88,6 +89,7 @@ module Json
88
89
  return unless pair?
89
90
 
90
91
  value = find_child_by_field("value")
92
+
91
93
  return unless value
92
94
 
93
95
  NodeWrapper.new(value, lines: @lines, source: @source)
@@ -148,6 +150,19 @@ module Json
148
150
  object? || array?
149
151
  end
150
152
 
153
+ # Check if this is a root-level container (direct child of document)
154
+ # Root-level containers get a generic signature so they always match.
155
+ # @return [Boolean]
156
+ def root_level_container?
157
+ return false unless container?
158
+
159
+ # Check if parent is a document node
160
+ parent_node = @node.parent if @node.respond_to?(:parent)
161
+ return false unless parent_node
162
+
163
+ parent_node.type.to_s == "document"
164
+ end
165
+
151
166
  # Get the opening line for a container node (the line with { or [)
152
167
  # Returns the full line content including any leading whitespace
153
168
  # @return [String, nil]
@@ -226,14 +241,26 @@ module Json
226
241
  child_type = child&.type&.to_s
227
242
  [:document, child_type]
228
243
  when "object"
229
- # Objects identified by their keys
230
- keys = extract_object_keys(node)
231
- [:object, keys.sort]
244
+ # For root-level objects (direct child of document), use a generic signature
245
+ # that always matches so merging happens at the pair level.
246
+ # This is critical for JSON merging - there's typically only one root object/array.
247
+ if root_level_container?
248
+ [:root_object]
249
+ else
250
+ # Nested objects identified by their keys
251
+ keys = extract_object_keys(node)
252
+ [:object, keys.sort]
253
+ end
232
254
  when "array"
233
- # Arrays identified by their length and first few elements
234
- elements_count = 0
235
- node.each { |c| elements_count += 1 unless %w[comment , \[ \]].include?(c.type.to_s) }
236
- [:array, elements_count]
255
+ # For root-level arrays (direct child of document), use a generic signature
256
+ if root_level_container?
257
+ [:root_array]
258
+ else
259
+ # Nested arrays identified by their length and first few elements
260
+ elements_count = 0
261
+ node.each { |c| elements_count += 1 unless %w[comment , \[ \]].include?(c.type.to_s) }
262
+ [:array, elements_count]
263
+ end
237
264
  when "pair"
238
265
  # Pairs identified by their key name
239
266
  key = key_name
@@ -267,6 +294,7 @@ module Json
267
294
  next unless child.type.to_s == "pair"
268
295
 
269
296
  key_node = child.respond_to?(:child_by_field_name) ? child.child_by_field_name("key") : nil
297
+
270
298
  next unless key_node
271
299
 
272
300
  key_text = node_text(key_node)&.gsub(/\A"|"\z/, "")
@@ -5,7 +5,7 @@ module Json
5
5
  # Version information for Json::Merge
6
6
  module Version
7
7
  # Current version of the json-merge gem
8
- VERSION = "1.0.0"
8
+ VERSION = "1.1.0"
9
9
  end
10
10
  VERSION = Version::VERSION # traditional location
11
11
  end
data/lib/json/merge.rb CHANGED
@@ -108,6 +108,18 @@ module Json
108
108
  end
109
109
  end
110
110
 
111
+ # Register with ast-merge's MergeGemRegistry for RSpec dependency tags
112
+ # Only register if MergeGemRegistry is loaded (i.e., in test environment)
113
+ if defined?(Ast::Merge::RSpec::MergeGemRegistry)
114
+ Ast::Merge::RSpec::MergeGemRegistry.register(
115
+ :json_merge,
116
+ require_path: "json/merge",
117
+ merger_class: "Json::Merge::SmartMerger",
118
+ test_source: '{"key": "value"}',
119
+ category: :data,
120
+ )
121
+ end
122
+
111
123
  Json::Merge::Version.class_eval do
112
124
  extend VersionGem::Basic
113
125
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-merge
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -43,40 +43,40 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '3.2'
46
+ version: '5.0'
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 3.2.6
49
+ version: 5.0.1
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - "~>"
55
55
  - !ruby/object:Gem::Version
56
- version: '3.2'
56
+ version: '5.0'
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: 3.2.6
59
+ version: 5.0.1
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: ast-merge
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: '3.0'
66
+ version: '4.0'
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 3.0.0
69
+ version: 4.0.2
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: '3.0'
76
+ version: '4.0'
77
77
  - - ">="
78
78
  - !ruby/object:Gem::Version
79
- version: 3.0.0
79
+ version: 4.0.2
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: version_gem
82
82
  requirement: !ruby/object:Gem::Requirement
@@ -294,10 +294,10 @@ licenses:
294
294
  - MIT
295
295
  metadata:
296
296
  homepage_uri: https://json-merge.galtzo.com/
297
- source_code_uri: https://github.com/kettle-rb/json-merge/tree/v1.0.0
298
- changelog_uri: https://github.com/kettle-rb/json-merge/blob/v1.0.0/CHANGELOG.md
297
+ source_code_uri: https://github.com/kettle-rb/json-merge/tree/v1.1.0
298
+ changelog_uri: https://github.com/kettle-rb/json-merge/blob/v1.1.0/CHANGELOG.md
299
299
  bug_tracker_uri: https://github.com/kettle-rb/json-merge/issues
300
- documentation_uri: https://www.rubydoc.info/gems/json-merge/1.0.0
300
+ documentation_uri: https://www.rubydoc.info/gems/json-merge/1.1.0
301
301
  funding_uri: https://github.com/sponsors/pboling
302
302
  wiki_uri: https://github.com/kettle-rb/json-merge/wiki
303
303
  news_uri: https://www.railsbling.com/tags/json-merge
metadata.gz.sig CHANGED
Binary file