tree_haver 3.2.6 → 4.0.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: 6e544c31dc86e71be78cfcf24e7cbf8a779ef954845b736ec37ed6b550ffe714
4
- data.tar.gz: 54f83ef36c8167f5071db278ce33f2603864d339a9c246a733957da41540eab4
3
+ metadata.gz: 6a3704393a089cd839f41b5bd1b84c159bfef219ea41ba993c73266c00183004
4
+ data.tar.gz: 149a7ff9f6812c0173b17e6a0646d802843da9bce67e6c29054b422e98bb867e
5
5
  SHA512:
6
- metadata.gz: c991da086ba12a279f461f337e7dfa9d0b98d8d6d4f4c5d3d78cc3b848a6174ed42ce71b674e8a2d4ed81c5b17289d6c3903c2ef813db86cd4557a33aa1c1599
7
- data.tar.gz: 034b9f7df24e5f757013f011b7661559fed243b3fde752d95ab2cac94a0fdfc8270d9ca002d07972c74c1d5455293ac12c7472eacdc106934051cc3231d005b9
6
+ metadata.gz: 03dc62871eac66744c11ca4adbcad33913d52bb9e1134175bdae549faf339ff6206fb4e98087441ffe74a3cba8f2f49684649f8591442e8a38ed74ce8ada50f8
7
+ data.tar.gz: ef5413e0bb4e2b147fe9e97d560a71994b4324f9e26837752783b27d59fa935da5890c9b1b1a193f2867164f349d522a42e5ec009fabff3b07786d35fdddd928
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- **# Changelog
1
+ # Changelog
2
2
 
3
3
  [![SemVer 2.0.0][📌semver-img]][📌semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog]
4
4
 
@@ -30,6 +30,42 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [4.0.0] - 2026-01-08
34
+
35
+ - TAG: [v4.0.0][4.0.0t]
36
+ - COVERAGE: 95.31% -- 2031/2131 lines in 28 files
37
+ - BRANCH COVERAGE: 84.21% -- 805/956 branches in 28 files
38
+ - 95.48% documented
39
+
40
+ ### Added
41
+
42
+ - **BackendRegistry**: New `TreeHaver::BackendRegistry` module for registering backend availability checkers
43
+ - Allows external gems (like `commonmarker-merge`, `markly-merge`, `rbs-merge`) to register their availability checkers
44
+ - `register_availability_checker(backend_name, &block)` - Register a callable that returns true if backend is available
45
+ - `available?(backend_name)` - Check if a backend is available (results are cached)
46
+ - `registered?(backend_name)` - Check if a checker is registered
47
+ - `registered_backends` - Get all registered backend names
48
+ - Used by `TreeHaver::RSpec::DependencyTags` for dynamic backend detection
49
+ - **Plugin System**: `commonmarker-merge` and `markly-merge` now provide their own backends via `TreeHaver`'s registry system, removing them from `TreeHaver` core.
50
+ - **Backend Architecture Documentation**: Added comprehensive documentation to base classes and all tree-sitter backends explaining the two backend categories:
51
+ - Tree-sitter backends (MRI, Rust, FFI, Java): Use `TreeHaver::Tree` and `TreeHaver::Node` wrappers for raw tree-sitter objects
52
+ - Pure-Ruby/Plugin backends (Citrus, Prism, Psych, Commonmarker, Markly): Define own `Backend::X::Tree` and `Backend::X::Node` classes
53
+
54
+ ### Changed
55
+
56
+ - **Base Class Inheritance**: `TreeHaver::Tree` and `TreeHaver::Node` now properly inherit from their respective `Base::` classes
57
+ - `TreeHaver::Tree < Base::Tree` - inherits `inner_tree`, `source`, `lines` attributes and default implementations
58
+ - `TreeHaver::Node < Base::Node` - inherits `inner_node`, `source`, `lines` attributes and API contract
59
+ - Base classes document the API contract; subclasses document divergence
60
+ - **Base::Node#initialize**: Now accepts keyword arguments `source:` and `lines:` instead of positional for consistency with subclasses
61
+ - **DependencyTags**: Now uses `BackendRegistry.available?(:backend_name)` instead of hardcoded `TreeHaver::Backends::*` checks
62
+ - **TreeHaver**: `commonmarker` and `markly` backends are no longer built-in. Use `commonmarker-merge` and `markly-merge` gems which register themselves.
63
+ - **All backends**: Now register their availability checkers with `BackendRegistry` when loaded (MRI, Rust, FFI, Java, Prism, Psych, Citrus)
64
+
65
+ ### Removed
66
+
67
+ - **TreeHaver**: Removed `TreeHaver::Backends::Commonmarker` and `TreeHaver::Backends::Markly` modules. These implementations have moved to their respective gems.
68
+
33
69
  ## [3.2.6] - 2026-01-06
