tree_haver 5.0.4 → 7.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/tree_haver/backend_context.rb +28 -0
- data/lib/tree_haver/backend_registry.rb +19 -432
- data/lib/tree_haver/contracts.rb +460 -0
- data/lib/tree_haver/kaitai_backend.rb +30 -0
- data/lib/tree_haver/language_pack.rb +190 -0
- data/lib/tree_haver/peg_backends.rb +76 -0
- data/lib/tree_haver/version.rb +1 -12
- data/lib/tree_haver.rb +7 -1316
- data.tar.gz.sig +0 -0
- metadata +34 -245
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -1366
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -359
- data/FUNDING.md +0 -74
- data/LICENSE.txt +0 -21
- data/README.md +0 -2347
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/tree_haver/backend_api.rb +0 -349
- data/lib/tree_haver/backends/citrus.rb +0 -487
- data/lib/tree_haver/backends/ffi.rb +0 -1009
- data/lib/tree_haver/backends/java.rb +0 -893
- data/lib/tree_haver/backends/mri.rb +0 -362
- data/lib/tree_haver/backends/parslet.rb +0 -560
- data/lib/tree_haver/backends/prism.rb +0 -471
- data/lib/tree_haver/backends/psych.rb +0 -375
- data/lib/tree_haver/backends/rust.rb +0 -239
- data/lib/tree_haver/base/language.rb +0 -98
- data/lib/tree_haver/base/node.rb +0 -322
- data/lib/tree_haver/base/parser.rb +0 -24
- data/lib/tree_haver/base/point.rb +0 -48
- data/lib/tree_haver/base/tree.rb +0 -128
- data/lib/tree_haver/base.rb +0 -12
- data/lib/tree_haver/citrus_grammar_finder.rb +0 -218
- data/lib/tree_haver/compat.rb +0 -43
- data/lib/tree_haver/grammar_finder.rb +0 -374
- data/lib/tree_haver/language.rb +0 -295
- data/lib/tree_haver/language_registry.rb +0 -190
- data/lib/tree_haver/library_path_utils.rb +0 -80
- data/lib/tree_haver/node.rb +0 -579
- data/lib/tree_haver/parser.rb +0 -438
- data/lib/tree_haver/parslet_grammar_finder.rb +0 -224
- data/lib/tree_haver/path_validator.rb +0 -353
- data/lib/tree_haver/point.rb +0 -27
- data/lib/tree_haver/rspec/dependency_tags.rb +0 -1392
- data/lib/tree_haver/rspec/testable_node.rb +0 -217
- data/lib/tree_haver/rspec.rb +0 -33
- data/lib/tree_haver/tree.rb +0 -258
- data/sig/tree_haver/backends.rbs +0 -352
- data/sig/tree_haver/grammar_finder.rbs +0 -29
- data/sig/tree_haver/path_validator.rbs +0 -32
- data/sig/tree_haver.rbs +0 -234
|
@@ -1,1392 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
|
-
# TreeHaver RSpec Dependency Tags
|
|
6
|
-
#
|
|
7
|
-
# This module provides dependency detection helpers for conditional test execution
|
|
8
|
-
# across all gems in the TreeHaver/ast-merge family. It detects which optional
|
|
9
|
-
# dependencies are available and configures RSpec to skip tests that require
|
|
10
|
-
# unavailable dependencies.
|
|
11
|
-
#
|
|
12
|
-
# @example Loading in spec_helper.rb
|
|
13
|
-
# require "tree_haver/rspec/dependency_tags"
|
|
14
|
-
#
|
|
15
|
-
# @example Usage in specs
|
|
16
|
-
# it "requires FFI", :ffi do
|
|
17
|
-
# # This test only runs when FFI is available
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
20
|
-
# it "requires ruby_tree_sitter", :mri_backend do
|
|
21
|
-
# # This test only runs when ruby_tree_sitter gem is available
|
|
22
|
-
# end
|
|
23
|
-
#
|
|
24
|
-
# it "requires tree_stump", :rust_backend do
|
|
25
|
-
# # This test only runs when tree_stump gem is available
|
|
26
|
-
# end
|
|
27
|
-
#
|
|
28
|
-
# it "requires JRuby", :jruby do
|
|
29
|
-
# # This test only runs on JRuby
|
|
30
|
-
# end
|
|
31
|
-
#
|
|
32
|
-
# it "requires libtree-sitter", :libtree_sitter do
|
|
33
|
-
# # This test only runs when libtree-sitter.so is loadable
|
|
34
|
-
# end
|
|
35
|
-
#
|
|
36
|
-
# it "requires a TOML grammar", :toml_grammar do
|
|
37
|
-
# # This test only runs when a TOML grammar library is available
|
|
38
|
-
# end
|
|
39
|
-
#
|
|
40
|
-
# @example Negated tags (for testing behavior when dependencies are NOT available)
|
|
41
|
-
# it "only runs when ruby_tree_sitter is NOT available", :not_mri_backend do
|
|
42
|
-
# # This test only runs when ruby_tree_sitter gem is NOT available
|
|
43
|
-
# end
|
|
44
|
-
#
|
|
45
|
-
# @example Backend-specific tags
|
|
46
|
-
# it "requires Prism backend", :prism_backend do
|
|
47
|
-
# # This test only runs when Prism is available
|
|
48
|
-
# end
|
|
49
|
-
#
|
|
50
|
-
# it "requires Psych backend", :psych_backend do
|
|
51
|
-
# # This test only runs when Psych is available
|
|
52
|
-
# end
|
|
53
|
-
#
|
|
54
|
-
# it "requires Commonmarker backend", :commonmarker_backend do
|
|
55
|
-
# # This test only runs when commonmarker gem is available
|
|
56
|
-
# end
|
|
57
|
-
#
|
|
58
|
-
# it "requires Markly backend", :markly_backend do
|
|
59
|
-
# # This test only runs when markly gem is available
|
|
60
|
-
# end
|
|
61
|
-
#
|
|
62
|
-
# it "requires Citrus backend", :citrus_backend do
|
|
63
|
-
# # This test only runs when Citrus gem is available
|
|
64
|
-
# end
|
|
65
|
-
#
|
|
66
|
-
# @example Language-specific grammar tags (for *-merge gems)
|
|
67
|
-
# it "requires tree-sitter-bash", :bash_grammar do
|
|
68
|
-
# # This test only runs when bash grammar is available and parsing works
|
|
69
|
-
# end
|
|
70
|
-
#
|
|
71
|
-
# it "requires tree-sitter-json", :json_grammar do
|
|
72
|
-
# # This test only runs when json grammar is available and parsing works
|
|
73
|
-
# end
|
|
74
|
-
#
|
|
75
|
-
# == Available Tags
|
|
76
|
-
#
|
|
77
|
-
# === Naming Conventions
|
|
78
|
-
#
|
|
79
|
-
# - `*_backend` = TreeHaver backends (mri, rust, ffi, java, prism, psych, commonmarker, markly, citrus)
|
|
80
|
-
# - `*_engine` = Ruby engines (mri, jruby, truffleruby)
|
|
81
|
-
# - `*_grammar` = tree-sitter grammar files (.so)
|
|
82
|
-
# - `*_parsing` = any parsing capability for a language (combines multiple backends/grammars)
|
|
83
|
-
# - `*_merge` = ast-merge family gems (toml-merge, json-merge, etc.)
|
|
84
|
-
#
|
|
85
|
-
# === Positive Tags (run when dependency IS available)
|
|
86
|
-
#
|
|
87
|
-
# ==== TreeHaver Backend Tags (*_backend)
|
|
88
|
-
#
|
|
89
|
-
# [:ffi_backend]
|
|
90
|
-
# FFI backend is available. Checked dynamically per-test because FFI becomes
|
|
91
|
-
# unavailable after MRI backend is used (due to libtree-sitter runtime conflicts).
|
|
92
|
-
# Legacy alias: :ffi
|
|
93
|
-
#
|
|
94
|
-
# [:ffi_backend_only]
|
|
95
|
-
# ISOLATED FFI tag - use when running FFI tests in isolation (e.g., ffi_specs task).
|
|
96
|
-
# Does NOT trigger mri_backend_available? check, preventing MRI from being loaded.
|
|
97
|
-
# Use this tag for tests that must run before MRI backend is loaded.
|
|
98
|
-
#
|
|
99
|
-
# [:mri_backend]
|
|
100
|
-
# ruby_tree_sitter gem is available.
|
|
101
|
-
#
|
|
102
|
-
# [:mri_backend_only]
|
|
103
|
-
# ISOLATED MRI tag - use when running MRI tests and FFI must not be checked.
|
|
104
|
-
# Does NOT trigger ffi_available? check, preventing FFI availability detection.
|
|
105
|
-
# Use this tag for tests that should run without FFI interference.
|
|
106
|
-
#
|
|
107
|
-
# [:rust_backend]
|
|
108
|
-
# tree_stump gem is available.
|
|
109
|
-
#
|
|
110
|
-
# [:java_backend]
|
|
111
|
-
# Java backend is available (requires JRuby + java-tree-sitter/jtreesitter).
|
|
112
|
-
#
|
|
113
|
-
# [:prism_backend]
|
|
114
|
-
# Prism gem is available.
|
|
115
|
-
#
|
|
116
|
-
# [:psych_backend]
|
|
117
|
-
# Psych is available (stdlib, should always be true).
|
|
118
|
-
#
|
|
119
|
-
# [:commonmarker_backend]
|
|
120
|
-
# commonmarker gem is available.
|
|
121
|
-
#
|
|
122
|
-
# [:markly_backend]
|
|
123
|
-
# markly gem is available.
|
|
124
|
-
#
|
|
125
|
-
# [:citrus_backend]
|
|
126
|
-
# Citrus gem is available.
|
|
127
|
-
#
|
|
128
|
-
# [:rbs_backend]
|
|
129
|
-
# RBS gem is available (official RBS parser, MRI only).
|
|
130
|
-
#
|
|
131
|
-
# ==== Ruby Engine Tags (*_engine)
|
|
132
|
-
#
|
|
133
|
-
# [:mri_engine]
|
|
134
|
-
# Running on MRI (CRuby).
|
|
135
|
-
#
|
|
136
|
-
# [:jruby_engine]
|
|
137
|
-
# Running on JRuby.
|
|
138
|
-
#
|
|
139
|
-
# [:truffleruby_engine]
|
|
140
|
-
# Running on TruffleRuby.
|
|
141
|
-
#
|
|
142
|
-
# ==== Tree-Sitter Grammar Tags (*_grammar)
|
|
143
|
-
#
|
|
144
|
-
# [:libtree_sitter]
|
|
145
|
-
# libtree-sitter.so is loadable via FFI.
|
|
146
|
-
#
|
|
147
|
-
# [:bash_grammar]
|
|
148
|
-
# tree-sitter-bash grammar is available and parsing works.
|
|
149
|
-
#
|
|
150
|
-
# [:toml_grammar]
|
|
151
|
-
# tree-sitter-toml grammar is available and parsing works.
|
|
152
|
-
#
|
|
153
|
-
# [:json_grammar]
|
|
154
|
-
# tree-sitter-json grammar is available and parsing works.
|
|
155
|
-
#
|
|
156
|
-
# [:jsonc_grammar]
|
|
157
|
-
# tree-sitter-jsonc grammar is available and parsing works.
|
|
158
|
-
#
|
|
159
|
-
# [:rbs_grammar]
|
|
160
|
-
# tree-sitter-rbs grammar is available and parsing works.
|
|
161
|
-
#
|
|
162
|
-
# ==== Language Parsing Capability Tags (*_parsing)
|
|
163
|
-
#
|
|
164
|
-
# [:toml_parsing]
|
|
165
|
-
# At least one TOML parser (tree-sitter-toml OR toml-rb/Citrus OR toml/Parslet) is available.
|
|
166
|
-
#
|
|
167
|
-
# [:markdown_parsing]
|
|
168
|
-
# At least one markdown parser (commonmarker OR markly) is available.
|
|
169
|
-
#
|
|
170
|
-
# [:json_parsing]
|
|
171
|
-
# At least one JSON parser (tree-sitter-json) is available.
|
|
172
|
-
#
|
|
173
|
-
# [:jsonc_parsing]
|
|
174
|
-
# At least one JSONC (JSON with Comments) parser (tree-sitter-jsonc) is available.
|
|
175
|
-
#
|
|
176
|
-
# [:rbs_parsing]
|
|
177
|
-
# At least one RBS parser (rbs gem OR tree-sitter-rbs) is available.
|
|
178
|
-
#
|
|
179
|
-
# [:native_parsing]
|
|
180
|
-
# A native tree-sitter backend and grammar are available.
|
|
181
|
-
#
|
|
182
|
-
# ==== Specific Library Tags (*_gem)
|
|
183
|
-
#
|
|
184
|
-
# [:toml_rb_gem]
|
|
185
|
-
# toml-rb gem is available (Citrus backend for TOML).
|
|
186
|
-
#
|
|
187
|
-
# [:rbs_gem]
|
|
188
|
-
# rbs gem is available (official RBS parser, MRI only).
|
|
189
|
-
# Note: Also available as :rbs_backend for consistency with other parser backends.
|
|
190
|
-
#
|
|
191
|
-
# === Negated Tags (run when dependency is NOT available)
|
|
192
|
-
#
|
|
193
|
-
# All positive tags have negated versions prefixed with `not_`:
|
|
194
|
-
# - :not_mri_backend, :not_rust_backend, :not_java_backend, :not_rbs_backend, etc.
|
|
195
|
-
# - :not_mri_engine, :not_jruby_engine, :not_truffleruby_engine
|
|
196
|
-
# - :not_libtree_sitter, :not_bash_grammar, :not_toml_grammar, :not_rbs_grammar, etc.
|
|
197
|
-
# - :not_toml_parsing, :not_markdown_parsing, :not_rbs_parsing
|
|
198
|
-
# - :not_toml_rb_gem, :not_rbs_gem
|
|
199
|
-
#
|
|
200
|
-
# == Backend Conflict Protection
|
|
201
|
-
#
|
|
202
|
-
# The MRI backend (ruby_tree_sitter) and FFI backend cannot coexist in the same
|
|
203
|
-
# process. Once MRI loads its native extension, FFI will segfault when trying
|
|
204
|
-
# to set a language on a parser.
|
|
205
|
-
#
|
|
206
|
-
# This module records backend usage when checking availability. When
|
|
207
|
-
# `mri_backend_available?` successfully loads ruby_tree_sitter, it calls
|
|
208
|
-
# `TreeHaver.record_backend_usage(:mri)`. This allows TreeHaver's conflict
|
|
209
|
-
# detection (`TreeHaver.conflicting_backends_for`) to properly identify when
|
|
210
|
-
# FFI would conflict with already-loaded backends.
|
|
211
|
-
#
|
|
212
|
-
# @see TreeHaver.record_backend_usage
|
|
213
|
-
# @see TreeHaver.conflicting_backends_for
|
|
214
|
-
# @see TreeHaver::Backends::BLOCKED_BY
|
|
215
|
-
|
|
216
|
-
require "tree_haver"
|
|
217
|
-
|
|
218
|
-
module TreeHaver
|
|
219
|
-
module RSpec
|
|
220
|
-
# Dependency detection helpers for conditional test execution
|
|
221
|
-
module DependencyTags
|
|
222
|
-
class << self
|
|
223
|
-
# ============================================================
|
|
224
|
-
# Backend Selection via Environment Variables
|
|
225
|
-
# ============================================================
|
|
226
|
-
#
|
|
227
|
-
# Three environment variables control backend availability:
|
|
228
|
-
#
|
|
229
|
-
# TREE_HAVER_BACKEND - Single backend selection (the primary one to use)
|
|
230
|
-
# Values: auto, mri, ffi, rust, java, citrus, prism, psych, commonmarker, markly
|
|
231
|
-
# Default: auto
|
|
232
|
-
#
|
|
233
|
-
# TREE_HAVER_NATIVE_BACKEND - Allow list for native backends
|
|
234
|
-
# Values: all, none, or comma-separated list (mri, ffi, rust, java)
|
|
235
|
-
# Default: all (empty or unset)
|
|
236
|
-
# Example: TREE_HAVER_NATIVE_BACKEND=mri,ffi
|
|
237
|
-
#
|
|
238
|
-
# TREE_HAVER_RUBY_BACKEND - Allow list for pure Ruby backends
|
|
239
|
-
# Values: all, none, or comma-separated list (citrus, prism, psych, commonmarker, markly)
|
|
240
|
-
# Default: all (empty or unset)
|
|
241
|
-
# Example: TREE_HAVER_RUBY_BACKEND=citrus
|
|
242
|
-
#
|
|
243
|
-
# This ensures tests tagged with :mri_backend only run when MRI is allowed, etc.
|
|
244
|
-
|
|
245
|
-
# Get the selected backend from TREE_HAVER_BACKEND
|
|
246
|
-
#
|
|
247
|
-
# @return [Symbol] the selected backend (:auto if not set)
|
|
248
|
-
def selected_backend
|
|
249
|
-
return @selected_backend if defined?(@selected_backend)
|
|
250
|
-
@selected_backend = TreeHaver.backend
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Get allowed native backends from TREE_HAVER_NATIVE_BACKEND
|
|
254
|
-
#
|
|
255
|
-
# @return [Array<Symbol>] list of allowed native backends, or [:all] or [:none]
|
|
256
|
-
def allowed_native_backends
|
|
257
|
-
return @allowed_native_backends if defined?(@allowed_native_backends)
|
|
258
|
-
@allowed_native_backends = TreeHaver.allowed_native_backends
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Get allowed Ruby backends from TREE_HAVER_RUBY_BACKEND
|
|
262
|
-
#
|
|
263
|
-
# @return [Array<Symbol>] list of allowed Ruby backends, or [:all] or [:none]
|
|
264
|
-
def allowed_ruby_backends
|
|
265
|
-
return @allowed_ruby_backends if defined?(@allowed_ruby_backends)
|
|
266
|
-
@allowed_ruby_backends = TreeHaver.allowed_ruby_backends
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Check if a specific backend is allowed based on environment variables
|
|
270
|
-
#
|
|
271
|
-
# Delegates to TreeHaver.backend_allowed? which handles both
|
|
272
|
-
# TREE_HAVER_NATIVE_BACKEND and TREE_HAVER_RUBY_BACKEND.
|
|
273
|
-
#
|
|
274
|
-
# @param backend [Symbol] the backend to check (:mri, :ffi, :citrus, etc.)
|
|
275
|
-
# @return [Boolean] true if the backend is allowed
|
|
276
|
-
def backend_allowed?(backend)
|
|
277
|
-
TreeHaver.backend_allowed?(backend)
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# ============================================================
|
|
281
|
-
# TreeHaver Backend Availability
|
|
282
|
-
# ============================================================
|
|
283
|
-
|
|
284
|
-
# Check if FFI backend is actually usable (live check, not memoized)
|
|
285
|
-
#
|
|
286
|
-
# This method attempts to actually use the FFI backend by loading a language.
|
|
287
|
-
# This provides "live" validation of backend availability because:
|
|
288
|
-
# - If FFI gem is missing, it will fail
|
|
289
|
-
# - If MRI backend was used first, BackendConflict will be raised
|
|
290
|
-
# - If libtree-sitter is missing, it will fail
|
|
291
|
-
#
|
|
292
|
-
# NOT MEMOIZED: Each call re-checks availability. This validates that
|
|
293
|
-
# backend protection works correctly as tests run. FFI tests should run
|
|
294
|
-
# first (via `rake spec` which runs ffi_specs then remaining_specs).
|
|
295
|
-
#
|
|
296
|
-
# For isolated FFI testing, use bin/rspec-ffi
|
|
297
|
-
#
|
|
298
|
-
# @return [Boolean] true if FFI backend is usable
|
|
299
|
-
def ffi_available?
|
|
300
|
-
# If TREE_HAVER_BACKEND explicitly selects a different native backend,
|
|
301
|
-
# FFI is not available for testing
|
|
302
|
-
return false unless backend_allowed?(:ffi)
|
|
303
|
-
|
|
304
|
-
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
305
|
-
# (used by ts_tree_root_node, ts_node_child, ts_node_start_point, etc.)
|
|
306
|
-
return false if truffleruby?
|
|
307
|
-
|
|
308
|
-
# Try to actually use the FFI backend
|
|
309
|
-
path = find_toml_grammar_path
|
|
310
|
-
return false unless path && File.exist?(path)
|
|
311
|
-
|
|
312
|
-
TreeHaver.with_backend(:ffi) do
|
|
313
|
-
TreeHaver::Language.from_library(path, symbol: "tree_sitter_toml")
|
|
314
|
-
end
|
|
315
|
-
true
|
|
316
|
-
rescue TreeHaver::BackendConflict, TreeHaver::NotAvailable, LoadError
|
|
317
|
-
false
|
|
318
|
-
rescue StandardError
|
|
319
|
-
# Catch any other FFI-related errors (e.g., Polyglot::ForeignException)
|
|
320
|
-
false
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Check if ruby_tree_sitter gem is available (MRI backend)
|
|
324
|
-
#
|
|
325
|
-
# The MRI backend only works on MRI Ruby (C extension).
|
|
326
|
-
# When this returns true, it also records MRI backend usage with
|
|
327
|
-
# TreeHaver.record_backend_usage(:mri). This is critical for conflict
|
|
328
|
-
# detection - without it, FFI would not know that MRI has been loaded.
|
|
329
|
-
#
|
|
330
|
-
# @return [Boolean] true if ruby_tree_sitter gem is available
|
|
331
|
-
def mri_backend_available?
|
|
332
|
-
return @mri_backend_available if defined?(@mri_backend_available)
|
|
333
|
-
|
|
334
|
-
# If TREE_HAVER_BACKEND explicitly selects a different native backend,
|
|
335
|
-
# MRI is not available for testing
|
|
336
|
-
return @mri_backend_available = false unless backend_allowed?(:mri)
|
|
337
|
-
|
|
338
|
-
# ruby_tree_sitter is a C extension that only works on MRI
|
|
339
|
-
return @mri_backend_available = false unless mri?
|
|
340
|
-
|
|
341
|
-
@mri_backend_available = begin
|
|
342
|
-
# Note: gem is ruby_tree_sitter but requires tree_sitter
|
|
343
|
-
require "tree_sitter"
|
|
344
|
-
# Record that MRI backend is now loaded - this is critical for
|
|
345
|
-
# conflict detection with FFI backend
|
|
346
|
-
TreeHaver.record_backend_usage(:mri)
|
|
347
|
-
true
|
|
348
|
-
rescue LoadError
|
|
349
|
-
false
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Check if FFI backend is available WITHOUT loading MRI first
|
|
354
|
-
#
|
|
355
|
-
# This method is primarily for backwards compatibility with the legacy
|
|
356
|
-
# :ffi_backend_only tag. The preferred approach is to use the standard
|
|
357
|
-
# :ffi_backend tag, which now also triggers isolated_test_mode when
|
|
358
|
-
# used with --tag ffi_backend.
|
|
359
|
-
#
|
|
360
|
-
# @return [Boolean] true if FFI backend is usable in isolation
|
|
361
|
-
# @deprecated Use :ffi_backend tag instead of :ffi_backend_only
|
|
362
|
-
def ffi_backend_only_available?
|
|
363
|
-
# If TREE_HAVER_BACKEND explicitly selects a different native backend,
|
|
364
|
-
# FFI is not available for testing
|
|
365
|
-
return false unless backend_allowed?(:ffi)
|
|
366
|
-
|
|
367
|
-
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
368
|
-
return false if truffleruby?
|
|
369
|
-
|
|
370
|
-
# Check if FFI gem is available without loading tree_sitter
|
|
371
|
-
begin
|
|
372
|
-
require "ffi"
|
|
373
|
-
rescue LoadError
|
|
374
|
-
return false
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
# Try to actually use the FFI backend
|
|
378
|
-
path = find_toml_grammar_path
|
|
379
|
-
return false unless path && File.exist?(path)
|
|
380
|
-
|
|
381
|
-
TreeHaver.with_backend(:ffi) do
|
|
382
|
-
TreeHaver::Language.from_library(path, symbol: "tree_sitter_toml")
|
|
383
|
-
end
|
|
384
|
-
true
|
|
385
|
-
rescue TreeHaver::BackendConflict, TreeHaver::NotAvailable, LoadError
|
|
386
|
-
false
|
|
387
|
-
rescue StandardError
|
|
388
|
-
# Catch any other FFI-related errors
|
|
389
|
-
false
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
# Check if MRI backend is available WITHOUT checking FFI availability
|
|
393
|
-
#
|
|
394
|
-
# This is used for the :mri_backend_only tag which runs MRI tests
|
|
395
|
-
# without triggering any FFI availability checks.
|
|
396
|
-
#
|
|
397
|
-
# @return [Boolean] true if MRI backend is usable
|
|
398
|
-
def mri_backend_only_available?
|
|
399
|
-
return @mri_backend_only_available if defined?(@mri_backend_only_available)
|
|
400
|
-
|
|
401
|
-
# If TREE_HAVER_BACKEND explicitly selects a different native backend,
|
|
402
|
-
# MRI is not available for testing
|
|
403
|
-
return @mri_backend_only_available = false unless backend_allowed?(:mri)
|
|
404
|
-
|
|
405
|
-
# ruby_tree_sitter is a C extension that only works on MRI
|
|
406
|
-
return @mri_backend_only_available = false unless mri?
|
|
407
|
-
|
|
408
|
-
@mri_backend_only_available = begin
|
|
409
|
-
require "tree_sitter"
|
|
410
|
-
TreeHaver.record_backend_usage(:mri)
|
|
411
|
-
true
|
|
412
|
-
rescue LoadError
|
|
413
|
-
false
|
|
414
|
-
end
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
# Check if tree_stump gem is available (Rust backend)
|
|
418
|
-
#
|
|
419
|
-
# The Rust backend only works on MRI Ruby (magnus uses MRI's C API).
|
|
420
|
-
#
|
|
421
|
-
# @return [Boolean] true if tree_stump gem is available
|
|
422
|
-
def rust_backend_available?
|
|
423
|
-
return @rust_backend_available if defined?(@rust_backend_available)
|
|
424
|
-
|
|
425
|
-
# If TREE_HAVER_BACKEND explicitly selects a different native backend,
|
|
426
|
-
# Rust is not available for testing
|
|
427
|
-
return @rust_backend_available = false unless backend_allowed?(:rust)
|
|
428
|
-
|
|
429
|
-
# tree_stump uses magnus which requires MRI's C API
|
|
430
|
-
return @rust_backend_available = false unless mri?
|
|
431
|
-
|
|
432
|
-
@rust_backend_available = begin
|
|
433
|
-
require "tree_stump"
|
|
434
|
-
true
|
|
435
|
-
rescue LoadError
|
|
436
|
-
false
|
|
437
|
-
end
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
# Check if Java backend is available AND can actually load grammars
|
|
441
|
-
#
|
|
442
|
-
# The Java backend requires:
|
|
443
|
-
# 1. Running on JRuby
|
|
444
|
-
# 2. java-tree-sitter (jtreesitter) JAR available
|
|
445
|
-
# 3. Grammars built for java-tree-sitter's Foreign Function Memory API
|
|
446
|
-
#
|
|
447
|
-
# Note: Standard `.so` files built for MRI's tree-sitter C bindings are NOT
|
|
448
|
-
# compatible with java-tree-sitter. You need grammar JARs from Maven Central
|
|
449
|
-
# or libraries specifically built for Java FFM API.
|
|
450
|
-
#
|
|
451
|
-
# @return [Boolean] true if Java backend is available and can load grammars
|
|
452
|
-
def java_backend_available?
|
|
453
|
-
return @java_backend_available if defined?(@java_backend_available)
|
|
454
|
-
|
|
455
|
-
# If TREE_HAVER_BACKEND explicitly selects a different native backend,
|
|
456
|
-
# Java is not available for testing
|
|
457
|
-
return @java_backend_available = false unless backend_allowed?(:java)
|
|
458
|
-
|
|
459
|
-
# Must be on JRuby and have java-tree-sitter classes available
|
|
460
|
-
return @java_backend_available = false unless jruby?
|
|
461
|
-
return @java_backend_available = false unless TreeHaver::Backends::Java.available?
|
|
462
|
-
|
|
463
|
-
# Try to actually load a grammar to verify the backend works end-to-end
|
|
464
|
-
# This catches the case where Java classes load but grammars fail
|
|
465
|
-
# (e.g., when using MRI-built .so files on JRuby)
|
|
466
|
-
@java_backend_available = java_grammar_loadable?
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Check if Java backend can actually load a grammar
|
|
470
|
-
#
|
|
471
|
-
# This does a live test by trying to load a TOML grammar via the Java backend.
|
|
472
|
-
# It catches the common failure case where java-tree-sitter is available but
|
|
473
|
-
# the grammar .so files are incompatible (built for MRI, not java-tree-sitter).
|
|
474
|
-
#
|
|
475
|
-
# @return [Boolean] true if a grammar can be loaded via Java backend
|
|
476
|
-
# @api private
|
|
477
|
-
def java_grammar_loadable?
|
|
478
|
-
return false unless jruby?
|
|
479
|
-
|
|
480
|
-
path = find_toml_grammar_path
|
|
481
|
-
return false unless path && File.exist?(path)
|
|
482
|
-
|
|
483
|
-
TreeHaver.with_backend(:java) do
|
|
484
|
-
TreeHaver::Backends::Java::Language.from_library(path, symbol: "tree_sitter_toml")
|
|
485
|
-
end
|
|
486
|
-
true
|
|
487
|
-
rescue TreeHaver::NotAvailable, TreeHaver::Error, LoadError
|
|
488
|
-
false
|
|
489
|
-
rescue StandardError
|
|
490
|
-
# Catch any other Java-related errors
|
|
491
|
-
false
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# Check if libtree-sitter runtime library is loadable
|
|
495
|
-
#
|
|
496
|
-
# @return [Boolean] true if libtree-sitter.so is loadable via FFI
|
|
497
|
-
def libtree_sitter_available?
|
|
498
|
-
return @libtree_sitter_available if defined?(@libtree_sitter_available)
|
|
499
|
-
@libtree_sitter_available = begin
|
|
500
|
-
if !ffi_available?
|
|
501
|
-
false
|
|
502
|
-
else
|
|
503
|
-
TreeHaver::Backends::FFI::Native.try_load!
|
|
504
|
-
true
|
|
505
|
-
end
|
|
506
|
-
rescue TreeHaver::NotAvailable, LoadError
|
|
507
|
-
false
|
|
508
|
-
rescue StandardError
|
|
509
|
-
# TruffleRuby raises Polyglot::ForeignException when FFI
|
|
510
|
-
# encounters unsupported types like STRUCT_BY_VALUE
|
|
511
|
-
false
|
|
512
|
-
end
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
# Check if a TOML grammar library is available via environment variable
|
|
516
|
-
#
|
|
517
|
-
# @return [Boolean] true if TREE_SITTER_TOML_PATH points to an existing file
|
|
518
|
-
def toml_grammar_available?
|
|
519
|
-
return @toml_grammar_available if defined?(@toml_grammar_available)
|
|
520
|
-
path = find_toml_grammar_path
|
|
521
|
-
@toml_grammar_available = path && File.exist?(path)
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
# Find the path to a TOML grammar library from environment variable
|
|
525
|
-
#
|
|
526
|
-
# Grammar paths should be configured via TREE_SITTER_TOML_PATH environment variable.
|
|
527
|
-
# This keeps configuration explicit and avoids magic path guessing.
|
|
528
|
-
#
|
|
529
|
-
# @return [String, nil] path to TOML grammar library, or nil if not found
|
|
530
|
-
def find_toml_grammar_path
|
|
531
|
-
# First check environment variable
|
|
532
|
-
env_path = ENV["TREE_SITTER_TOML_PATH"]
|
|
533
|
-
return env_path if env_path && File.exist?(env_path)
|
|
534
|
-
|
|
535
|
-
# Use GrammarFinder to search standard paths
|
|
536
|
-
finder = TreeHaver::GrammarFinder.new(:toml, validate: false)
|
|
537
|
-
finder.find_library_path
|
|
538
|
-
rescue StandardError
|
|
539
|
-
# GrammarFinder might not be available or might fail
|
|
540
|
-
nil
|
|
541
|
-
end
|
|
542
|
-
|
|
543
|
-
# ============================================================
|
|
544
|
-
# Dynamic Backend Availability (via BackendRegistry)
|
|
545
|
-
# ============================================================
|
|
546
|
-
#
|
|
547
|
-
# External gems register tags with BackendRegistry.register_tag which
|
|
548
|
-
# dynamically defines *_available? methods on this module.
|
|
549
|
-
#
|
|
550
|
-
# @example External gem registers a tag
|
|
551
|
-
# TreeHaver::BackendRegistry.register_tag(
|
|
552
|
-
# :my_backend_backend,
|
|
553
|
-
# category: :backend,
|
|
554
|
-
# require_path: "my_backend/merge"
|
|
555
|
-
# ) { MyBackend::Merge::Backend.available? }
|
|
556
|
-
#
|
|
557
|
-
# # The registration automatically defines:
|
|
558
|
-
# TreeHaver::RSpec::DependencyTags.my_backend_available? # => true/false
|
|
559
|
-
#
|
|
560
|
-
# Built-in backends (prism, psych, citrus, parslet) have explicit methods
|
|
561
|
-
# defined below. External backends get methods defined dynamically when
|
|
562
|
-
# their gem calls register_tag.
|
|
563
|
-
|
|
564
|
-
# Check if prism gem is available
|
|
565
|
-
#
|
|
566
|
-
# @return [Boolean] true if Prism is available
|
|
567
|
-
def prism_available?
|
|
568
|
-
return @prism_available if defined?(@prism_available)
|
|
569
|
-
@prism_available = TreeHaver::BackendRegistry.available?(:prism)
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
# Check if psych is available (stdlib, should always be true)
|
|
573
|
-
#
|
|
574
|
-
# @return [Boolean] true if Psych is available
|
|
575
|
-
def psych_available?
|
|
576
|
-
return @psych_available if defined?(@psych_available)
|
|
577
|
-
@psych_available = TreeHaver::BackendRegistry.available?(:psych)
|
|
578
|
-
end
|
|
579
|
-
|
|
580
|
-
# Check if Citrus backend is available
|
|
581
|
-
#
|
|
582
|
-
# This checks if the citrus gem is installed and the backend works.
|
|
583
|
-
#
|
|
584
|
-
# @return [Boolean] true if Citrus backend is available
|
|
585
|
-
def citrus_available?
|
|
586
|
-
return @citrus_available if defined?(@citrus_available)
|
|
587
|
-
@citrus_available = TreeHaver::BackendRegistry.available?(:citrus)
|
|
588
|
-
end
|
|
589
|
-
|
|
590
|
-
# Check if Parslet backend is available
|
|
591
|
-
#
|
|
592
|
-
# This checks if the parslet gem is installed and the backend works.
|
|
593
|
-
#
|
|
594
|
-
# @return [Boolean] true if Parslet backend is available
|
|
595
|
-
def parslet_available?
|
|
596
|
-
return @parslet_available if defined?(@parslet_available)
|
|
597
|
-
@parslet_available = TreeHaver::BackendRegistry.available?(:parslet)
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
# ============================================================
|
|
601
|
-
# Ruby Engine Detection
|
|
602
|
-
# ============================================================
|
|
603
|
-
|
|
604
|
-
# Check if running on JRuby
|
|
605
|
-
#
|
|
606
|
-
# @return [Boolean] true if running on JRuby
|
|
607
|
-
def jruby?
|
|
608
|
-
defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
# Check if running on TruffleRuby
|
|
612
|
-
#
|
|
613
|
-
# @return [Boolean] true if running on TruffleRuby
|
|
614
|
-
def truffleruby?
|
|
615
|
-
defined?(RUBY_ENGINE) && RUBY_ENGINE == "truffleruby"
|
|
616
|
-
end
|
|
617
|
-
|
|
618
|
-
# Check if running on MRI (CRuby)
|
|
619
|
-
#
|
|
620
|
-
# @return [Boolean] true if running on MRI
|
|
621
|
-
def mri?
|
|
622
|
-
defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# ============================================================
|
|
626
|
-
# Language-Specific Grammar Availability
|
|
627
|
-
# These check that parsing actually works, not just that a grammar exists
|
|
628
|
-
# ============================================================
|
|
629
|
-
|
|
630
|
-
# Check if tree-sitter-bash grammar is available and working
|
|
631
|
-
#
|
|
632
|
-
# @return [Boolean] true if bash grammar works
|
|
633
|
-
def tree_sitter_bash_available?
|
|
634
|
-
return @tree_sitter_bash_available if defined?(@tree_sitter_bash_available)
|
|
635
|
-
@tree_sitter_bash_available = grammar_works?(:bash, "echo hello")
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
# Check if tree-sitter-toml grammar is available and working via TreeHaver
|
|
639
|
-
#
|
|
640
|
-
# @return [Boolean] true if toml grammar works
|
|
641
|
-
def tree_sitter_toml_available?
|
|
642
|
-
return @tree_sitter_toml_available if defined?(@tree_sitter_toml_available)
|
|
643
|
-
@tree_sitter_toml_available = grammar_works?(:toml, 'key = "value"')
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
# Check if tree-sitter-json grammar is available and working
|
|
647
|
-
#
|
|
648
|
-
# @return [Boolean] true if json grammar works
|
|
649
|
-
def tree_sitter_json_available?
|
|
650
|
-
return @tree_sitter_json_available if defined?(@tree_sitter_json_available)
|
|
651
|
-
@tree_sitter_json_available = grammar_works?(:json, '{"key": "value"}')
|
|
652
|
-
end
|
|
653
|
-
|
|
654
|
-
# Check if tree-sitter-jsonc grammar is available and working
|
|
655
|
-
#
|
|
656
|
-
# @return [Boolean] true if jsonc grammar works
|
|
657
|
-
def tree_sitter_jsonc_available?
|
|
658
|
-
return @tree_sitter_jsonc_available if defined?(@tree_sitter_jsonc_available)
|
|
659
|
-
@tree_sitter_jsonc_available = grammar_works?(:jsonc, '{"key": "value" /* comment */}')
|
|
660
|
-
end
|
|
661
|
-
|
|
662
|
-
# Check if tree-sitter-rbs grammar is available and working
|
|
663
|
-
#
|
|
664
|
-
# @return [Boolean] true if rbs grammar works
|
|
665
|
-
def tree_sitter_rbs_available?
|
|
666
|
-
return @tree_sitter_rbs_available if defined?(@tree_sitter_rbs_available)
|
|
667
|
-
@tree_sitter_rbs_available = grammar_works?(:rbs, "class Foo end")
|
|
668
|
-
end
|
|
669
|
-
|
|
670
|
-
# Check if the RBS gem is available and functional
|
|
671
|
-
#
|
|
672
|
-
# The RBS gem only works on MRI Ruby (C extension).
|
|
673
|
-
#
|
|
674
|
-
# @return [Boolean] true if rbs gem is available and can parse RBS
|
|
675
|
-
def rbs_gem_available?
|
|
676
|
-
return @rbs_gem_available if defined?(@rbs_gem_available)
|
|
677
|
-
@rbs_gem_available = begin
|
|
678
|
-
require "rbs"
|
|
679
|
-
# Verify it can actually parse - just requiring isn't enough
|
|
680
|
-
buffer = ::RBS::Buffer.new(name: "test.rbs", content: "class Foo end")
|
|
681
|
-
::RBS::Parser.parse_signature(buffer)
|
|
682
|
-
true
|
|
683
|
-
rescue LoadError
|
|
684
|
-
false
|
|
685
|
-
rescue StandardError
|
|
686
|
-
false
|
|
687
|
-
end
|
|
688
|
-
end
|
|
689
|
-
|
|
690
|
-
# Alias for rbs_gem_available? - for consistency with other backends
|
|
691
|
-
# Use :rbs_backend tag in specs for consistency with :prism_backend, :psych_backend, etc.
|
|
692
|
-
#
|
|
693
|
-
# @return [Boolean] true if rbs gem is available
|
|
694
|
-
alias_method :rbs_backend_available?, :rbs_gem_available?
|
|
695
|
-
|
|
696
|
-
# Check if at least one RBS backend is available
|
|
697
|
-
#
|
|
698
|
-
# @return [Boolean] true if any RBS backend works
|
|
699
|
-
def any_rbs_backend_available?
|
|
700
|
-
rbs_gem_available? || tree_sitter_rbs_available?
|
|
701
|
-
end
|
|
702
|
-
|
|
703
|
-
# Check if toml-rb gem is available and functional (Citrus backend for TOML)
|
|
704
|
-
#
|
|
705
|
-
# @return [Boolean] true if toml-rb gem is available and can parse TOML
|
|
706
|
-
def toml_rb_gem_available?
|
|
707
|
-
return @toml_rb_gem_available if defined?(@toml_rb_gem_available)
|
|
708
|
-
@toml_rb_gem_available = begin
|
|
709
|
-
require "toml-rb"
|
|
710
|
-
# Verify it can actually parse - just requiring isn't enough
|
|
711
|
-
TomlRB.parse('key = "value"')
|
|
712
|
-
true
|
|
713
|
-
rescue LoadError
|
|
714
|
-
false
|
|
715
|
-
rescue StandardError
|
|
716
|
-
false
|
|
717
|
-
end
|
|
718
|
-
end
|
|
719
|
-
|
|
720
|
-
# Check if toml gem is available and functional (Parslet backend for TOML)
|
|
721
|
-
#
|
|
722
|
-
# @return [Boolean] true if toml gem is available and can parse TOML
|
|
723
|
-
def toml_gem_available?
|
|
724
|
-
return @toml_gem_available if defined?(@toml_gem_available)
|
|
725
|
-
@toml_gem_available = begin
|
|
726
|
-
require "toml"
|
|
727
|
-
# Verify it can actually parse - just requiring isn't enough
|
|
728
|
-
source_toml = <<~TOML
|
|
729
|
-
# My Information
|
|
730
|
-
[machine]
|
|
731
|
-
host = "localhost"
|
|
732
|
-
TOML
|
|
733
|
-
TOML.load(source_toml)
|
|
734
|
-
true
|
|
735
|
-
rescue LoadError
|
|
736
|
-
false
|
|
737
|
-
rescue StandardError
|
|
738
|
-
false
|
|
739
|
-
end
|
|
740
|
-
end
|
|
741
|
-
|
|
742
|
-
# Check if at least one TOML backend is available
|
|
743
|
-
#
|
|
744
|
-
# @return [Boolean] true if any TOML backend works
|
|
745
|
-
def any_toml_backend_available?
|
|
746
|
-
tree_sitter_toml_available? || toml_rb_gem_available? || toml_gem_available?
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
# Check if at least one markdown backend is available
|
|
750
|
-
#
|
|
751
|
-
# Uses BackendRegistry.tag_available? to check external backends that may
|
|
752
|
-
# not have their methods defined yet (registered by external gems).
|
|
753
|
-
#
|
|
754
|
-
# @return [Boolean] true if any markdown backend works
|
|
755
|
-
def any_markdown_backend_available?
|
|
756
|
-
TreeHaver::BackendRegistry.tag_available?(:markly_backend) ||
|
|
757
|
-
TreeHaver::BackendRegistry.tag_available?(:commonmarker_backend)
|
|
758
|
-
end
|
|
759
|
-
|
|
760
|
-
# Check if at least one JSON parsing backend is available
|
|
761
|
-
#
|
|
762
|
-
# Currently only tree-sitter-json is supported for JSON parsing.
|
|
763
|
-
# Future backends (e.g., pure-Ruby JSON parsers) can be added here.
|
|
764
|
-
#
|
|
765
|
-
# @return [Boolean] true if any JSON parsing backend works
|
|
766
|
-
def any_json_backend_available?
|
|
767
|
-
tree_sitter_json_available?
|
|
768
|
-
end
|
|
769
|
-
|
|
770
|
-
# Check if at least one JSONC parsing backend is available
|
|
771
|
-
#
|
|
772
|
-
# Currently only tree-sitter-jsonc is supported for JSONC parsing.
|
|
773
|
-
# Future backends (e.g., pure-Ruby JSONC parsers) can be added here.
|
|
774
|
-
#
|
|
775
|
-
# @return [Boolean] true if any JSONC parsing backend works
|
|
776
|
-
def any_jsonc_backend_available?
|
|
777
|
-
tree_sitter_jsonc_available?
|
|
778
|
-
end
|
|
779
|
-
|
|
780
|
-
def any_native_grammar_available?
|
|
781
|
-
libtree_sitter_available? && (
|
|
782
|
-
tree_sitter_bash_available? ||
|
|
783
|
-
tree_sitter_toml_available? ||
|
|
784
|
-
tree_sitter_json_available? ||
|
|
785
|
-
tree_sitter_jsonc_available?
|
|
786
|
-
)
|
|
787
|
-
end
|
|
788
|
-
|
|
789
|
-
# ============================================================
|
|
790
|
-
# Summary and Reset
|
|
791
|
-
# ============================================================
|
|
792
|
-
|
|
793
|
-
# Determine which backends are blocked based on environment and ARGV
|
|
794
|
-
#
|
|
795
|
-
# This replicates the logic from RSpec.configure to determine blocked
|
|
796
|
-
# backends BEFORE the RSpec.configure block has run. This is necessary
|
|
797
|
-
# because summary may be called in a before(:suite) hook that runs
|
|
798
|
-
# before the blocked_backends instance variable is set.
|
|
799
|
-
#
|
|
800
|
-
# @return [Set<Symbol>] set of blocked backend symbols
|
|
801
|
-
def compute_blocked_backends
|
|
802
|
-
blocked = Set.new
|
|
803
|
-
|
|
804
|
-
# Check TREE_HAVER_BACKEND environment variable
|
|
805
|
-
env_backend = ENV["TREE_HAVER_BACKEND"]
|
|
806
|
-
if env_backend && !env_backend.empty? && env_backend != "auto"
|
|
807
|
-
backend_sym = env_backend.to_sym
|
|
808
|
-
TreeHaver::Backends::BLOCKED_BY[backend_sym]&.each { |blocker| blocked << blocker }
|
|
809
|
-
end
|
|
810
|
-
|
|
811
|
-
# Check ARGV for --tag options that indicate isolated backend testing
|
|
812
|
-
ARGV.each_with_index do |arg, i|
|
|
813
|
-
tag_value = nil
|
|
814
|
-
if arg == "--tag" && ARGV[i + 1]
|
|
815
|
-
tag_str = ARGV[i + 1]
|
|
816
|
-
next if tag_str.start_with?("~")
|
|
817
|
-
tag_value = tag_str.to_sym
|
|
818
|
-
elsif arg.start_with?("--tag=")
|
|
819
|
-
tag_str = arg.sub("--tag=", "")
|
|
820
|
-
next if tag_str.start_with?("~")
|
|
821
|
-
tag_value = tag_str.to_sym
|
|
822
|
-
end
|
|
823
|
-
|
|
824
|
-
next unless tag_value
|
|
825
|
-
|
|
826
|
-
# Check for standard backend tags (e.g., :ffi_backend)
|
|
827
|
-
TreeHaver::Backends::BLOCKED_BY.each do |backend, blockers|
|
|
828
|
-
standard_tag = :"#{backend}_backend"
|
|
829
|
-
legacy_tag = :"#{backend}_backend_only"
|
|
830
|
-
if tag_value == standard_tag || tag_value == legacy_tag
|
|
831
|
-
blockers.each { |blocker| blocked << blocker }
|
|
832
|
-
end
|
|
833
|
-
end
|
|
834
|
-
end
|
|
835
|
-
|
|
836
|
-
blocked
|
|
837
|
-
end
|
|
838
|
-
|
|
839
|
-
# Get a summary of available dependencies (for debugging)
|
|
840
|
-
#
|
|
841
|
-
# This method respects blocked_backends to avoid loading backends
|
|
842
|
-
# that would conflict with isolated test modes (e.g., FFI-only tests).
|
|
843
|
-
#
|
|
844
|
-
# @return [Hash{Symbol => Boolean}] map of dependency name to availability
|
|
845
|
-
def summary
|
|
846
|
-
# Use stored blocked_backends if available, otherwise compute dynamically
|
|
847
|
-
blocked = @blocked_backends || compute_blocked_backends
|
|
848
|
-
|
|
849
|
-
result = {
|
|
850
|
-
# Backend selection from environment variables
|
|
851
|
-
selected_backend: selected_backend,
|
|
852
|
-
allowed_native_backends: allowed_native_backends,
|
|
853
|
-
allowed_ruby_backends: allowed_ruby_backends,
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
# Built-in TreeHaver backends (*_backend) - skip blocked backends to avoid loading them
|
|
857
|
-
builtin_backends = {
|
|
858
|
-
ffi: :ffi_available?,
|
|
859
|
-
mri: :mri_backend_available?,
|
|
860
|
-
rust: :rust_backend_available?,
|
|
861
|
-
java: :java_backend_available?,
|
|
862
|
-
prism: :prism_available?,
|
|
863
|
-
psych: :psych_available?,
|
|
864
|
-
citrus: :citrus_available?,
|
|
865
|
-
parslet: :parslet_available?,
|
|
866
|
-
rbs: :rbs_backend_available?,
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
builtin_backends.each do |backend, method|
|
|
870
|
-
tag = :"#{backend}_backend"
|
|
871
|
-
result[tag] = blocked.include?(backend) ? :blocked : public_send(method)
|
|
872
|
-
end
|
|
873
|
-
|
|
874
|
-
# Dynamically registered backends from BackendRegistry
|
|
875
|
-
TreeHaver::BackendRegistry.registered_tags.each do |tag_name|
|
|
876
|
-
next if result.key?(tag_name) # Don't override built-ins
|
|
877
|
-
|
|
878
|
-
meta = TreeHaver::BackendRegistry.tag_metadata(tag_name)
|
|
879
|
-
next unless meta && meta[:category] == :backend
|
|
880
|
-
|
|
881
|
-
backend = meta[:backend_name]
|
|
882
|
-
result[tag_name] = blocked.include?(backend) ? :blocked : TreeHaver::BackendRegistry.tag_available?(tag_name)
|
|
883
|
-
end
|
|
884
|
-
|
|
885
|
-
# Ruby engines (*_engine)
|
|
886
|
-
result[:ruby_engine] = RUBY_ENGINE
|
|
887
|
-
result[:mri_engine] = mri?
|
|
888
|
-
result[:jruby_engine] = jruby?
|
|
889
|
-
result[:truffleruby_engine] = truffleruby?
|
|
890
|
-
|
|
891
|
-
# Tree-sitter grammars (*_grammar) - also respect blocked backends
|
|
892
|
-
# since grammar checks may load backends
|
|
893
|
-
result[:libtree_sitter] = libtree_sitter_available?
|
|
894
|
-
result[:bash_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_bash_available?
|
|
895
|
-
result[:toml_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_toml_available?
|
|
896
|
-
result[:json_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_json_available?
|
|
897
|
-
result[:jsonc_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_jsonc_available?
|
|
898
|
-
result[:rbs_grammar] = blocked.include?(:mri) ? :blocked : tree_sitter_rbs_available?
|
|
899
|
-
result[:any_native_grammar] = blocked.include?(:mri) ? :blocked : any_native_grammar_available?
|
|
900
|
-
|
|
901
|
-
# Language parsing capabilities (*_parsing)
|
|
902
|
-
result[:toml_parsing] = any_toml_backend_available?
|
|
903
|
-
result[:markdown_parsing] = any_markdown_backend_available?
|
|
904
|
-
result[:json_parsing] = any_json_backend_available?
|
|
905
|
-
result[:jsonc_parsing] = any_jsonc_backend_available?
|
|
906
|
-
result[:rbs_parsing] = any_rbs_backend_available?
|
|
907
|
-
|
|
908
|
-
# Specific libraries (*_gem)
|
|
909
|
-
result[:toml_rb_gem] = toml_rb_gem_available?
|
|
910
|
-
result[:toml_gem] = toml_gem_available?
|
|
911
|
-
result[:rbs_gem] = rbs_gem_available?
|
|
912
|
-
|
|
913
|
-
result
|
|
914
|
-
end
|
|
915
|
-
|
|
916
|
-
# Get environment variable summary for debugging
|
|
917
|
-
#
|
|
918
|
-
# @return [Hash{String => String}] relevant environment variables
|
|
919
|
-
def env_summary
|
|
920
|
-
{
|
|
921
|
-
"TREE_SITTER_BASH_PATH" => ENV["TREE_SITTER_BASH_PATH"],
|
|
922
|
-
"TREE_SITTER_TOML_PATH" => ENV["TREE_SITTER_TOML_PATH"],
|
|
923
|
-
"TREE_SITTER_JSON_PATH" => ENV["TREE_SITTER_JSON_PATH"],
|
|
924
|
-
"TREE_SITTER_JSONC_PATH" => ENV["TREE_SITTER_JSONC_PATH"],
|
|
925
|
-
"TREE_SITTER_RBS_PATH" => ENV["TREE_SITTER_RBS_PATH"],
|
|
926
|
-
"TREE_SITTER_RUNTIME_LIB" => ENV["TREE_SITTER_RUNTIME_LIB"],
|
|
927
|
-
"TREE_HAVER_BACKEND" => ENV["TREE_HAVER_BACKEND"],
|
|
928
|
-
"TREE_HAVER_DEBUG" => ENV["TREE_HAVER_DEBUG"],
|
|
929
|
-
# Library paths used by tree-sitter shared libraries
|
|
930
|
-
"LD_LIBRARY_PATH" => ENV["LD_LIBRARY_PATH"],
|
|
931
|
-
"DYLD_LIBRARY_PATH" => ENV["DYLD_LIBRARY_PATH"],
|
|
932
|
-
}
|
|
933
|
-
end
|
|
934
|
-
|
|
935
|
-
# Reset all memoized availability checks
|
|
936
|
-
#
|
|
937
|
-
# Useful in tests that need to re-check availability after mocking.
|
|
938
|
-
# Note: This does NOT undo backend usage recording.
|
|
939
|
-
#
|
|
940
|
-
# @return [void]
|
|
941
|
-
def reset!
|
|
942
|
-
instance_variables.each do |ivar|
|
|
943
|
-
# Don't reset ENV-based values
|
|
944
|
-
next if %i[@selected_backend @allowed_native_backends @allowed_ruby_backends].include?(ivar)
|
|
945
|
-
remove_instance_variable(ivar) if ivar.to_s.end_with?("_available")
|
|
946
|
-
end
|
|
947
|
-
end
|
|
948
|
-
|
|
949
|
-
# Reset selected backend caches (useful for testing with different ENV values)
|
|
950
|
-
#
|
|
951
|
-
# Also resets TreeHaver's backend caches.
|
|
952
|
-
#
|
|
953
|
-
# @return [void]
|
|
954
|
-
def reset_selected_backend!
|
|
955
|
-
remove_instance_variable(:@selected_backend) if defined?(@selected_backend)
|
|
956
|
-
remove_instance_variable(:@allowed_native_backends) if defined?(@allowed_native_backends)
|
|
957
|
-
remove_instance_variable(:@allowed_ruby_backends) if defined?(@allowed_ruby_backends)
|
|
958
|
-
TreeHaver.reset_backend!
|
|
959
|
-
end
|
|
960
|
-
|
|
961
|
-
private
|
|
962
|
-
|
|
963
|
-
# Generic helper to check if a grammar works by parsing test source
|
|
964
|
-
#
|
|
965
|
-
# @param language [Symbol] the language to test
|
|
966
|
-
# @param test_source [String] sample source code to parse
|
|
967
|
-
# @return [Boolean] true if parsing works without errors
|
|
968
|
-
def grammar_works?(language, test_source)
|
|
969
|
-
debug = !ENV.fetch("TREE_HAVER_DEBUG", "false").casecmp?("false")
|
|
970
|
-
env_var = "TREE_SITTER_#{language.to_s.upcase}_PATH"
|
|
971
|
-
env_value = ENV[env_var]
|
|
972
|
-
|
|
973
|
-
if debug
|
|
974
|
-
puts " [grammar_works? #{language}] ENV[#{env_var}] = #{env_value.inspect}"
|
|
975
|
-
puts " [grammar_works? #{language}] Attempting TreeHaver.parser_for(#{language.inspect})..."
|
|
976
|
-
end
|
|
977
|
-
|
|
978
|
-
parser = TreeHaver.parser_for(language)
|
|
979
|
-
if debug
|
|
980
|
-
puts " [grammar_works? #{language}] Parser created: #{parser.class}"
|
|
981
|
-
puts " [grammar_works? #{language}] Parser backend: #{parser.respond_to?(:backend) ? parser.backend : "unknown"}"
|
|
982
|
-
end
|
|
983
|
-
|
|
984
|
-
result = parser.parse(test_source)
|
|
985
|
-
success = !result.nil? && result.root_node && !result.root_node.has_error?
|
|
986
|
-
|
|
987
|
-
if debug
|
|
988
|
-
puts " [grammar_works? #{language}] Parse result nil?: #{result.nil?}"
|
|
989
|
-
puts " [grammar_works? #{language}] Root node: #{result&.root_node&.class}"
|
|
990
|
-
puts " [grammar_works? #{language}] Has error?: #{result&.root_node&.has_error?}"
|
|
991
|
-
puts " [grammar_works? #{language}] Success: #{success}"
|
|
992
|
-
end
|
|
993
|
-
|
|
994
|
-
success
|
|
995
|
-
rescue TreeHaver::NotAvailable, TreeHaver::Error, StandardError => e
|
|
996
|
-
if debug
|
|
997
|
-
puts " [grammar_works? #{language}] Exception: #{e.class}: #{e.message}"
|
|
998
|
-
puts " [grammar_works? #{language}] Returning false"
|
|
999
|
-
end
|
|
1000
|
-
false
|
|
1001
|
-
end
|
|
1002
|
-
end
|
|
1003
|
-
end
|
|
1004
|
-
end
|
|
1005
|
-
end
|
|
1006
|
-
|
|
1007
|
-
# NOTE: Availability methods for dynamically registered backends (like markly, commonmarker)
|
|
1008
|
-
# are defined by BackendRegistry.define_availability_method when the backend registers via
|
|
1009
|
-
# register_tag. This happens automatically when gems like markly-merge load, AFTER this file
|
|
1010
|
-
# has been required. The define_availability_method in BackendRegistry checks if DependencyTags
|
|
1011
|
-
# is loaded and defines the *_available? method at registration time.
|
|
1012
|
-
#
|
|
1013
|
-
# Example flow for markly-merge:
|
|
1014
|
-
# 1. spec_helper loads tree_haver/rspec (this file) - DependencyTags module now exists
|
|
1015
|
-
# 2. spec_helper loads markly/merge - calls BackendRegistry.register_tag(:markly_backend)
|
|
1016
|
-
# 3. register_tag calls define_availability_method(:markly, :markly_backend)
|
|
1017
|
-
# 4. define_availability_method defines TreeHaver::RSpec::DependencyTags.markly_available?
|
|
1018
|
-
#
|
|
1019
|
-
# This means by the time RSpec.configure runs below, the methods are already defined.
|
|
1020
|
-
|
|
1021
|
-
# Configure RSpec with dependency-based exclusion filters
|
|
1022
|
-
RSpec.configure do |config|
|
|
1023
|
-
deps = TreeHaver::RSpec::DependencyTags
|
|
1024
|
-
|
|
1025
|
-
# Define exclusion filters for optional dependencies
|
|
1026
|
-
# Tests tagged with these will be skipped when the dependency is not available
|
|
1027
|
-
|
|
1028
|
-
# ============================================================
|
|
1029
|
-
# Backend Protection for Test Suites
|
|
1030
|
-
# ============================================================
|
|
1031
|
-
#
|
|
1032
|
-
# TreeHaver protects against backend conflicts by default (e.g., FFI cannot
|
|
1033
|
-
# be used after MRI has been loaded because it would cause a segfault).
|
|
1034
|
-
# This protection remains enabled in test suites to prevent crashes.
|
|
1035
|
-
#
|
|
1036
|
-
# If you need to test multiple incompatible backends in the same process
|
|
1037
|
-
# (accepting the risk of segfaults), you can disable protection:
|
|
1038
|
-
# TREE_HAVER_BACKEND_PROTECT=false bundle exec rspec
|
|
1039
|
-
#
|
|
1040
|
-
# Note: The recommended approach is to run separate test processes for
|
|
1041
|
-
# incompatible backends using RSpec tags or separate CI jobs.
|
|
1042
|
-
if ENV["TREE_HAVER_BACKEND_PROTECT"] == "false"
|
|
1043
|
-
TreeHaver.backend_protect = false
|
|
1044
|
-
end
|
|
1045
|
-
|
|
1046
|
-
config.before(:suite) do
|
|
1047
|
-
# Print dependency summary if TREE_HAVER_DEBUG is set
|
|
1048
|
-
unless ENV.fetch("TREE_HAVER_DEBUG", "false").casecmp?("false")
|
|
1049
|
-
puts "\n=== TreeHaver Environment Variables ==="
|
|
1050
|
-
deps.env_summary.each do |var, value|
|
|
1051
|
-
puts " #{var}: #{value.inspect}"
|
|
1052
|
-
end
|
|
1053
|
-
|
|
1054
|
-
# Only print full dependency summary if we're not running with blocked backends
|
|
1055
|
-
# The summary calls grammar availability checks which would load blocked backends
|
|
1056
|
-
current_blocked = TreeHaver::RSpec::DependencyTags.instance_variable_get(:@blocked_backends) || Set.new
|
|
1057
|
-
if current_blocked.any?
|
|
1058
|
-
puts "\n=== TreeHaver Test Dependencies (limited - running isolated tests) ==="
|
|
1059
|
-
puts " blocked_backends: #{current_blocked.to_a.inspect}"
|
|
1060
|
-
puts " (Skipping full summary to avoid loading blocked backends)"
|
|
1061
|
-
else
|
|
1062
|
-
puts "\n=== TreeHaver Test Dependencies ==="
|
|
1063
|
-
deps.summary.each do |dep, available|
|
|
1064
|
-
status = case available
|
|
1065
|
-
when true then "✓ available"
|
|
1066
|
-
when false then "✗ not available"
|
|
1067
|
-
else available.to_s
|
|
1068
|
-
end
|
|
1069
|
-
puts " #{dep}: #{status}"
|
|
1070
|
-
end
|
|
1071
|
-
end
|
|
1072
|
-
puts "===================================\n"
|
|
1073
|
-
end
|
|
1074
|
-
end
|
|
1075
|
-
|
|
1076
|
-
# ============================================================
|
|
1077
|
-
# TreeHaver Backend Tags
|
|
1078
|
-
# ============================================================
|
|
1079
|
-
# Tags: *_backend - require a specific TreeHaver backend to be available
|
|
1080
|
-
#
|
|
1081
|
-
# Native backends (load .so files):
|
|
1082
|
-
# :ffi_backend, :mri_backend, :rust_backend, :java_backend
|
|
1083
|
-
# Pure-Ruby backends:
|
|
1084
|
-
# :prism_backend, :psych_backend, :commonmarker_backend, :markly_backend, :citrus_backend
|
|
1085
|
-
#
|
|
1086
|
-
# Isolated backend tags (for running tests without loading conflicting backends):
|
|
1087
|
-
# :ffi_backend_only - runs FFI tests without loading MRI backend
|
|
1088
|
-
# :mri_backend_only - runs MRI tests without checking FFI availability
|
|
1089
|
-
|
|
1090
|
-
# FFI backend exclusion:
|
|
1091
|
-
# If MRI has already been used, FFI is blocked and will never be available.
|
|
1092
|
-
# In this case, exclude FFI tests tagged with :ffi_backend entirely rather than
|
|
1093
|
-
# showing them as pending.
|
|
1094
|
-
#
|
|
1095
|
-
# NOTE: We do NOT exclude :ffi_backend_only here because the Rakefile uses
|
|
1096
|
-
# `--tag ~ffi_backend_only` for the remaining_specs task. RSpec interprets
|
|
1097
|
-
# `--tag ~X` as an include filter with key "~X", which conflicts with
|
|
1098
|
-
# filter_run_excluding. Instead, :ffi_backend_only tests will be skipped
|
|
1099
|
-
# via the before(:each) hook below when FFI is not available.
|
|
1100
|
-
if TreeHaver.backends_used.include?(:mri)
|
|
1101
|
-
config.filter_run_excluding(ffi_backend: true)
|
|
1102
|
-
end
|
|
1103
|
-
|
|
1104
|
-
# FFI availability is checked dynamically per-test (not at load time)
|
|
1105
|
-
# because FFI becomes unavailable after MRI backend is used.
|
|
1106
|
-
# When running with :ffi_backend_only tag, this hook defers to the isolated check.
|
|
1107
|
-
config.before(:each, :ffi_backend) do |example|
|
|
1108
|
-
# If also tagged with :ffi_backend_only, let that hook handle the check
|
|
1109
|
-
next if example.metadata[:ffi_backend_only]
|
|
1110
|
-
|
|
1111
|
-
skip "FFI backend not available (MRI backend may have been used)" unless deps.ffi_available?
|
|
1112
|
-
end
|
|
1113
|
-
|
|
1114
|
-
# ISOLATED FFI TAG: Checked dynamically but does NOT trigger mri_backend_available?
|
|
1115
|
-
# Use this tag for tests that must run before MRI is loaded (e.g., in ffi_specs task)
|
|
1116
|
-
config.before(:each, :ffi_backend_only) do
|
|
1117
|
-
skip "FFI backend not available (isolated check)" unless deps.ffi_backend_only_available?
|
|
1118
|
-
end
|
|
1119
|
-
|
|
1120
|
-
# ISOLATED MRI TAG: Checked dynamically but does NOT trigger ffi_available?
|
|
1121
|
-
# Use this tag for tests that should run without FFI interference
|
|
1122
|
-
config.before(:each, :mri_backend_only) do
|
|
1123
|
-
skip "MRI backend not available (isolated check)" unless deps.mri_backend_only_available?
|
|
1124
|
-
end
|
|
1125
|
-
|
|
1126
|
-
# ============================================================
|
|
1127
|
-
# Dynamic Backend Exclusions (using BLOCKED_BY)
|
|
1128
|
-
# ============================================================
|
|
1129
|
-
# When running with *_backend_only tags, we skip availability checks for
|
|
1130
|
-
# backends that would block the isolated backend. This prevents loading
|
|
1131
|
-
# conflicting backends before isolated tests run.
|
|
1132
|
-
#
|
|
1133
|
-
# For example, when running with --tag ffi_backend_only:
|
|
1134
|
-
# - FFI is blocked by [:mri] (from BLOCKED_BY)
|
|
1135
|
-
# - So we skip mri_backend_available? to prevent loading MRI
|
|
1136
|
-
#
|
|
1137
|
-
# This is dynamic based on TreeHaver::Backends::BLOCKED_BY configuration.
|
|
1138
|
-
|
|
1139
|
-
# Build backend maps dynamically from BackendRegistry and built-in backends
|
|
1140
|
-
# This allows external gems to register and automatically get tag support
|
|
1141
|
-
backend_availability_methods = {}
|
|
1142
|
-
backend_tags = {}
|
|
1143
|
-
|
|
1144
|
-
# Built-in backends (always present in tree_haver)
|
|
1145
|
-
builtin_backends = %i[mri rust ffi java prism psych citrus parslet rbs]
|
|
1146
|
-
builtin_backends.each do |backend|
|
|
1147
|
-
# Special case for ffi which uses ffi_available? not ffi_backend_available?
|
|
1148
|
-
availability_method = (backend == :ffi) ? :ffi_available? : :"#{backend}_available?"
|
|
1149
|
-
# Special case for backends that use *_backend_available? naming
|
|
1150
|
-
availability_method = :"#{backend}_backend_available?" if %i[mri rust java rbs].include?(backend)
|
|
1151
|
-
|
|
1152
|
-
backend_availability_methods[backend] = availability_method
|
|
1153
|
-
backend_tags[backend] = :"#{backend}_backend"
|
|
1154
|
-
end
|
|
1155
|
-
|
|
1156
|
-
# Add dynamically registered backends from BackendRegistry
|
|
1157
|
-
# This picks up external gems like commonmarker-merge, markly-merge, etc.
|
|
1158
|
-
TreeHaver::BackendRegistry.registered_tags.each do |tag_name|
|
|
1159
|
-
meta = TreeHaver::BackendRegistry.tag_metadata(tag_name)
|
|
1160
|
-
next unless meta && meta[:category] == :backend
|
|
1161
|
-
|
|
1162
|
-
backend_name = meta[:backend_name]
|
|
1163
|
-
next if backend_availability_methods.key?(backend_name) # Don't override built-ins
|
|
1164
|
-
|
|
1165
|
-
backend_availability_methods[backend_name] = :"#{backend_name}_available?"
|
|
1166
|
-
backend_tags[backend_name] = tag_name
|
|
1167
|
-
end
|
|
1168
|
-
|
|
1169
|
-
# Determine which backends should NOT have availability checked
|
|
1170
|
-
# based on which *_backend_only tag is being run OR which backend is
|
|
1171
|
-
# explicitly selected via TREE_HAVER_BACKEND environment variable.
|
|
1172
|
-
blocked_backends = Set.new
|
|
1173
|
-
|
|
1174
|
-
# Track whether we're in isolated test mode (running *_backend_only tags).
|
|
1175
|
-
# This is different from just having TREE_HAVER_BACKEND set.
|
|
1176
|
-
# In isolated mode, we skip ALL grammar checks because they might trigger
|
|
1177
|
-
# backend loading via TreeHaver.parser_for's auto-detection.
|
|
1178
|
-
# When just TREE_HAVER_BACKEND is set, grammar checks are fine because
|
|
1179
|
-
# parser_for will use the selected backend, not auto-detect.
|
|
1180
|
-
isolated_test_mode = false
|
|
1181
|
-
|
|
1182
|
-
# First, check if TREE_HAVER_BACKEND explicitly selects a backend.
|
|
1183
|
-
# If so, block all backends that would conflict with it.
|
|
1184
|
-
# This prevents loading MRI when TREE_HAVER_BACKEND=ffi, for example.
|
|
1185
|
-
env_backend = ENV["TREE_HAVER_BACKEND"]
|
|
1186
|
-
if env_backend && !env_backend.empty? && env_backend != "auto"
|
|
1187
|
-
backend_sym = env_backend.to_sym
|
|
1188
|
-
TreeHaver::Backends::BLOCKED_BY[backend_sym]&.each { |blocker| blocked_backends << blocker }
|
|
1189
|
-
end
|
|
1190
|
-
|
|
1191
|
-
# Check which *_backend_only tags are being run and block their conflicting backends
|
|
1192
|
-
# config.inclusion_filter contains tags passed via --tag on command line
|
|
1193
|
-
inclusion_rules = config.inclusion_filter.rules
|
|
1194
|
-
|
|
1195
|
-
# If filter.rules is empty, check ARGV directly for --tag options
|
|
1196
|
-
# This handles the case where RSpec hasn't processed filters yet during configuration
|
|
1197
|
-
if inclusion_rules.empty?
|
|
1198
|
-
ARGV.each_with_index do |arg, i|
|
|
1199
|
-
if arg == "--tag" && ARGV[i + 1]
|
|
1200
|
-
tag_str = ARGV[i + 1]
|
|
1201
|
-
# Skip exclusion tags (prefixed with ~) - they are NOT inclusion filters
|
|
1202
|
-
next if tag_str.start_with?("~")
|
|
1203
|
-
tag_value = tag_str.to_sym
|
|
1204
|
-
inclusion_rules[tag_value] = true
|
|
1205
|
-
elsif arg.start_with?("--tag=")
|
|
1206
|
-
tag_str = arg.sub("--tag=", "")
|
|
1207
|
-
# Skip exclusion tags (prefixed with ~) - they are NOT inclusion filters
|
|
1208
|
-
next if tag_str.start_with?("~")
|
|
1209
|
-
tag_value = tag_str.to_sym
|
|
1210
|
-
inclusion_rules[tag_value] = true
|
|
1211
|
-
end
|
|
1212
|
-
end
|
|
1213
|
-
end
|
|
1214
|
-
|
|
1215
|
-
# Check if we're running isolated backend tests using standard backend tags
|
|
1216
|
-
# When running with --tag ffi_backend (or other native backend tags), we need
|
|
1217
|
-
# to block conflicting backends to prevent them from loading first.
|
|
1218
|
-
# This replaces the old *_backend_only pattern with the standard *_backend tags.
|
|
1219
|
-
TreeHaver::Backends::BLOCKED_BY.each do |backend, blockers|
|
|
1220
|
-
# Check if we're running this backend's tests using standard tag (e.g., :ffi_backend)
|
|
1221
|
-
standard_tag = :"#{backend}_backend"
|
|
1222
|
-
if inclusion_rules[standard_tag]
|
|
1223
|
-
isolated_test_mode = true
|
|
1224
|
-
# Add all backends that would block this one
|
|
1225
|
-
blockers.each { |blocker| blocked_backends << blocker }
|
|
1226
|
-
end
|
|
1227
|
-
|
|
1228
|
-
# Also support legacy *_backend_only tags for backwards compatibility
|
|
1229
|
-
legacy_tag = :"#{backend}_backend_only"
|
|
1230
|
-
if inclusion_rules[legacy_tag]
|
|
1231
|
-
isolated_test_mode = true
|
|
1232
|
-
blockers.each { |blocker| blocked_backends << blocker }
|
|
1233
|
-
end
|
|
1234
|
-
end
|
|
1235
|
-
|
|
1236
|
-
# Store blocked_backends in a module variable so before(:suite) can access it
|
|
1237
|
-
TreeHaver::RSpec::DependencyTags.instance_variable_set(:@blocked_backends, blocked_backends)
|
|
1238
|
-
TreeHaver::RSpec::DependencyTags.instance_variable_set(:@isolated_test_mode, isolated_test_mode)
|
|
1239
|
-
|
|
1240
|
-
# Now configure exclusions, skipping availability checks for blocked backends
|
|
1241
|
-
backend_tags.each do |backend, tag|
|
|
1242
|
-
# FFI is handled specially with before(:each) hook above
|
|
1243
|
-
next if backend == :ffi
|
|
1244
|
-
|
|
1245
|
-
# If this backend is in blocked_backends, we exclude its tests WITHOUT checking
|
|
1246
|
-
# availability. This prevents loading a conflicting backend while still ensuring
|
|
1247
|
-
# tests for unavailable backends are skipped.
|
|
1248
|
-
if blocked_backends.include?(backend)
|
|
1249
|
-
config.filter_run_excluding(tag => true)
|
|
1250
|
-
next
|
|
1251
|
-
end
|
|
1252
|
-
|
|
1253
|
-
availability_method = backend_availability_methods[backend]
|
|
1254
|
-
config.filter_run_excluding(tag => true) unless deps.public_send(availability_method)
|
|
1255
|
-
end
|
|
1256
|
-
|
|
1257
|
-
# ============================================================
|
|
1258
|
-
# Ruby Engine Tags
|
|
1259
|
-
# ============================================================
|
|
1260
|
-
# Tags: *_engine - require a specific Ruby engine
|
|
1261
|
-
# :mri_engine, :jruby_engine, :truffleruby_engine
|
|
1262
|
-
|
|
1263
|
-
config.filter_run_excluding(mri_engine: true) unless deps.mri?
|
|
1264
|
-
config.filter_run_excluding(jruby_engine: true) unless deps.jruby?
|
|
1265
|
-
config.filter_run_excluding(truffleruby_engine: true) unless deps.truffleruby?
|
|
1266
|
-
|
|
1267
|
-
# ============================================================
|
|
1268
|
-
# Tree-Sitter Grammar Tags
|
|
1269
|
-
# ============================================================
|
|
1270
|
-
# Tags: *_grammar - require a specific tree-sitter grammar (.so file)
|
|
1271
|
-
# :bash_grammar, :toml_grammar, :json_grammar, :jsonc_grammar, :rbs_grammar
|
|
1272
|
-
#
|
|
1273
|
-
# Also: :libtree_sitter - requires the libtree-sitter runtime library
|
|
1274
|
-
#
|
|
1275
|
-
# NOTE: When running with *_backend_only tags, we skip these checks to avoid
|
|
1276
|
-
# loading blocked backends. The grammar checks use TreeHaver.parser_for which
|
|
1277
|
-
# would load the default backend (MRI) and block FFI.
|
|
1278
|
-
|
|
1279
|
-
# Skip grammar availability checks only when in isolated test mode.
|
|
1280
|
-
# When TREE_HAVER_BACKEND is explicitly set (but not using *_backend_only tags),
|
|
1281
|
-
# grammar checks are fine because TreeHaver.parser_for respects the env var.
|
|
1282
|
-
unless isolated_test_mode
|
|
1283
|
-
config.before(:each) do |example|
|
|
1284
|
-
grammar_tags = {
|
|
1285
|
-
bash_grammar: [:tree_sitter_bash_available?, "tree-sitter-bash"],
|
|
1286
|
-
toml_grammar: [:tree_sitter_toml_available?, "tree-sitter-toml"],
|
|
1287
|
-
json_grammar: [:tree_sitter_json_available?, "tree-sitter-json"],
|
|
1288
|
-
jsonc_grammar: [:tree_sitter_jsonc_available?, "tree-sitter-jsonc"],
|
|
1289
|
-
rbs_grammar: [:tree_sitter_rbs_available?, "tree-sitter-rbs"],
|
|
1290
|
-
libtree_sitter: [:libtree_sitter_available?, "libtree-sitter"],
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
grammar_tags.each do |tag, (method, name)|
|
|
1294
|
-
next unless example.metadata[tag]
|
|
1295
|
-
unless deps.public_send(method)
|
|
1296
|
-
env_var = "TREE_SITTER_#{tag.to_s.sub("_grammar", "").upcase}_PATH"
|
|
1297
|
-
env_var = "TREE_SITTER_RUNTIME_LIB" if tag == :libtree_sitter
|
|
1298
|
-
skip "#{name} grammar not available. Set #{env_var} to the path of the shared library."
|
|
1299
|
-
end
|
|
1300
|
-
end
|
|
1301
|
-
end
|
|
1302
|
-
end
|
|
1303
|
-
|
|
1304
|
-
# ============================================================
|
|
1305
|
-
# Language Parsing Capability Tags
|
|
1306
|
-
# ============================================================
|
|
1307
|
-
# Tags: *_parsing - require ANY parser for a language (any backend that can parse it)
|
|
1308
|
-
# :toml_parsing - any TOML parser (tree-sitter-toml OR toml-rb/Citrus OR toml/Parslet)
|
|
1309
|
-
# :markdown_parsing - any Markdown parser (commonmarker OR markly)
|
|
1310
|
-
# :rbs_parsing - any RBS parser (rbs gem OR tree-sitter-rbs)
|
|
1311
|
-
# :native_parsing - any native tree-sitter backend + grammar
|
|
1312
|
-
#
|
|
1313
|
-
# NOTE: any_toml_backend_available? calls tree_sitter_toml_available? which
|
|
1314
|
-
# triggers grammar_works? and loads MRI. Skip when running isolated tests.
|
|
1315
|
-
|
|
1316
|
-
unless isolated_test_mode
|
|
1317
|
-
config.before(:each) do |example|
|
|
1318
|
-
parsing_tags = {
|
|
1319
|
-
toml_parsing: [:any_toml_backend_available?, "TOML"],
|
|
1320
|
-
markdown_parsing: [:any_markdown_backend_available?, "Markdown"],
|
|
1321
|
-
rbs_parsing: [:any_rbs_backend_available?, "RBS"],
|
|
1322
|
-
native_parsing: [:any_native_grammar_available?, "Native"],
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
parsing_tags.each do |tag, (method, name)|
|
|
1326
|
-
next unless example.metadata[tag]
|
|
1327
|
-
unless deps.public_send(method)
|
|
1328
|
-
skip "#{name} parsing capability not available."
|
|
1329
|
-
end
|
|
1330
|
-
end
|
|
1331
|
-
end
|
|
1332
|
-
end
|
|
1333
|
-
|
|
1334
|
-
# ============================================================
|
|
1335
|
-
# Specific Library Tags
|
|
1336
|
-
# ============================================================
|
|
1337
|
-
# Tags for specific gems/libraries (*_gem suffix)
|
|
1338
|
-
# :toml_gem - the toml gem (Parslet-based TOML parser)
|
|
1339
|
-
# :toml_rb_gem - the toml-rb gem (Citrus-based TOML parser)
|
|
1340
|
-
# :rbs_gem - the rbs gem (official RBS parser, MRI only)
|
|
1341
|
-
# Note: :rbs_backend is also available as an alias for :rbs_gem
|
|
1342
|
-
|
|
1343
|
-
config.filter_run_excluding(toml_gem: true) unless deps.toml_gem_available?
|
|
1344
|
-
config.filter_run_excluding(toml_rb_gem: true) unless deps.toml_rb_gem_available?
|
|
1345
|
-
config.filter_run_excluding(rbs_gem: true) unless deps.rbs_gem_available?
|
|
1346
|
-
|
|
1347
|
-
# ============================================================
|
|
1348
|
-
# Negated Tags (run when dependency is NOT available)
|
|
1349
|
-
# ============================================================
|
|
1350
|
-
# Prefix: not_* - exclude tests when the dependency IS available
|
|
1351
|
-
|
|
1352
|
-
# NOTE: :not_ffi_backend tag is not provided because FFI availability is dynamic.
|
|
1353
|
-
|
|
1354
|
-
# TreeHaver backends - handled dynamically to respect blocked backends
|
|
1355
|
-
backend_tags.each do |backend, tag|
|
|
1356
|
-
next if blocked_backends.include?(backend)
|
|
1357
|
-
|
|
1358
|
-
# FFI is handled specially (availability is always dynamic)
|
|
1359
|
-
next if backend == :ffi
|
|
1360
|
-
|
|
1361
|
-
negated_tag = :"not_#{tag}"
|
|
1362
|
-
availability_method = backend_availability_methods[backend]
|
|
1363
|
-
config.filter_run_excluding(negated_tag => true) if deps.public_send(availability_method)
|
|
1364
|
-
end
|
|
1365
|
-
|
|
1366
|
-
# Ruby engines
|
|
1367
|
-
config.filter_run_excluding(not_mri_engine: true) if deps.mri?
|
|
1368
|
-
config.filter_run_excluding(not_jruby_engine: true) if deps.jruby?
|
|
1369
|
-
config.filter_run_excluding(not_truffleruby_engine: true) if deps.truffleruby?
|
|
1370
|
-
|
|
1371
|
-
# Tree-sitter grammars - skip when running isolated backend tests
|
|
1372
|
-
unless isolated_test_mode
|
|
1373
|
-
config.filter_run_excluding(not_libtree_sitter: true) if deps.libtree_sitter_available?
|
|
1374
|
-
config.filter_run_excluding(not_bash_grammar: true) if deps.tree_sitter_bash_available?
|
|
1375
|
-
config.filter_run_excluding(not_toml_grammar: true) if deps.tree_sitter_toml_available?
|
|
1376
|
-
config.filter_run_excluding(not_json_grammar: true) if deps.tree_sitter_json_available?
|
|
1377
|
-
config.filter_run_excluding(not_jsonc_grammar: true) if deps.tree_sitter_jsonc_available?
|
|
1378
|
-
config.filter_run_excluding(not_rbs_grammar: true) if deps.tree_sitter_rbs_available?
|
|
1379
|
-
|
|
1380
|
-
# Language parsing capabilities
|
|
1381
|
-
config.filter_run_excluding(not_toml_parsing: true) if deps.any_toml_backend_available?
|
|
1382
|
-
config.filter_run_excluding(not_markdown_parsing: true) if deps.any_markdown_backend_available?
|
|
1383
|
-
config.filter_run_excluding(not_json_parsing: true) if deps.any_json_backend_available?
|
|
1384
|
-
config.filter_run_excluding(not_jsonc_parsing: true) if deps.any_jsonc_backend_available?
|
|
1385
|
-
config.filter_run_excluding(not_rbs_parsing: true) if deps.any_rbs_backend_available?
|
|
1386
|
-
end
|
|
1387
|
-
|
|
1388
|
-
# Specific libraries
|
|
1389
|
-
config.filter_run_excluding(not_toml_gem: true) if deps.toml_gem_available?
|
|
1390
|
-
config.filter_run_excluding(not_toml_rb_gem: true) if deps.toml_rb_gem_available?
|
|
1391
|
-
config.filter_run_excluding(not_rbs_gem: true) if deps.rbs_gem_available?
|
|
1392
|
-
end
|