typescript-merge 7.0.0 → 7.1.3

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: 2b6fe33eb2fd3b77c7055bc48c46ae35dc55128a1ac641f7561f226eafbfdd42
4
- data.tar.gz: 8f17de0ab38fbd970f75149281a58f237c0cff91a8fd8da21f981ac5871e885d
3
+ metadata.gz: 92374fc2c22a8142c444657938d8f099d4b8f6fd141ee84bb9a59817d5ff573c
4
+ data.tar.gz: 9caa1d69534ee9c62820e3c6c93244e5068667403bddfbead6cfefe23915f7ed
5
5
  SHA512:
6
- metadata.gz: 2554eaa84e861bc8d20a7ce6da6fcea064e73b88ebaec6685c3ed6534f3395b69322a76fb85987aba3f572346369fab18d672312ab298cbd221dccfb8f40a17d
7
- data.tar.gz: 68f5d3f18052f9cf1c3f38f13f40c1ab754bb6d003ad82d81415f59fbb4130c6a92c98913bfd0156ceb817c6a740bb8ba6d679d66f3874121511b3f64058f0fc
6
+ metadata.gz: cf3322b809657af80cbe142168bf5e12dcbd253d7e98c671adf20a5eba537c172b68e1d43b16bfafa576ce268211279fb3507989cdd927a32406a9bc705c0841
7
+ data.tar.gz: 7ca821538482d3163f7aa3bbb6874116df64fbdb47d9b36584f62b479831b8ec5e3857a8d8c7bbecc0dc26d696d146eaec58084e1ec080be0381dd091ada611f
checksums.yaml.gz.sig CHANGED
Binary file
data/LICENSE.md ADDED
@@ -0,0 +1,13 @@
1
+ # License
2
+
3
+ This project is made available under the following licenses.
4
+ Choose the option that best fits your use case:
5
+
6
+ - [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md)
7
+ - [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md)
8
+
9
+ If none of the above licenses fit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
10
+
11
+ ## Copyright Notice
12
+
13
+ - Required Notice: Copyright (c) 2026 Peter H. Boling
data/README.md ADDED
@@ -0,0 +1,286 @@
1
+ <a href="https://github.com/structuredmerge"><img alt="structuredmerge Logo by GitHub" src="https://github.com/structuredmerge.png?size=192" width="14%" align="right"/></a>
2
+
3
+ # ☯️ TypeScript::Merge
4
+
5
+ [![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only OR PolyForm-Small-Business-1.0.0][📄license-img]][📄license] [![Total downloads][👽dl-ranki]][👽dl-rank] [![CI Current][🚎11-c-wfi]][🚎11-c-wf]
6
+
7
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know on Discord][✉️discord-invite] or [RubyForum][✉️ruby-forum], as I may have missed the notification.
8
+
9
+ ---
10
+
11
+ `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
12
+
13
+ [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
14
+
15
+ <details markdown="1">
16
+ <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
17
+
18
+ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
19
+
20
+ </details>
21
+
22
+ ## 🌻 Synopsis <a href="https://discord.gg/3qme4XHNKN"><img alt="Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px.svg" width="8%" align="right"/></a> <a href="https://ruby-toolbox.com"><img alt="ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5" src="https://logos.galtzo.com/assets/images/ruby-lang/avatar-128px.svg" width="8%" align="right"/></a>
23
+
24
+ `Typescript::Merge` merges TypeScript source files through the shared tree-sitter language-pack backend. It preserves destination declarations and appends template-only declarations when structural paths do not already exist.
25
+
26
+ ### Key Features
27
+
28
+ - TypeScript dialect feature profile and backend capability reporting.
29
+ - Declaration ownership for imports, exports, functions, classes, interfaces, types, enums, and variables as exposed by the language pack.
30
+ - Destination parse diagnostics with the common result shape.
31
+ - Module-level APIs for fixture runners and Git integrations.
32
+
33
+ ## 💡 Info you can shake a stick at
34
+
35
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
36
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
37
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
38
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Get help from RubyForum][✉️ruby-forum-img]][✉️ruby-forum] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
39
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on GitHub.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
40
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
41
+ | Compliance | [![License: AGPL-3.0-only OR PolyForm-Small-Business-1.0.0][📄license-img]][📄license] [![Apache license compatibility: Category X][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
42
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
43
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
44
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
45
+
46
+ ### Compatibility
47
+
48
+ Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
49
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
50
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
51
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
52
+ not practical for the current toolchain.
53
+
54
+ <a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/kettle-dev/avatar-128px.svg" width="14%" align="right"/></a>
55
+
56
+ The _amazing_ test matrix is powered by the kettle-dev stack.
57
+
58
+ <details markdown="1">
59
+ <summary>How kettle-dev manages complexity in tests</summary>
60
+
61
+ | Gem | Source | Role | Total downloads |
62
+ |-----|--------|------|---------------------|
63
+ | [appraisal2](https://clickgems.clickhouse.com/dashboard/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [![Total downloads for appraisal2](https://img.shields.io/gem/dt/appraisal2.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/appraisal2) |
64
+ | [appraisal2-rubocop](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [![Total downloads for appraisal2-rubocop](https://img.shields.io/gem/dt/appraisal2-rubocop.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) |
65
+ | [kettle-dev](https://clickgems.clickhouse.com/dashboard/kettle-dev) | [GitHub](https://github.com/kettle-dev/kettle-dev) | development, release, and CI workflow tooling | [![Total downloads for kettle-dev](https://img.shields.io/gem/dt/kettle-dev.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-dev) |
66
+ | [kettle-jem](https://clickgems.clickhouse.com/dashboard/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [![Total downloads for kettle-jem](https://img.shields.io/gem/dt/kettle-jem.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-jem) |
67
+ | [kettle-soup-cover](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [![Total downloads for kettle-soup-cover](https://img.shields.io/gem/dt/kettle-soup-cover.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) |
68
+ | [kettle-test](https://clickgems.clickhouse.com/dashboard/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [![Total downloads for kettle-test](https://img.shields.io/gem/dt/kettle-test.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-test) |
69
+ | [rubocop-lts](https://clickgems.clickhouse.com/dashboard/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [![Total downloads for rubocop-lts](https://img.shields.io/gem/dt/rubocop-lts.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/rubocop-lts) |
70
+ | [turbo_tests2](https://clickgems.clickhouse.com/dashboard/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [![Total downloads for turbo_tests2](https://img.shields.io/gem/dt/turbo_tests2.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/turbo_tests2) |
71
+
72
+ </details>
73
+
74
+ ## ✨ Installation
75
+
76
+ Install the gem and add to the application's Gemfile by executing:
77
+
78
+ ```console
79
+ bundle add typescript-merge
80
+ ```
81
+
82
+ If bundler is not being used to manage dependencies, install the gem by executing:
83
+
84
+ ```console
85
+ gem install typescript-merge
86
+ ```
87
+
88
+ ## ⚙️ Configuration
89
+
90
+ ```ruby
91
+ profile = Typescript::Merge.type_script_backend_feature_profile
92
+ context = Typescript::Merge.type_script_plan_context
93
+ backends = Typescript::Merge.available_type_script_backends
94
+ ```
95
+
96
+ The public method names use `type_script` to avoid relying on a lower-case acronym split in generated Ruby identifiers.
97
+
98
+ ## 🔧 Basic Usage
99
+
100
+ ```ruby
101
+ require "typescript/merge"
102
+
103
+ result = Typescript::Merge.merge_type_script(
104
+ File.read("template.ts"),
105
+ File.read("index.ts"),
106
+ "typescript",
107
+ )
108
+
109
+ abort result.fetch(:diagnostics).inspect unless result.fetch(:ok)
110
+ File.write("index.ts", result.fetch(:output))
111
+ ```
112
+
113
+ Most module-level merge methods return a Hash with `:ok`, `:diagnostics`, `:output`, and `:policies`. Check `:ok` before writing the result; diagnostics are structured for CI output and fixture conformance reports.
114
+
115
+ ## 🔐 Security
116
+
117
+ See [SECURITY.md][🔐security].
118
+
119
+ ## 🤝 Contributing
120
+
121
+ If you need some ideas of where to help, you could work on adding more code coverage,
122
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
123
+
124
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
125
+
126
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
127
+
128
+ ## 📌 Versioning
129
+
130
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
131
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
132
+
133
+ For example:
134
+
135
+ ```ruby
136
+ spec.add_dependency("typescript-merge", "~> 7.0")
137
+ ```
138
+
139
+ <details markdown="1">
140
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
141
+
142
+ Dropping support for a platform can be a breaking change for affected users.
143
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
144
+
145
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
146
+ read this article from the creator of SemVer:
147
+
148
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
149
+
150
+ </details>
151
+
152
+ See [CHANGELOG.md][📌changelog] for a list of releases.
153
+
154
+ ## 📄 License
155
+
156
+ The gem is available under the following licenses: [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md), [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md).
157
+ See [LICENSE.md][📄license] for details.
158
+
159
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
160
+
161
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
162
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
163
+ [⛳liberapay]: https://liberapay.com/pboling/donate
164
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
165
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
166
+ [🖇sponsor]: https://github.com/sponsors/pboling
167
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
168
+ [🖇kofi]: https://ko-fi.com/pboling
169
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
170
+ [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
171
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
172
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
173
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
174
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
175
+ [🖇floss-funding.dev]: https://floss-funding.dev
176
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
177
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
178
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
179
+ [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
180
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
181
+ [✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=flat&logo=discourse&label=Ruby%20Users%20Forum
182
+ [✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=for-the-badge&logo=discourse&label=Ruby%20Users%20Forum
183
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
184
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
185
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/typescript-merge
186
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-TypeScript::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
187
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/typescript-merge
188
+ [⛳️name-img]: https://img.shields.io/badge/name-typescript--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
189
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
190
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
191
+ [🚂maint-blog]: http://www.railsbling.com/tags/typescript-merge
192
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
193
+ [🚂maint-contact]: http://www.railsbling.com/contact
194
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
195
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
196
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
197
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
198
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
199
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
200
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
201
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
202
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
203
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
204
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
205
+ [💖🌳linktree]: https://linktr.ee/galtzo
206
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
207
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
208
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
209
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
210
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
211
+ [💖🧊berg]: https://codeberg.org/pboling
212
+ [💖🐙hub]: https://github.org/pboling
213
+ [💖🛖hut]: https://sr.ht/~galtzo/
214
+ [💖🧪lab]: https://gitlab.com/pboling
215
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
216
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
217
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
218
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
219
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-typescript-merge?utm_source=rubygems-typescript-merge&utm_medium=referral&utm_campaign=readme
220
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
221
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
222
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
223
+ [🚂railsbling]: http://www.railsbling.com
224
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
225
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/typescript-merge
226
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
227
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/typescript-merge
228
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
229
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/typescript-merge
230
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
231
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
232
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
233
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
234
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
235
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
236
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/typescript-merge
237
+ [👽dl-ranki]: https://img.shields.io/gem/dt/typescript-merge.svg
238
+ [👽version]: https://clickgems.clickhouse.com/dashboard/typescript-merge
239
+ [👽versioni]: https://img.shields.io/gem/v/typescript-merge.svg
240
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
241
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
242
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
243
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
244
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
245
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
246
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
247
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
248
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
249
+ [🤝cb-donate]: https://donate.codeberg.org/
250
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
251
+ [🖐contrib-rocks]: https://contrib.rocks
252
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
253
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
254
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
255
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
256
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
257
+ [📌semver]: https://semver.org/spec/v2.0.0.html
258
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
259
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
260
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
261
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
262
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
263
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
264
+ [📌gitmoji]: https://gitmoji.dev
265
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
266
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
267
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.135-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
268
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
269
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
270
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
271
+ [📄license]: LICENSE.md
272
+ [📄license-ref]: LICENSE.md
273
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
274
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
275
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
276
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
277
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
278
+ [🚎yard-current]: http://rubydoc.info/gems/typescript-merge
279
+ [🚎yard-head]: https://typescript-merge.galtzo.com
280
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
281
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
282
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
283
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
284
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
285
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
286
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeScript
4
+ module Merge
5
+ # Native TreeHaver analysis of safely attributable top-level TypeScript source.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- one AST pass establishes ordered ownership and comment attachment
7
+ class FileAnalysis
8
+ attr_reader :source, :root_node, :declarations, :errors, :backend, :dialect
9
+
10
+ def initialize(source, dialect: :typescript)
11
+ @source = source
12
+ @dialect = dialect.to_sym
13
+ @backend = TREE_SITTER_BACKEND.id.to_sym
14
+ @errors = []
15
+ parse!
16
+ rescue StandardError => e
17
+ @root_node = nil
18
+ @declarations = [].freeze
19
+ @errors = [e].freeze
20
+ end
21
+
22
+ private
23
+
24
+ def parse!
25
+ language = dialect == :tsx ? :tsx : :typescript
26
+ @root_node = TreeHaver.parser_for(language, backend_type: :tree_sitter).parse(source).root_node
27
+ raise TreeHaver::NotAvailable, 'TypeScript parse returned no root node' unless root_node
28
+ raise TreeHaver::NotAvailable, 'TypeScript parse contains syntax errors' if root_node.has_error?
29
+
30
+ wrappers = []
31
+ pending_comments = []
32
+ root_node.children.each do |node|
33
+ if node.type == 'comment'
34
+ if trailing_comment?(wrappers.last, node)
35
+ wrappers.last.attach_trailing_comment!(node)
36
+ else
37
+ pending_comments << node
38
+ end
39
+ next
40
+ end
41
+ unless NodeWrapper::DECLARATION_TYPES.include?(node.type)
42
+ raise TreeHaver::NotAvailable, "Unmanaged top-level TypeScript node #{node.type.inspect}" if node.named?
43
+
44
+ pending_comments.clear
45
+ next
46
+ end
47
+
48
+ wrappers << NodeWrapper.new(node, source: source, leading_comments: attached_comments(pending_comments, node))
49
+ pending_comments.clear
50
+ end
51
+ @declarations = wrappers.freeze
52
+ end
53
+
54
+ def trailing_comment?(owner, comment)
55
+ owner && whitespace_only?(source.byteslice(owner.end_byte...comment.start_byte).to_s) &&
56
+ source.byteslice(owner.end_byte...comment.start_byte).to_s.count("\n").zero?
57
+ end
58
+
59
+ def attached_comments(comments, owner)
60
+ attached = []
61
+ cursor = owner.start_byte
62
+ comments.reverse_each do |comment|
63
+ gap = source.byteslice(comment.end_byte...cursor).to_s
64
+ break unless whitespace_only?(gap) && gap.count("\n") <= 1
65
+
66
+ attached.unshift(comment)
67
+ cursor = comment.start_byte
68
+ end
69
+ attached.freeze
70
+ end
71
+
72
+ def whitespace_only?(value)
73
+ value.each_byte.all? { |byte| [9, 10, 13, 32].include?(byte) }
74
+ end
75
+ end
76
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
77
+ end
78
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeScript
4
+ module Merge
5
+ # Structural identity and source-range adapter for one top-level TypeScript owner.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- TypeScript declaration forms share one structural identity boundary
7
+ class NodeWrapper
8
+ DECLARATION_TYPES = %w[
9
+ ambient_declaration
10
+ class_declaration
11
+ enum_declaration
12
+ expression_statement
13
+ export_statement
14
+ function_declaration
15
+ function_signature
16
+ import_statement
17
+ interface_declaration
18
+ internal_module
19
+ lexical_declaration
20
+ type_alias_declaration
21
+ variable_declaration
22
+ ].freeze
23
+ NAMED_DECLARATIONS = {
24
+ 'class_declaration' => [:class, %w[type_identifier identifier]],
25
+ 'enum_declaration' => [:enum, %w[identifier type_identifier]],
26
+ 'interface_declaration' => [:interface, %w[type_identifier identifier]],
27
+ 'type_alias_declaration' => [:type, %w[type_identifier identifier]],
28
+ 'internal_module' => [:namespace, %w[identifier string nested_identifier]],
29
+ 'function_declaration' => [:function, %w[identifier]],
30
+ 'function_signature' => [:function, %w[identifier]]
31
+ }.freeze
32
+
33
+ attr_reader :node, :source, :leading_comments, :trailing_comments
34
+
35
+ def initialize(node, source:, leading_comments: [])
36
+ @node = node
37
+ @source = source
38
+ @leading_comments = leading_comments.freeze
39
+ @trailing_comments = []
40
+ end
41
+
42
+ def signature
43
+ return expression_statement_signature if node.type == 'expression_statement'
44
+ return import_signature if node.type == 'import_statement'
45
+ return export_signature if node.type == 'export_statement'
46
+ return variable_signature(node) if variable_declaration?(node)
47
+ return ambient_signature if node.type == 'ambient_declaration'
48
+
49
+ named_signature(node)
50
+ end
51
+
52
+ def membership_signature
53
+ signature
54
+ end
55
+
56
+ def grouped? = variable_declaration?(node)
57
+
58
+ def start_byte = leading_comments.first&.start_byte || node.start_byte
59
+ def end_byte = trailing_comments.last&.end_byte || node.end_byte
60
+ def start_line = line_for_byte(start_byte)
61
+ def end_line = line_for_byte(end_byte.zero? ? 0 : end_byte - 1)
62
+ def source_text = source.byteslice(start_byte...end_byte).to_s
63
+ def attach_trailing_comment!(comment) = trailing_comments << comment
64
+ def semantic_tree = semantic_node(node)
65
+
66
+ private
67
+
68
+ def expression_statement_signature
69
+ declaration = node.children.find { |child| NAMED_DECLARATIONS.key?(child.type) }
70
+ return named_signature(declaration) if declaration
71
+
72
+ call = direct_child(node, 'call_expression')
73
+ raise ArgumentError, 'expression statement is not a supported top-level call' unless call
74
+
75
+ callee = call.children.find(&:named?)
76
+ unless callee && callee.type == 'identifier'
77
+ raise ArgumentError, 'top-level call has no safely attributable named callee'
78
+ end
79
+
80
+ arguments = direct_child(call, 'arguments')
81
+ identity = arguments&.children&.find(&:named?)
82
+ unless identity && identity.type == 'string'
83
+ raise ArgumentError, 'top-level call has no literal string identity'
84
+ end
85
+
86
+ [:call, text(callee), text(identity)]
87
+ end
88
+
89
+ def import_signature
90
+ source_node = direct_child(node, 'string') || find_descendant(node, %w[string])
91
+ raise ArgumentError, 'import_statement has no structural module source' unless source_node
92
+
93
+ return [:import, text(source_node), :import_equals] if direct_child(node, 'import_require_clause')
94
+
95
+ clause = direct_child(node, 'import_clause')
96
+ type_only = !direct_child(node, 'type').nil?
97
+ [:import, text(source_node), type_only ? :type : :value, import_form(clause)]
98
+ end
99
+
100
+ def export_signature
101
+ default = node.children.any? { |child| child.type == 'default' }
102
+ return [:default_export] if default
103
+
104
+ declaration = node.children.find { |child| DECLARATION_TYPES.include?(child.type) }
105
+ return [:export_declaration, declaration_signature(declaration)] if declaration
106
+
107
+ source_node = direct_child(node, 'string')
108
+ type_only = !direct_child(node, 'type').nil?
109
+ if source_node
110
+ return [:export_from, text(source_node), type_only ? :type : :value, export_form]
111
+ end
112
+ return [:export_local, type_only ? :type : :value, :named] if direct_child(node, 'export_clause')
113
+ return [:export_assignment] if node.children.any? { |child| child.type == '=' }
114
+ return [:export_as_namespace] if node.children.any? { |child| child.type == 'namespace' }
115
+
116
+ [:export, semantic_node(node)]
117
+ end
118
+
119
+ def import_form(clause)
120
+ return :side_effect unless clause
121
+
122
+ child_types = clause.children.select(&:named?).map(&:type)
123
+ default = child_types.include?('identifier')
124
+ named = child_types.include?('named_imports')
125
+ namespace = child_types.include?('namespace_import')
126
+ return :default_named if default && named
127
+ return :default_namespace if default && namespace
128
+ return :default if default
129
+ return :named if named
130
+ return :namespace if namespace
131
+
132
+ raise ArgumentError, "unsupported structural import clause #{child_types.inspect}"
133
+ end
134
+
135
+ def export_form
136
+ return :named if direct_child(node, 'export_clause')
137
+ return :namespace if direct_child(node, 'namespace_export')
138
+ return :all if node.children.any? { |child| child.type == '*' }
139
+
140
+ raise ArgumentError, 'unsupported structural export-from form'
141
+ end
142
+
143
+ def declaration_signature(declaration)
144
+ return variable_signature(declaration) if variable_declaration?(declaration)
145
+ return ambient_signature(declaration) if declaration.type == 'ambient_declaration'
146
+
147
+ named_signature(declaration)
148
+ end
149
+
150
+ def named_signature(value)
151
+ kind, name_types = NAMED_DECLARATIONS.fetch(value.type) do
152
+ raise ArgumentError, "Unsupported top-level TypeScript node #{value.type.inspect}"
153
+ end
154
+ name = direct_named_child_text(value, name_types)
155
+ raise ArgumentError, "#{value.type} has no structurally attributable name" unless name
156
+
157
+ [kind, name]
158
+ end
159
+
160
+ def variable_signature(value)
161
+ declarators = value.children.select { |child| child.type == 'variable_declarator' }
162
+ names = declarators.map do |declarator|
163
+ name = declarator.children.find(&:named?)
164
+ raise ArgumentError, 'variable_declarator has no binding' unless name
165
+ raise ArgumentError, "unsupported variable binding #{name.type}" unless %w[identifier].include?(name.type)
166
+
167
+ text(name)
168
+ end
169
+ raise ArgumentError, "#{value.type} has no variable declarators" if names.empty?
170
+
171
+ keyword = value.children.find { |child| %w[const let var].include?(child.type) }
172
+ [:variables, keyword&.type, names.freeze]
173
+ end
174
+
175
+ def ambient_signature(value = node)
176
+ declaration = value.children.find { |child| child.named? && child.type != 'declare' }
177
+ raise ArgumentError, 'ambient_declaration has no declaration' unless declaration
178
+
179
+ if declaration.type == 'module'
180
+ name = declaration.children.find { |child| %w[string identifier nested_identifier].include?(child.type) }
181
+ raise ArgumentError, 'module augmentation has no structural name' unless name
182
+
183
+ return [:module_augmentation, text(name)]
184
+ end
185
+
186
+ [:ambient, named_signature(declaration)]
187
+ end
188
+
189
+ def variable_declaration?(value)
190
+ %w[lexical_declaration variable_declaration].include?(value.type)
191
+ end
192
+
193
+ def direct_child(parent, type)
194
+ parent.children.find { |child| child.type == type }
195
+ end
196
+
197
+ def direct_named_child_text(parent, types)
198
+ child = parent.children.find { |candidate| types.include?(candidate.type) }
199
+ child && text(child)
200
+ end
201
+
202
+ def find_descendant(parent, types)
203
+ return parent if types.include?(parent.type)
204
+
205
+ parent.children.each do |child|
206
+ found = find_descendant(child, types)
207
+ return found if found
208
+ end
209
+ nil
210
+ end
211
+
212
+ def semantic_node(value)
213
+ children = value.children
214
+ return [value.type, text(value)] if children.empty?
215
+
216
+ [value.type, children.select(&:named?).map { |child| semantic_node(child) }]
217
+ end
218
+
219
+ def text(value) = source.byteslice(value.start_byte...value.end_byte).to_s
220
+ def line_for_byte(byte) = source.byteslice(0...byte).to_s.count("\n") + 1
221
+ end
222
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
223
+ end
224
+ end