34
70
 
35
71
  - TAG: [v3.2.6][3.2.6t]
@@ -915,7 +951,9 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
915
951
 
916
952
  - Initial release
917
953
 
918
- [Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v3.2.6...HEAD
954
+ [Unreleased]: https://github.com/kettle-rb/tree_haver/compare/v4.0.0...HEAD
955
+ [4.0.0]: https://github.com/kettle-rb/tree_haver/compare/v3.2.6...v4.0.0
956
+ [4.0.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v4.0.0
919
957
  [3.2.6]: https://github.com/kettle-rb/tree_haver/compare/v3.2.5...v3.2.6
920
958
  [3.2.6t]: https://github.com/kettle-rb/tree_haver/releases/tag/v3.2.6
921
959
  [3.2.5]: https://github.com/kettle-rb/tree_haver/compare/v3.2.4...v3.2.5
@@ -941,4 +979,4 @@ Despite the major version bump to 3.0.0 (following semver due to the breaking `L
941
979
  [2.0.0]: https://github.com/kettle-rb/tree_haver/compare/v1.0.0...v2.0.0
942
980
  [2.0.0t]: https://github.com/kettle-rb/tree_haver/releases/tag/v2.0.0
943
981
  [1.0.0]: https://github.com/kettle-rb/tree_haver/compare/a89211bff10f4440b96758a8ac9d7d539001b0c8...v1.0.0
944
- [1.0.0t]: https://github.com/kettle-rb/tree_haver/tags/v1.0.0**
982
+ [1.0.0t]: https://github.com/kettle-rb/tree_haver/tags/v1.0.0
data/README.md CHANGED
@@ -341,12 +341,36 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
341
341
  | [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 |
342
342
  | [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 |
343
343
 
344
+ #### Backend Platform Compatibility
345
+
346
+ tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
347
+
348
+ | Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
349
+ |------------------------------------------------|:---:|:-----:|:-----------:|-----------------------------------------------------|
350
+ | **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
351
+ | **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
352
+ | **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
353
+ | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
354
+ | **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
355
+ | **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
356
+ | **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
357
+ | **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
358
+ | **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
359
+
360
+ **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
361
+
362
+ **Why some backends don't work on certain platforms**:
363
+
364
+ - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
365
+ - **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`)
366
+ - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
367
+
344
368
  **Example implementations** for the gem templating use case:
345
369
 
346
- | Gem | Purpose | Description |
347
- | --- | --- | --- |
348
- | [kettle-dev](https://github.com/kettle-rb/kettle-dev) | Gem Development | Gem templating tool using `*-merge` gems |
349
- | [kettle-jem](https://github.com/kettle-rb/kettle-jem) | Gem Templating | Gem template library with smart merge support |
370
+ | Gem | Purpose | Description |
371
+ |--------------------------|-----------------|-----------------------------------------------|
372
+ | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
373
+ | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
350
374
 
351
375
  [tree_haver]: https://github.com/kettle-rb/tree_haver
352
376
  [ast-merge]: https://github.com/kettle-rb/ast-merge
@@ -366,16 +390,18 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
366
390
  [prism]: https://github.com/ruby/prism
367
391
  [psych]: https://github.com/ruby/psych
368
392
  [ts-json]: https://github.com/tree-sitter/tree-sitter-json
393
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
369
394
  [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
395
+ [ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
370
396
  [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
397
+ [dotenv]: https://github.com/bkeepers/dotenv
371
398
  [rbs]: https://github.com/ruby/rbs
372
399
  [toml-rb]: https://github.com/emancu/toml-rb
373
400
  [markly]: https://github.com/ioquatix/markly
374
401
  [commonmarker]: https://github.com/gjtorikian/commonmarker
375
-
376
-
377
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
378
- [dotenv]: https://github.com/bkeepers/dotenv
402
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
403
+ [tree_stump]: https://github.com/joker1007/tree_stump
404
+ [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
379
405
 
380
406
  ### Comparison with Other Ruby AST / Parser Bindings
381
407
 
@@ -796,6 +822,65 @@ You can also set the backend via environment variable:
796
822
  export TREE_HAVER_BACKEND=rust
797
823
  ```
798
824
 
825
+ ### Backend Registry
826
+
827
+ TreeHaver provides a `BackendRegistry` module that allows external gems to register their backend availability checkers. This enables dynamic backend detection without hardcoding dependencies.
828
+
829
+ #### Registering a Backend Availability Checker
830
+
831
+ External gems (like `commonmarker-merge`, `markly-merge`, `rbs-merge`) can register their availability checker when loaded:
832
+
833
+ ```ruby
834
+ # In your gem's backend module
835
+ TreeHaver::BackendRegistry.register_availability_checker(:my_backend) do
836
+ # Return true if backend is available
837
+ require "my_backend_gem"
838
+ true
839
+ rescue LoadError
840
+ false
841
+ end
842
+ ```
843
+
844
+ #### Checking Backend Availability
845
+
846
+ ```ruby
847
+ # Check if a backend is available
848
+ TreeHaver::BackendRegistry.available?(:commonmarker) # => true/false
849
+ TreeHaver::BackendRegistry.available?(:markly) # => true/false
850
+ TreeHaver::BackendRegistry.available?(:rbs) # => true/false
851
+
852
+ # Check if a checker is registered
853
+ TreeHaver::BackendRegistry.registered?(:my_backend) # => true/false
854
+
855
+ # Get all registered backend names
856
+ TreeHaver::BackendRegistry.registered_backends # => [:mri, :rust, :ffi, ...]
857
+ ```
858
+
859
+ #### How It Works
860
+
861
+ 1. Built-in backends (MRI, Rust, FFI, Java, Prism, Psych, Citrus) automatically register their checkers when loaded
862
+ 2. External gems register their checkers when their backend module is loaded
863
+ 3. `TreeHaver::RSpec::DependencyTags` uses the registry to dynamically detect available backends
864
+ 4. Results are cached for performance (use `clear_cache!` to reset)
865
+
866
+ #### RSpec Integration
867
+
868
+ The `BackendRegistry` is used by `TreeHaver::RSpec::DependencyTags` to configure RSpec exclusion filters:
869
+
870
+ ```ruby
871
+ # In your spec_helper.rb
872
+ require "tree_haver/rspec/dependency_tags"
873
+
874
+ # Then in specs, use tags to skip tests when backends aren't available
875
+ it "requires commonmarker", :commonmarker_backend do
876
+ # This test only runs when commonmarker is available
877
+ end
878
+
879
+ it "requires markly", :markly_backend do
880
+ # This test only runs when markly is available
881
+ end
882
+ ```
883
+
799
884
  ### Environment Variables
800
885
 
801
886
  TreeHaver recognizes several environment variables for configuration:
@@ -2097,7 +2182,7 @@ Thanks for RTFM. ☺️
2097
2182
  [📌gitmoji]: https://gitmoji.dev
2098
2183
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
2099
2184
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
2100
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.422-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
2185
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.131-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
2101
2186
  [🔐security]: SECURITY.md
2102
2187
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
2103
2188
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -2117,6 +2202,3 @@ Thanks for RTFM. ☺️
2117
2202
  [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
2118
2203
  [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
2119
2204
  [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
2120
-
2121
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
2122
- [dotenv]: https://github.com/bkeepers/dotenv
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeHaver
4
+ # Registry for backend dependency tag availability checkers
5
+ #
6
+ # This module allows external gems (like commonmarker-merge, markly-merge, rbs-merge)
7
+ # to register their availability checker for RSpec dependency tags without
8
+ # TreeHaver needing to know about them directly.
9
+ #
10
+ # == Purpose
11
+ #
12
+ # When running RSpec tests with dependency tags (e.g., `:commonmarker_backend`),
13
+ # TreeHaver needs to know if each backend is available. Rather than hardcoding
14
+ # checks like `TreeHaver::Backends::Commonmarker.available?` (which would fail
15
+ # if the backend module doesn't exist), the BackendRegistry provides a dynamic
16
+ # way for backends to register their availability checkers.
17
+ #
18
+ # == Built-in vs External Backends
19
+ #
20
+ # - **Built-in backends** (MRI, Rust, FFI, Java, Prism, Psych, Citrus) register
21
+ # their checkers automatically when loaded from `tree_haver/backends/*.rb`
22
+ # - **External backends** (commonmarker-merge, markly-merge, rbs-merge) register
23
+ # their checkers when their backend module is loaded
24
+ #
25
+ # == Thread Safety
26
+ #
27
+ # All operations are thread-safe using a Mutex for synchronization.
28
+ # Results are cached after first check for performance.
29
+ #
30
+ # @example Registering a backend availability checker (in your gem)
31
+ # # In commonmarker-merge/lib/commonmarker/merge/backend.rb
32
+ # TreeHaver::BackendRegistry.register_availability_checker(:commonmarker) do
33
+ # available?
34
+ # end
35
+ #
36
+ # @example Checking backend availability
37
+ # TreeHaver::BackendRegistry.available?(:commonmarker) # => true/false
38
+ # TreeHaver::BackendRegistry.available?(:markly) # => true/false
39
+ # TreeHaver::BackendRegistry.available?(:rbs) # => true/false
40
+ #
41
+ # @example Checking if a checker is registered
42
+ # TreeHaver::BackendRegistry.registered?(:commonmarker) # => true/false
43
+ #
44
+ # @example Getting all registered backends
45
+ # TreeHaver::BackendRegistry.registered_backends # => [:mri, :rust, :ffi, ...]
46
+ #
47
+ # @see TreeHaver::RSpec::DependencyTags Uses BackendRegistry for dynamic backend detection
48
+ # @api public
49
+ module BackendRegistry
50
+ @mutex = Mutex.new
51
+ @availability_checkers = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
52
+ @availability_cache = {} # rubocop:disable ThreadSafety/MutableClassInstanceVariable
53
+
54
+ module_function
55
+
56
+ # Register an availability checker for a backend
57
+ #
58
+ # The checker should be a callable (lambda/proc/block) that returns true if
59
+ # the backend is available and can be used. The checker is called lazily
60
+ # (only when {available?} is first called for this backend).
61
+ #
62
+ # @param backend_name [Symbol, String] the backend name (e.g., :commonmarker, :markly)
63
+ # @param checker [#call, nil] a callable that returns true if the backend is available
64
+ # @yield Block form of checker (alternative to passing a callable)
65
+ # @yieldreturn [Boolean] true if the backend is available
66
+ # @return [void]
67
+ # @raise [ArgumentError] if no checker callable or block is provided
68
+ # @raise [ArgumentError] if checker doesn't respond to #call
69
+ #
70
+ # @example Register with a block
71
+ # TreeHaver::BackendRegistry.register_availability_checker(:commonmarker) do
72
+ # require "commonmarker"
73
+ # true
74
+ # rescue LoadError
75
+ # false
76
+ # end
77
+ #
78
+ # @example Register with a lambda
79
+ # checker = -> { Commonmarker::Merge::Backend.available? }
80
+ # TreeHaver::BackendRegistry.register_availability_checker(:commonmarker, checker)
81
+ #
82
+ # @example Register referencing the module's available? method
83
+ # TreeHaver::BackendRegistry.register_availability_checker(:my_backend) do
84
+ # available? # Calls the enclosing module's available? method
85
+ # end
86
+ def register_availability_checker(backend_name, checker = nil, &block)
87
+ callable = checker || block
88
+ raise ArgumentError, "Must provide a checker callable or block" unless callable
89
+ raise ArgumentError, "Checker must respond to #call" unless callable.respond_to?(:call)
90
+
91
+ @mutex.synchronize do
92
+ @availability_checkers[backend_name.to_sym] = callable
93
+ # Clear cache for this backend when re-registering
94
+ @availability_cache.delete(backend_name.to_sym)
95
+ end
96
+ nil
97
+ end
98
+
99
+ # Check if a backend is available
100
+ #
101
+ # If a checker was registered via {register_availability_checker}, it is called
102
+ # (and the result cached). If no checker is registered, falls back to checking
103
+ # `TreeHaver::Backends::<Name>.available?` for built-in backends.
104
+ #
105
+ # Results are cached to avoid repeated expensive checks (e.g., requiring gems).
106
+ # Use {clear_cache!} to reset the cache if backend availability may have changed.
107
+ #
108
+ # @param backend_name [Symbol, String] the backend name to check
109
+ # @return [Boolean] true if the backend is available, false otherwise
110
+ #
111
+ # @example
112
+ # TreeHaver::BackendRegistry.available?(:commonmarker) # => true
113
+ # TreeHaver::BackendRegistry.available?(:nonexistent) # => false
114
+ def available?(backend_name)
115
+ key = backend_name.to_sym
116
+
117
+ # First, check cache and get checker without holding mutex for long
118
+ checker = nil
119
+ @mutex.synchronize do
120
+ # Return cached result if available
121
+ return @availability_cache[key] if @availability_cache.key?(key)
122
+
123
+ # Get registered checker (if any)
124
+ checker = @availability_checkers[key]
125
+ end
126
+
127
+ # Compute result OUTSIDE the mutex to avoid deadlock when loading backends
128
+ # (loading a backend module triggers register_availability_checker which needs the mutex)
129
+ result = if checker
130
+ # Use the registered checker
131
+ begin
132
+ checker.call
133
+ rescue StandardError
134
+ false
135
+ end
136
+ else
137
+ # Fall back to checking TreeHaver::Backends::<Name>
138
+ # This may load the backend module, which will register its checker
139
+ check_builtin_backend(key)
140
+ end
141
+
142
+ # Cache the result
143
+ @mutex.synchronize do
144
+ # Double-check cache in case another thread computed it
145
+ return @availability_cache[key] if @availability_cache.key?(key)
146
+ @availability_cache[key] = result
147
+ end
148
+
149
+ result
150
+ end
151
+
152
+ # Check if an availability checker is registered for a backend
153
+ #
154
+ # @param backend_name [Symbol, String] the backend name
155
+ # @return [Boolean] true if a checker is registered
156
+ #
157
+ # @example
158
+ # TreeHaver::BackendRegistry.registered?(:commonmarker) # => true (if loaded)
159
+ # TreeHaver::BackendRegistry.registered?(:nonexistent) # => false
160
+ def registered?(backend_name)
161
+ @mutex.synchronize do
162
+ @availability_checkers.key?(backend_name.to_sym)
163
+ end
164
+ end
165
+
166
+ # Get all registered backend names
167
+ #
168
+ # @return [Array<Symbol>] list of registered backend names
169
+ #
170
+ # @example
171
+ # TreeHaver::BackendRegistry.registered_backends
172
+ # # => [:mri, :rust, :ffi, :java, :prism, :psych, :citrus, :commonmarker, :markly]
173
+ def registered_backends
174
+ @mutex.synchronize do
175
+ @availability_checkers.keys.dup
176
+ end
177
+ end
178
+
179
+ # Clear the availability cache
180
+ #
181
+ # Useful for testing or when backend availability may have changed
182
+ # (e.g., after installing a gem mid-process).
183
+ #
184
+ # @return [void]
185
+ #
186
+ # @example
187
+ # TreeHaver::BackendRegistry.clear_cache!
188
+ # # Next call to available? will re-check
189
+ def clear_cache!
190
+ @mutex.synchronize do
191
+ @availability_cache.clear
192
+ end
193
+ nil
194
+ end
195
+
196
+ # Clear all registrations and cache
197
+ #
198
+ # Removes all registered checkers and cached results.
199
+ # Primarily useful for testing to reset state between test cases.
200
+ #
201
+ # @return [void]
202
+ #
203
+ # @example
204
+ # TreeHaver::BackendRegistry.clear!
205
+ def clear!
206
+ @mutex.synchronize do
207
+ @availability_checkers.clear
208
+ @availability_cache.clear
209
+ end
210
+ nil
211
+ end
212
+
213
+ # Check a built-in TreeHaver backend
214
+ #
215
+ # Attempts to find the backend module at `TreeHaver::Backends::<Name>` and
216
+ # call its `available?` method. This is the fallback when no explicit
217
+ # checker has been registered.
218
+ #
219
+ # @param backend_name [Symbol] the backend name (e.g., :mri, :rust, :ffi)
220
+ # @return [Boolean] true if the backend module exists and reports available
221
+ # @api private
222
+ def check_builtin_backend(backend_name)
223
+ # Convert backend_name to PascalCase constant name
224
+ # e.g., :mri -> "MRI", :ffi -> "FFI", :commonmarker -> "Commonmarker"
225
+ const_name = backend_name.to_s.split("_").map(&:capitalize).join
226
+ backend_mod = TreeHaver::Backends.const_get(const_name)
227
+ backend_mod.respond_to?(:available?) && backend_mod.available?
228
+ rescue NameError
229
+ # Backend module doesn't exist
230
+ false
231
+ end
232
+ private_class_method :check_builtin_backend
233
+ end
234
+ end
@@ -42,10 +42,11 @@ module TreeHaver
42
42
  @load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
43
43
  begin
44
44
  require "citrus"
45
-
46
45
  @loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
47
46
  rescue LoadError
48
47
  @loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
48
+ rescue StandardError
49
+ @loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
49
50
  end
50
51
  @loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
51
52
  end
@@ -492,6 +493,11 @@ module TreeHaver
492
493
  {row: lines_before, column: column}
493
494
  end
494
495
  end
496
+
497
+ # Register availability checker for RSpec dependency tags
498
+ TreeHaver::BackendRegistry.register_availability_checker(:citrus) do
499
+ available?
500
+ end
495
501
  end
496
502
  end
497
503
  end
@@ -15,6 +15,23 @@ module TreeHaver
15
15
  # Not yet supported:
16
16
  # - Query API (tree-sitter queries/patterns)
17
17
  #
18
+ # == Tree/Node Architecture
19
+ #
20
+ # This backend (like all tree-sitter backends: MRI, Rust, FFI, Java) does NOT
21
+ # define its own Tree or Node classes. Instead:
22
+ #
23
+ # - Parser#parse returns raw FFI-wrapped tree objects
24
+ # - These are wrapped by `TreeHaver::Tree` (inherits from `Base::Tree`)
25
+ # - `TreeHaver::Tree#root_node` wraps raw nodes in `TreeHaver::Node`
26
+ #
27
+ # This differs from pure-Ruby backends (Citrus, Prism, Psych) which define
28
+ # their own `Backend::X::Tree` and `Backend::X::Node` classes.
29
+ #
30
+ # @see TreeHaver::Tree The wrapper class for tree-sitter Tree objects
31
+ # @see TreeHaver::Node The wrapper class for tree-sitter Node objects
32
+ # @see TreeHaver::Base::Tree Base class documenting the Tree API contract
33
+ # @see TreeHaver::Base::Node Base class documenting the Node API contract
34
+ #
18
35
  # == Platform Compatibility
19
36
  #
20
37
  # - MRI Ruby: ✓ Full support
@@ -56,19 +73,24 @@ module TreeHaver
56
73
  # @note Returns false on TruffleRuby because TruffleRuby's FFI doesn't support
57
74
  # STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)
58
75
  def ffi_gem_available?
59
- return @loaded if @load_attempted
76
+ return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
77
+ @load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
60
78
 
61
- @load_attempted = true
62
- @loaded = begin
79
+ @loaded = begin # rubocop:disable ThreadSafety/ClassInstanceVariable
63
80
  # TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
64
81
  # which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
65
- return false if RUBY_ENGINE == "truffleruby"
66
-
67
- require "ffi"
68
- true
82
+ if RUBY_ENGINE == "truffleruby"
83
+ false
84
+ else
85
+ require "ffi"
86
+ true
87
+ end
69
88
  rescue LoadError
70
89
  false
90
+ rescue StandardError
91
+ false
71
92
  end
93
+ @loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
72
94
  end
73
95
 
74
96
  # Reset the load state (primarily for testing)
@@ -76,8 +98,8 @@ module TreeHaver
76
98
  # @return [void]
77
99
  # @api private
78
100
  def reset!
79
- @load_attempted = false
80
- @loaded = false
101
+ @load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
102
+ @loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
81
103
  end
82
104
 
83
105
  # Get capabilities supported by this backend
@@ -717,6 +739,11 @@ module TreeHaver
717
739
  type <=> other.type
718
740
  end
719
741
  end
742
+
743
+ # Register availability checker for RSpec dependency tags
744
+ TreeHaver::BackendRegistry.register_availability_checker(:ffi) do
745
+ available?
746
+ end
720
747
  end
721
748
  end
722
749
  end