tree_haver 3.1.2 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +227 -2
- data/README.md +391 -357
- data/lib/tree_haver/backends/citrus.rb +7 -1
- data/lib/tree_haver/backends/ffi.rb +80 -66
- data/lib/tree_haver/backends/java.rb +11 -4
- data/lib/tree_haver/backends/mri.rb +37 -21
- data/lib/tree_haver/backends/rust.rb +17 -5
- data/lib/tree_haver/citrus_grammar_finder.rb +57 -9
- data/lib/tree_haver/grammar_finder.rb +4 -1
- data/lib/tree_haver/language.rb +255 -0
- data/lib/tree_haver/library_path_utils.rb +80 -0
- data/lib/tree_haver/node.rb +4 -1
- data/lib/tree_haver/parser.rb +352 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +406 -226
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +128 -560
- data.tar.gz.sig +0 -0
- metadata +7 -4
- metadata.gz.sig +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
3
5
|
# TreeHaver RSpec Dependency Tags
|
|
4
6
|
#
|
|
5
7
|
# This module provides dependency detection helpers for conditional test execution
|
|
@@ -49,49 +51,59 @@
|
|
|
49
51
|
# # This test only runs when Psych is available
|
|
50
52
|
# end
|
|
51
53
|
#
|
|
52
|
-
# it "requires Commonmarker backend", :
|
|
54
|
+
# it "requires Commonmarker backend", :commonmarker_backend do
|
|
53
55
|
# # This test only runs when commonmarker gem is available
|
|
54
56
|
# end
|
|
55
57
|
#
|
|
56
|
-
# it "requires Markly backend", :
|
|
58
|
+
# it "requires Markly backend", :markly_backend do
|
|
57
59
|
# # This test only runs when markly gem is available
|
|
58
60
|
# end
|
|
59
61
|
#
|
|
60
|
-
# it "requires Citrus
|
|
61
|
-
# # This test only runs when
|
|
62
|
+
# it "requires Citrus backend", :citrus_backend do
|
|
63
|
+
# # This test only runs when Citrus gem is available
|
|
62
64
|
# end
|
|
63
65
|
#
|
|
64
66
|
# @example Language-specific grammar tags (for *-merge gems)
|
|
65
|
-
# it "requires tree-sitter-bash", :
|
|
67
|
+
# it "requires tree-sitter-bash", :bash_grammar do
|
|
66
68
|
# # This test only runs when bash grammar is available and parsing works
|
|
67
69
|
# end
|
|
68
70
|
#
|
|
69
|
-
# it "requires tree-sitter-json", :
|
|
71
|
+
# it "requires tree-sitter-json", :json_grammar do
|
|
70
72
|
# # This test only runs when json grammar is available and parsing works
|
|
71
73
|
# end
|
|
72
74
|
#
|
|
73
|
-
#
|
|
74
|
-
# it "requires toml-merge", :toml_merge do
|
|
75
|
-
# # This test only runs when toml-merge is fully functional
|
|
76
|
-
# end
|
|
75
|
+
# == Available Tags
|
|
77
76
|
#
|
|
78
|
-
#
|
|
79
|
-
# # This test only runs when prism-merge is fully functional
|
|
80
|
-
# end
|
|
77
|
+
# === Naming Conventions
|
|
81
78
|
#
|
|
82
|
-
#
|
|
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.)
|
|
83
84
|
#
|
|
84
85
|
# === Positive Tags (run when dependency IS available)
|
|
85
86
|
#
|
|
86
|
-
# ==== TreeHaver Backend Tags
|
|
87
|
+
# ==== TreeHaver Backend Tags (*_backend)
|
|
87
88
|
#
|
|
88
|
-
# [:
|
|
89
|
+
# [:ffi_backend]
|
|
89
90
|
# FFI backend is available. Checked dynamically per-test because FFI becomes
|
|
90
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.
|
|
91
98
|
#
|
|
92
99
|
# [:mri_backend]
|
|
93
100
|
# ruby_tree_sitter gem is available.
|
|
94
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
|
+
#
|
|
95
107
|
# [:rust_backend]
|
|
96
108
|
# tree_stump gem is available.
|
|
97
109
|
#
|
|
@@ -104,83 +116,66 @@
|
|
|
104
116
|
# [:psych_backend]
|
|
105
117
|
# Psych is available (stdlib, should always be true).
|
|
106
118
|
#
|
|
107
|
-
# [:
|
|
119
|
+
# [:commonmarker_backend]
|
|
108
120
|
# commonmarker gem is available.
|
|
109
121
|
#
|
|
110
|
-
# [:
|
|
122
|
+
# [:markly_backend]
|
|
111
123
|
# markly gem is available.
|
|
112
124
|
#
|
|
113
|
-
# [:
|
|
114
|
-
#
|
|
125
|
+
# [:citrus_backend]
|
|
126
|
+
# Citrus gem is available.
|
|
115
127
|
#
|
|
116
|
-
# ==== Ruby Engine Tags
|
|
128
|
+
# ==== Ruby Engine Tags (*_engine)
|
|
117
129
|
#
|
|
118
|
-
# [:
|
|
130
|
+
# [:mri_engine]
|
|
131
|
+
# Running on MRI (CRuby).
|
|
132
|
+
#
|
|
133
|
+
# [:jruby_engine]
|
|
119
134
|
# Running on JRuby.
|
|
120
135
|
#
|
|
121
|
-
# [:
|
|
136
|
+
# [:truffleruby_engine]
|
|
122
137
|
# Running on TruffleRuby.
|
|
123
138
|
#
|
|
124
|
-
#
|
|
125
|
-
# Running on MRI (CRuby).
|
|
126
|
-
#
|
|
127
|
-
# ==== Grammar/Library Tags
|
|
139
|
+
# ==== Tree-Sitter Grammar Tags (*_grammar)
|
|
128
140
|
#
|
|
129
141
|
# [:libtree_sitter]
|
|
130
142
|
# libtree-sitter.so is loadable via FFI.
|
|
131
143
|
#
|
|
132
|
-
# [:
|
|
133
|
-
# A TOML grammar library is available (via TREE_SITTER_TOML_PATH env var).
|
|
134
|
-
#
|
|
135
|
-
# [:native_parsing]
|
|
136
|
-
# Both libtree_sitter and toml_grammar are available.
|
|
137
|
-
#
|
|
138
|
-
# ==== Language-Specific Grammar Tags (for *-merge gems)
|
|
139
|
-
#
|
|
140
|
-
# [:tree_sitter_bash]
|
|
144
|
+
# [:bash_grammar]
|
|
141
145
|
# tree-sitter-bash grammar is available and parsing works.
|
|
142
146
|
#
|
|
143
|
-
# [:
|
|
147
|
+
# [:toml_grammar]
|
|
144
148
|
# tree-sitter-toml grammar is available and parsing works.
|
|
145
149
|
#
|
|
146
|
-
# [:
|
|
150
|
+
# [:json_grammar]
|
|
147
151
|
# tree-sitter-json grammar is available and parsing works.
|
|
148
152
|
#
|
|
149
|
-
# [:
|
|
153
|
+
# [:jsonc_grammar]
|
|
150
154
|
# tree-sitter-jsonc grammar is available and parsing works.
|
|
151
155
|
#
|
|
152
|
-
#
|
|
153
|
-
# toml-rb gem is available (Citrus backend for TOML).
|
|
154
|
-
#
|
|
155
|
-
# [:toml_backend]
|
|
156
|
-
# At least one TOML backend (tree-sitter or toml-rb) is available.
|
|
157
|
-
#
|
|
158
|
-
# [:markdown_backend]
|
|
159
|
-
# At least one markdown backend (markly or commonmarker) is available.
|
|
156
|
+
# ==== Language Parsing Capability Tags (*_parsing)
|
|
160
157
|
#
|
|
161
|
-
#
|
|
158
|
+
# [:toml_parsing]
|
|
159
|
+
# At least one TOML parser (tree-sitter-toml OR toml-rb/Citrus) is available.
|
|
162
160
|
#
|
|
163
|
-
# [:
|
|
164
|
-
#
|
|
161
|
+
# [:markdown_parsing]
|
|
162
|
+
# At least one markdown parser (commonmarker OR markly) is available.
|
|
165
163
|
#
|
|
166
|
-
# [:
|
|
167
|
-
#
|
|
164
|
+
# [:native_parsing]
|
|
165
|
+
# A native tree-sitter backend and grammar are available.
|
|
168
166
|
#
|
|
169
|
-
#
|
|
170
|
-
# prism-merge gem is available and functional.
|
|
167
|
+
# ==== Specific Library Tags
|
|
171
168
|
#
|
|
172
|
-
# [:
|
|
173
|
-
#
|
|
169
|
+
# [:toml_rb]
|
|
170
|
+
# toml-rb gem is available (Citrus backend for TOML).
|
|
174
171
|
#
|
|
175
172
|
# === Negated Tags (run when dependency is NOT available)
|
|
176
173
|
#
|
|
177
174
|
# All positive tags have negated versions prefixed with `not_`:
|
|
178
|
-
# - :not_mri_backend, :not_rust_backend, :not_java_backend
|
|
179
|
-
# - :
|
|
180
|
-
# - :not_libtree_sitter, :not_toml_grammar
|
|
181
|
-
# - :
|
|
182
|
-
# - :not_toml_rb, :not_toml_backend, :not_markdown_backend
|
|
183
|
-
# - :not_toml_merge, :not_json_merge, :not_prism_merge, :not_psych_merge
|
|
175
|
+
# - :not_mri_backend, :not_rust_backend, :not_java_backend, etc.
|
|
176
|
+
# - :not_mri_engine, :not_jruby_engine, :not_truffleruby_engine
|
|
177
|
+
# - :not_libtree_sitter, :not_bash_grammar, :not_toml_grammar, etc.
|
|
178
|
+
# - :not_toml_parsing, :not_markdown_parsing
|
|
184
179
|
#
|
|
185
180
|
# == Backend Conflict Protection
|
|
186
181
|
#
|
|
@@ -225,6 +220,10 @@ module TreeHaver
|
|
|
225
220
|
#
|
|
226
221
|
# @return [Boolean] true if FFI backend is usable
|
|
227
222
|
def ffi_available?
|
|
223
|
+
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
224
|
+
# (used by ts_tree_root_node, ts_node_child, ts_node_start_point, etc.)
|
|
225
|
+
return false if truffleruby?
|
|
226
|
+
|
|
228
227
|
# Try to actually use the FFI backend
|
|
229
228
|
path = find_toml_grammar_path
|
|
230
229
|
return false unless path && File.exist?(path)
|
|
@@ -235,10 +234,14 @@ module TreeHaver
|
|
|
235
234
|
true
|
|
236
235
|
rescue TreeHaver::BackendConflict, TreeHaver::NotAvailable, LoadError
|
|
237
236
|
false
|
|
237
|
+
rescue StandardError
|
|
238
|
+
# Catch any other FFI-related errors (e.g., Polyglot::ForeignException)
|
|
239
|
+
false
|
|
238
240
|
end
|
|
239
241
|
|
|
240
242
|
# Check if ruby_tree_sitter gem is available (MRI backend)
|
|
241
243
|
#
|
|
244
|
+
# The MRI backend only works on MRI Ruby (C extension).
|
|
242
245
|
# When this returns true, it also records MRI backend usage with
|
|
243
246
|
# TreeHaver.record_backend_usage(:mri). This is critical for conflict
|
|
244
247
|
# detection - without it, FFI would not know that MRI has been loaded.
|
|
@@ -246,8 +249,13 @@ module TreeHaver
|
|
|
246
249
|
# @return [Boolean] true if ruby_tree_sitter gem is available
|
|
247
250
|
def mri_backend_available?
|
|
248
251
|
return @mri_backend_available if defined?(@mri_backend_available)
|
|
252
|
+
|
|
253
|
+
# ruby_tree_sitter is a C extension that only works on MRI
|
|
254
|
+
return @mri_backend_available = false unless mri?
|
|
255
|
+
|
|
249
256
|
@mri_backend_available = begin
|
|
250
|
-
|
|
257
|
+
# Note: gem is ruby_tree_sitter but requires tree_sitter
|
|
258
|
+
require "tree_sitter"
|
|
251
259
|
# Record that MRI backend is now loaded - this is critical for
|
|
252
260
|
# conflict detection with FFI backend
|
|
253
261
|
TreeHaver.record_backend_usage(:mri)
|
|
@@ -257,11 +265,71 @@ module TreeHaver
|
|
|
257
265
|
end
|
|
258
266
|
end
|
|
259
267
|
|
|
268
|
+
# Check if FFI backend is available WITHOUT loading MRI first
|
|
269
|
+
#
|
|
270
|
+
# This is used for the :ffi_backend_only tag which runs FFI tests
|
|
271
|
+
# in isolation before MRI can be loaded. Unlike ffi_available?,
|
|
272
|
+
# this method does NOT check mri_backend_available?.
|
|
273
|
+
#
|
|
274
|
+
# @return [Boolean] true if FFI backend is usable in isolation
|
|
275
|
+
def ffi_backend_only_available?
|
|
276
|
+
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
277
|
+
return false if truffleruby?
|
|
278
|
+
|
|
279
|
+
# Check if FFI gem is available without loading tree_sitter
|
|
280
|
+
begin
|
|
281
|
+
require "ffi"
|
|
282
|
+
rescue LoadError
|
|
283
|
+
return false
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Try to actually use the FFI backend
|
|
287
|
+
path = find_toml_grammar_path
|
|
288
|
+
return false unless path && File.exist?(path)
|
|
289
|
+
|
|
290
|
+
TreeHaver.with_backend(:ffi) do
|
|
291
|
+
TreeHaver::Language.from_library(path, symbol: "tree_sitter_toml")
|
|
292
|
+
end
|
|
293
|
+
true
|
|
294
|
+
rescue TreeHaver::BackendConflict, TreeHaver::NotAvailable, LoadError
|
|
295
|
+
false
|
|
296
|
+
rescue StandardError
|
|
297
|
+
# Catch any other FFI-related errors
|
|
298
|
+
false
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Check if MRI backend is available WITHOUT checking FFI availability
|
|
302
|
+
#
|
|
303
|
+
# This is used for the :mri_backend_only tag which runs MRI tests
|
|
304
|
+
# without triggering any FFI availability checks.
|
|
305
|
+
#
|
|
306
|
+
# @return [Boolean] true if MRI backend is usable
|
|
307
|
+
def mri_backend_only_available?
|
|
308
|
+
return @mri_backend_only_available if defined?(@mri_backend_only_available)
|
|
309
|
+
|
|
310
|
+
# ruby_tree_sitter is a C extension that only works on MRI
|
|
311
|
+
return @mri_backend_only_available = false unless mri?
|
|
312
|
+
|
|
313
|
+
@mri_backend_only_available = begin
|
|
314
|
+
require "tree_sitter"
|
|
315
|
+
TreeHaver.record_backend_usage(:mri)
|
|
316
|
+
true
|
|
317
|
+
rescue LoadError
|
|
318
|
+
false
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
260
322
|
# Check if tree_stump gem is available (Rust backend)
|
|
261
323
|
#
|
|
324
|
+
# The Rust backend only works on MRI Ruby (magnus uses MRI's C API).
|
|
325
|
+
#
|
|
262
326
|
# @return [Boolean] true if tree_stump gem is available
|
|
263
327
|
def rust_backend_available?
|
|
264
328
|
return @rust_backend_available if defined?(@rust_backend_available)
|
|
329
|
+
|
|
330
|
+
# tree_stump uses magnus which requires MRI's C API
|
|
331
|
+
return @rust_backend_available = false unless mri?
|
|
332
|
+
|
|
265
333
|
@rust_backend_available = begin
|
|
266
334
|
require "tree_stump"
|
|
267
335
|
true
|
|
@@ -289,6 +357,10 @@ module TreeHaver
|
|
|
289
357
|
true
|
|
290
358
|
rescue TreeHaver::NotAvailable, LoadError
|
|
291
359
|
false
|
|
360
|
+
rescue StandardError
|
|
361
|
+
# TruffleRuby raises Polyglot::ForeignException when FFI
|
|
362
|
+
# encounters unsupported types like STRUCT_BY_VALUE
|
|
363
|
+
false
|
|
292
364
|
end
|
|
293
365
|
end
|
|
294
366
|
|
|
@@ -306,9 +378,18 @@ module TreeHaver
|
|
|
306
378
|
# Grammar paths should be configured via TREE_SITTER_TOML_PATH environment variable.
|
|
307
379
|
# This keeps configuration explicit and avoids magic path guessing.
|
|
308
380
|
#
|
|
309
|
-
# @return [String, nil] path
|
|
381
|
+
# @return [String, nil] path to TOML grammar library, or nil if not found
|
|
310
382
|
def find_toml_grammar_path
|
|
311
|
-
|
|
383
|
+
# First check environment variable
|
|
384
|
+
env_path = ENV["TREE_SITTER_TOML_PATH"]
|
|
385
|
+
return env_path if env_path && File.exist?(env_path)
|
|
386
|
+
|
|
387
|
+
# Use GrammarFinder to search standard paths
|
|
388
|
+
finder = TreeHaver::GrammarFinder.new(:toml, validate: false)
|
|
389
|
+
finder.find_library_path
|
|
390
|
+
rescue StandardError
|
|
391
|
+
# GrammarFinder might not be available or might fail
|
|
392
|
+
nil
|
|
312
393
|
end
|
|
313
394
|
|
|
314
395
|
# Check if commonmarker gem is available
|
|
@@ -343,22 +424,14 @@ module TreeHaver
|
|
|
343
424
|
@psych_available = TreeHaver::Backends::Psych.available?
|
|
344
425
|
end
|
|
345
426
|
|
|
346
|
-
# Check if
|
|
427
|
+
# Check if Citrus backend is available
|
|
347
428
|
#
|
|
348
|
-
#
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
language: :toml,
|
|
355
|
-
gem_name: "toml-rb",
|
|
356
|
-
grammar_const: "TomlRB::Document",
|
|
357
|
-
)
|
|
358
|
-
finder.available?
|
|
359
|
-
rescue LoadError, NameError
|
|
360
|
-
false
|
|
361
|
-
end
|
|
429
|
+
# This checks if the citrus gem is installed and the backend works.
|
|
430
|
+
#
|
|
431
|
+
# @return [Boolean] true if Citrus backend is available
|
|
432
|
+
def citrus_available?
|
|
433
|
+
return @citrus_available if defined?(@citrus_available)
|
|
434
|
+
@citrus_available = TreeHaver::Backends::Citrus.available?
|
|
362
435
|
end
|
|
363
436
|
|
|
364
437
|
# ============================================================
|
|
@@ -423,16 +496,20 @@ module TreeHaver
|
|
|
423
496
|
@tree_sitter_jsonc_available = grammar_works?(:jsonc, '{"key": "value" /* comment */}')
|
|
424
497
|
end
|
|
425
498
|
|
|
426
|
-
# Check if toml-rb gem is available (Citrus backend for TOML)
|
|
499
|
+
# Check if toml-rb gem is available and functional (Citrus backend for TOML)
|
|
427
500
|
#
|
|
428
|
-
# @return [Boolean] true if toml-rb gem is available
|
|
501
|
+
# @return [Boolean] true if toml-rb gem is available and can parse TOML
|
|
429
502
|
def toml_rb_available?
|
|
430
503
|
return @toml_rb_available if defined?(@toml_rb_available)
|
|
431
504
|
@toml_rb_available = begin
|
|
432
505
|
require "toml-rb"
|
|
506
|
+
# Verify it can actually parse - just requiring isn't enough
|
|
507
|
+
TomlRB.parse('key = "value"')
|
|
433
508
|
true
|
|
434
509
|
rescue LoadError
|
|
435
510
|
false
|
|
511
|
+
rescue StandardError
|
|
512
|
+
false
|
|
436
513
|
end
|
|
437
514
|
end
|
|
438
515
|
|
|
@@ -450,41 +527,13 @@ module TreeHaver
|
|
|
450
527
|
markly_available? || commonmarker_available?
|
|
451
528
|
end
|
|
452
529
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
# @return [Boolean] true if toml-merge works
|
|
461
|
-
def toml_merge_available?
|
|
462
|
-
return @toml_merge_available if defined?(@toml_merge_available)
|
|
463
|
-
@toml_merge_available = inner_merge_works?("toml/merge", "Toml::Merge::SmartMerger", "key = 'test'")
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
# Check if json-merge is available and functional
|
|
467
|
-
#
|
|
468
|
-
# @return [Boolean] true if json-merge works
|
|
469
|
-
def json_merge_available?
|
|
470
|
-
return @json_merge_available if defined?(@json_merge_available)
|
|
471
|
-
@json_merge_available = inner_merge_works?("json/merge", "Json::Merge::SmartMerger", '{"a":1}')
|
|
472
|
-
end
|
|
473
|
-
|
|
474
|
-
# Check if prism-merge is available and functional
|
|
475
|
-
#
|
|
476
|
-
# @return [Boolean] true if prism-merge works
|
|
477
|
-
def prism_merge_available?
|
|
478
|
-
return @prism_merge_available if defined?(@prism_merge_available)
|
|
479
|
-
@prism_merge_available = inner_merge_works?("prism/merge", "Prism::Merge::SmartMerger", "puts 1")
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
# Check if psych-merge is available and functional
|
|
483
|
-
#
|
|
484
|
-
# @return [Boolean] true if psych-merge works
|
|
485
|
-
def psych_merge_available?
|
|
486
|
-
return @psych_merge_available if defined?(@psych_merge_available)
|
|
487
|
-
@psych_merge_available = inner_merge_works?("psych/merge", "Psych::Merge::SmartMerger", "key: value")
|
|
530
|
+
def any_native_grammar_available?
|
|
531
|
+
libtree_sitter_available? && (
|
|
532
|
+
tree_sitter_bash_available? ||
|
|
533
|
+
tree_sitter_toml_available? ||
|
|
534
|
+
tree_sitter_json_available? ||
|
|
535
|
+
tree_sitter_jsonc_available?
|
|
536
|
+
)
|
|
488
537
|
end
|
|
489
538
|
|
|
490
539
|
# ============================================================
|
|
@@ -496,37 +545,33 @@ module TreeHaver
|
|
|
496
545
|
# @return [Hash{Symbol => Boolean}] map of dependency name to availability
|
|
497
546
|
def summary
|
|
498
547
|
{
|
|
499
|
-
# TreeHaver backends
|
|
500
|
-
|
|
548
|
+
# TreeHaver backends (*_backend)
|
|
549
|
+
ffi_backend: ffi_available?,
|
|
501
550
|
mri_backend: mri_backend_available?,
|
|
502
551
|
rust_backend: rust_backend_available?,
|
|
503
552
|
java_backend: java_backend_available?,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
#
|
|
510
|
-
libtree_sitter: libtree_sitter_available?,
|
|
511
|
-
toml_grammar: toml_grammar_available?,
|
|
512
|
-
# Ruby engines
|
|
553
|
+
prism_backend: prism_available?,
|
|
554
|
+
psych_backend: psych_available?,
|
|
555
|
+
commonmarker_backend: commonmarker_available?,
|
|
556
|
+
markly_backend: markly_available?,
|
|
557
|
+
citrus_backend: citrus_available?,
|
|
558
|
+
# Ruby engines (*_engine)
|
|
513
559
|
ruby_engine: RUBY_ENGINE,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
#
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
560
|
+
mri_engine: mri?,
|
|
561
|
+
jruby_engine: jruby?,
|
|
562
|
+
truffleruby_engine: truffleruby?,
|
|
563
|
+
# Tree-sitter grammars (*_grammar)
|
|
564
|
+
libtree_sitter: libtree_sitter_available?,
|
|
565
|
+
bash_grammar: tree_sitter_bash_available?,
|
|
566
|
+
toml_grammar: tree_sitter_toml_available?,
|
|
567
|
+
json_grammar: tree_sitter_json_available?,
|
|
568
|
+
jsonc_grammar: tree_sitter_jsonc_available?,
|
|
569
|
+
any_native_grammar: any_native_grammar_available?,
|
|
570
|
+
# Language parsing capabilities (*_parsing)
|
|
571
|
+
toml_parsing: any_toml_backend_available?,
|
|
572
|
+
markdown_parsing: any_markdown_backend_available?,
|
|
573
|
+
# Specific libraries
|
|
522
574
|
toml_rb: toml_rb_available?,
|
|
523
|
-
any_toml_backend: any_toml_backend_available?,
|
|
524
|
-
any_markdown_backend: any_markdown_backend_available?,
|
|
525
|
-
# Inner-merge dependencies
|
|
526
|
-
toml_merge: toml_merge_available?,
|
|
527
|
-
json_merge: json_merge_available?,
|
|
528
|
-
prism_merge: prism_merge_available?,
|
|
529
|
-
psych_merge: psych_merge_available?,
|
|
530
575
|
}
|
|
531
576
|
end
|
|
532
577
|
|
|
@@ -598,21 +643,6 @@ module TreeHaver
|
|
|
598
643
|
end
|
|
599
644
|
false
|
|
600
645
|
end
|
|
601
|
-
|
|
602
|
-
# Generic helper to check if an inner-merge gem is available and functional
|
|
603
|
-
#
|
|
604
|
-
# @param require_path [String] the require path for the gem
|
|
605
|
-
# @param merger_class [String] the full class name of the SmartMerger
|
|
606
|
-
# @param test_source [String] sample source code to test merging
|
|
607
|
-
# @return [Boolean] true if the merger can be instantiated
|
|
608
|
-
def inner_merge_works?(require_path, merger_class, test_source)
|
|
609
|
-
require require_path
|
|
610
|
-
klass = Object.const_get(merger_class)
|
|
611
|
-
klass.new(test_source, test_source)
|
|
612
|
-
true
|
|
613
|
-
rescue LoadError, NameError, TreeHaver::Error, TreeHaver::NotAvailable
|
|
614
|
-
false
|
|
615
|
-
end
|
|
616
646
|
end
|
|
617
647
|
end
|
|
618
648
|
end
|
|
@@ -633,14 +663,23 @@ RSpec.configure do |config|
|
|
|
633
663
|
puts " #{var}: #{value.inspect}"
|
|
634
664
|
end
|
|
635
665
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
666
|
+
# Only print full dependency summary if we're not running with blocked backends
|
|
667
|
+
# The summary calls grammar availability checks which would load blocked backends
|
|
668
|
+
current_blocked = TreeHaver::RSpec::DependencyTags.instance_variable_get(:@blocked_backends) || Set.new
|
|
669
|
+
if current_blocked.any?
|
|
670
|
+
puts "\n=== TreeHaver Test Dependencies (limited - running isolated tests) ==="
|
|
671
|
+
puts " blocked_backends: #{current_blocked.to_a.inspect}"
|
|
672
|
+
puts " (Skipping full summary to avoid loading blocked backends)"
|
|
673
|
+
else
|
|
674
|
+
puts "\n=== TreeHaver Test Dependencies ==="
|
|
675
|
+
deps.summary.each do |dep, available|
|
|
676
|
+
status = case available
|
|
677
|
+
when true then "✓ available"
|
|
678
|
+
when false then "✗ not available"
|
|
679
|
+
else available.to_s
|
|
680
|
+
end
|
|
681
|
+
puts " #{dep}: #{status}"
|
|
642
682
|
end
|
|
643
|
-
puts " #{dep}: #{status}"
|
|
644
683
|
end
|
|
645
684
|
puts "===================================\n"
|
|
646
685
|
end
|
|
@@ -649,96 +688,237 @@ RSpec.configure do |config|
|
|
|
649
688
|
# ============================================================
|
|
650
689
|
# TreeHaver Backend Tags
|
|
651
690
|
# ============================================================
|
|
691
|
+
# Tags: *_backend - require a specific TreeHaver backend to be available
|
|
692
|
+
#
|
|
693
|
+
# Native backends (load .so files):
|
|
694
|
+
# :ffi_backend, :mri_backend, :rust_backend, :java_backend
|
|
695
|
+
# Pure-Ruby backends:
|
|
696
|
+
# :prism_backend, :psych_backend, :commonmarker_backend, :markly_backend, :citrus_backend
|
|
697
|
+
#
|
|
698
|
+
# Isolated backend tags (for running tests without loading conflicting backends):
|
|
699
|
+
# :ffi_backend_only - runs FFI tests without loading MRI backend
|
|
700
|
+
# :mri_backend_only - runs MRI tests without checking FFI availability
|
|
701
|
+
|
|
702
|
+
# FFI backend exclusion:
|
|
703
|
+
# If MRI has already been used, FFI is blocked and will never be available.
|
|
704
|
+
# In this case, exclude FFI tests tagged with :ffi_backend entirely rather than
|
|
705
|
+
# showing them as pending.
|
|
706
|
+
#
|
|
707
|
+
# NOTE: We do NOT exclude :ffi_backend_only here because the Rakefile uses
|
|
708
|
+
# `--tag ~ffi_backend_only` for the remaining_specs task. RSpec interprets
|
|
709
|
+
# `--tag ~X` as an include filter with key "~X", which conflicts with
|
|
710
|
+
# filter_run_excluding. Instead, :ffi_backend_only tests will be skipped
|
|
711
|
+
# via the before(:each) hook below when FFI is not available.
|
|
712
|
+
if TreeHaver.backends_used.include?(:mri)
|
|
713
|
+
config.filter_run_excluding(ffi_backend: true)
|
|
714
|
+
end
|
|
652
715
|
|
|
653
716
|
# FFI availability is checked dynamically per-test (not at load time)
|
|
654
717
|
# because FFI becomes unavailable after MRI backend is used.
|
|
655
|
-
|
|
718
|
+
# When running with :ffi_backend_only tag, this hook defers to the isolated check.
|
|
719
|
+
config.before(:each, :ffi_backend) do |example|
|
|
720
|
+
# If also tagged with :ffi_backend_only, let that hook handle the check
|
|
721
|
+
next if example.metadata[:ffi_backend_only]
|
|
722
|
+
|
|
656
723
|
skip "FFI backend not available (MRI backend may have been used)" unless deps.ffi_available?
|
|
657
724
|
end
|
|
658
725
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
config.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
726
|
+
# ISOLATED FFI TAG: Checked dynamically but does NOT trigger mri_backend_available?
|
|
727
|
+
# Use this tag for tests that must run before MRI is loaded (e.g., in ffi_specs task)
|
|
728
|
+
config.before(:each, :ffi_backend_only) do
|
|
729
|
+
skip "FFI backend not available (isolated check)" unless deps.ffi_backend_only_available?
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# ISOLATED MRI TAG: Checked dynamically but does NOT trigger ffi_available?
|
|
733
|
+
# Use this tag for tests that should run without FFI interference
|
|
734
|
+
config.before(:each, :mri_backend_only) do
|
|
735
|
+
skip "MRI backend not available (isolated check)" unless deps.mri_backend_only_available?
|
|
736
|
+
end
|
|
667
737
|
|
|
668
738
|
# ============================================================
|
|
669
|
-
#
|
|
739
|
+
# Dynamic Backend Exclusions (using BLOCKED_BY)
|
|
670
740
|
# ============================================================
|
|
741
|
+
# When running with *_backend_only tags, we skip availability checks for
|
|
742
|
+
# backends that would block the isolated backend. This prevents loading
|
|
743
|
+
# conflicting backends before isolated tests run.
|
|
744
|
+
#
|
|
745
|
+
# For example, when running with --tag ffi_backend_only:
|
|
746
|
+
# - FFI is blocked by [:mri] (from BLOCKED_BY)
|
|
747
|
+
# - So we skip mri_backend_available? to prevent loading MRI
|
|
748
|
+
#
|
|
749
|
+
# This is dynamic based on TreeHaver::Backends::BLOCKED_BY configuration.
|
|
750
|
+
|
|
751
|
+
# Map of backend symbols to their availability check methods
|
|
752
|
+
backend_availability_methods = {
|
|
753
|
+
mri: :mri_backend_available?,
|
|
754
|
+
rust: :rust_backend_available?,
|
|
755
|
+
ffi: :ffi_available?,
|
|
756
|
+
java: :java_backend_available?,
|
|
757
|
+
prism: :prism_available?,
|
|
758
|
+
psych: :psych_available?,
|
|
759
|
+
commonmarker: :commonmarker_available?,
|
|
760
|
+
markly: :markly_available?,
|
|
761
|
+
citrus: :citrus_available?,
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
# Map of backend symbols to their RSpec tag names
|
|
765
|
+
backend_tags = {
|
|
766
|
+
mri: :mri_backend,
|
|
767
|
+
rust: :rust_backend,
|
|
768
|
+
ffi: :ffi_backend,
|
|
769
|
+
java: :java_backend,
|
|
770
|
+
prism: :prism_backend,
|
|
771
|
+
psych: :psych_backend,
|
|
772
|
+
commonmarker: :commonmarker_backend,
|
|
773
|
+
markly: :markly_backend,
|
|
774
|
+
citrus: :citrus_backend,
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
# Determine which backends should NOT have availability checked
|
|
778
|
+
# based on which *_backend_only tag is being run
|
|
779
|
+
blocked_backends = Set.new
|
|
780
|
+
|
|
781
|
+
# Check which *_backend_only tags are being run and block their conflicting backends
|
|
782
|
+
# config.inclusion_filter contains tags passed via --tag on command line
|
|
783
|
+
inclusion_rules = config.inclusion_filter.rules
|
|
784
|
+
|
|
785
|
+
# If filter.rules is empty, check ARGV directly for --tag options
|
|
786
|
+
# This handles the case where RSpec hasn't processed filters yet during configuration
|
|
787
|
+
if inclusion_rules.empty?
|
|
788
|
+
ARGV.each_with_index do |arg, i|
|
|
789
|
+
if arg == "--tag" && ARGV[i + 1]
|
|
790
|
+
tag_str = ARGV[i + 1]
|
|
791
|
+
# Skip exclusion tags (prefixed with ~) - they are NOT inclusion filters
|
|
792
|
+
next if tag_str.start_with?("~")
|
|
793
|
+
tag_value = tag_str.to_sym
|
|
794
|
+
inclusion_rules[tag_value] = true
|
|
795
|
+
elsif arg.start_with?("--tag=")
|
|
796
|
+
tag_str = arg.sub("--tag=", "")
|
|
797
|
+
# Skip exclusion tags (prefixed with ~) - they are NOT inclusion filters
|
|
798
|
+
next if tag_str.start_with?("~")
|
|
799
|
+
tag_value = tag_str.to_sym
|
|
800
|
+
inclusion_rules[tag_value] = true
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
TreeHaver::Backends::BLOCKED_BY.each do |backend, blockers|
|
|
806
|
+
# Check if we're running this backend's isolated tests
|
|
807
|
+
isolated_tag = :"#{backend}_backend_only"
|
|
808
|
+
if inclusion_rules[isolated_tag]
|
|
809
|
+
# Add all backends that would block this one
|
|
810
|
+
blockers.each { |blocker| blocked_backends << blocker }
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
# Store blocked_backends in a module variable so before(:suite) can access it
|
|
815
|
+
TreeHaver::RSpec::DependencyTags.instance_variable_set(:@blocked_backends, blocked_backends)
|
|
816
|
+
|
|
817
|
+
# Now configure exclusions, skipping availability checks for blocked backends
|
|
818
|
+
backend_tags.each do |backend, tag|
|
|
819
|
+
next if blocked_backends.include?(backend)
|
|
671
820
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
821
|
+
# FFI is handled specially with before(:each) hook above
|
|
822
|
+
next if backend == :ffi
|
|
823
|
+
|
|
824
|
+
availability_method = backend_availability_methods[backend]
|
|
825
|
+
config.filter_run_excluding(tag => true) unless deps.public_send(availability_method)
|
|
826
|
+
end
|
|
675
827
|
|
|
676
828
|
# ============================================================
|
|
677
|
-
#
|
|
829
|
+
# Ruby Engine Tags
|
|
678
830
|
# ============================================================
|
|
831
|
+
# Tags: *_engine - require a specific Ruby engine
|
|
832
|
+
# :mri_engine, :jruby_engine, :truffleruby_engine
|
|
679
833
|
|
|
680
|
-
config.filter_run_excluding(
|
|
681
|
-
config.filter_run_excluding(
|
|
682
|
-
config.filter_run_excluding(
|
|
834
|
+
config.filter_run_excluding(mri_engine: true) unless deps.mri?
|
|
835
|
+
config.filter_run_excluding(jruby_engine: true) unless deps.jruby?
|
|
836
|
+
config.filter_run_excluding(truffleruby_engine: true) unless deps.truffleruby?
|
|
683
837
|
|
|
684
838
|
# ============================================================
|
|
685
|
-
#
|
|
839
|
+
# Tree-Sitter Grammar Tags
|
|
686
840
|
# ============================================================
|
|
841
|
+
# Tags: *_grammar - require a specific tree-sitter grammar (.so file)
|
|
842
|
+
# :bash_grammar, :toml_grammar, :json_grammar, :jsonc_grammar
|
|
843
|
+
#
|
|
844
|
+
# Also: :libtree_sitter - requires the libtree-sitter runtime library
|
|
845
|
+
#
|
|
846
|
+
# NOTE: When running with *_backend_only tags, we skip these checks to avoid
|
|
847
|
+
# loading blocked backends. The grammar checks use TreeHaver.parser_for which
|
|
848
|
+
# would load the default backend (MRI) and block FFI.
|
|
849
|
+
|
|
850
|
+
# Skip grammar availability checks if any backend is blocked
|
|
851
|
+
# (i.e., we're running isolated backend tests)
|
|
852
|
+
if blocked_backends.none?
|
|
853
|
+
config.filter_run_excluding(libtree_sitter: true) unless deps.libtree_sitter_available?
|
|
854
|
+
config.filter_run_excluding(bash_grammar: true) unless deps.tree_sitter_bash_available?
|
|
855
|
+
config.filter_run_excluding(toml_grammar: true) unless deps.tree_sitter_toml_available?
|
|
856
|
+
config.filter_run_excluding(json_grammar: true) unless deps.tree_sitter_json_available?
|
|
857
|
+
config.filter_run_excluding(jsonc_grammar: true) unless deps.tree_sitter_jsonc_available?
|
|
858
|
+
end
|
|
687
859
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
860
|
+
# ============================================================
|
|
861
|
+
# Language Parsing Capability Tags
|
|
862
|
+
# ============================================================
|
|
863
|
+
# Tags: *_parsing - require ANY parser for a language (any backend that can parse it)
|
|
864
|
+
# :toml_parsing - any TOML parser (tree-sitter-toml OR toml-rb/Citrus)
|
|
865
|
+
# :markdown_parsing - any Markdown parser (commonmarker OR markly)
|
|
866
|
+
# :native_parsing - any native tree-sitter backend + grammar
|
|
867
|
+
#
|
|
868
|
+
# NOTE: any_toml_backend_available? calls tree_sitter_toml_available? which
|
|
869
|
+
# triggers grammar_works? and loads MRI. Skip when running isolated tests.
|
|
870
|
+
|
|
871
|
+
if blocked_backends.none?
|
|
872
|
+
config.filter_run_excluding(toml_parsing: true) unless deps.any_toml_backend_available?
|
|
873
|
+
config.filter_run_excluding(markdown_parsing: true) unless deps.any_markdown_backend_available?
|
|
874
|
+
config.filter_run_excluding(native_parsing: true) unless deps.any_native_grammar_available?
|
|
875
|
+
end
|
|
695
876
|
|
|
696
877
|
# ============================================================
|
|
697
|
-
#
|
|
878
|
+
# Specific Library Tags
|
|
698
879
|
# ============================================================
|
|
880
|
+
# Tags for specific gems/libraries (not backends, but dependencies)
|
|
881
|
+
# :toml_rb - the toml-rb gem (Citrus-based TOML parser)
|
|
699
882
|
|
|
700
|
-
config.filter_run_excluding(
|
|
701
|
-
config.filter_run_excluding(json_merge: true) unless deps.json_merge_available?
|
|
702
|
-
config.filter_run_excluding(prism_merge: true) unless deps.prism_merge_available?
|
|
703
|
-
config.filter_run_excluding(psych_merge: true) unless deps.psych_merge_available?
|
|
883
|
+
config.filter_run_excluding(toml_rb: true) unless deps.toml_rb_available?
|
|
704
884
|
|
|
705
885
|
# ============================================================
|
|
706
886
|
# Negated Tags (run when dependency is NOT available)
|
|
707
887
|
# ============================================================
|
|
888
|
+
# Prefix: not_* - exclude tests when the dependency IS available
|
|
708
889
|
|
|
709
|
-
# NOTE: :
|
|
890
|
+
# NOTE: :not_ffi_backend tag is not provided because FFI availability is dynamic.
|
|
710
891
|
|
|
711
|
-
# TreeHaver backends
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
892
|
+
# TreeHaver backends - handled dynamically to respect blocked backends
|
|
893
|
+
backend_tags.each do |backend, tag|
|
|
894
|
+
next if blocked_backends.include?(backend)
|
|
895
|
+
|
|
896
|
+
# FFI is handled specially (availability is always dynamic)
|
|
897
|
+
next if backend == :ffi
|
|
898
|
+
|
|
899
|
+
negated_tag = :"not_#{tag}"
|
|
900
|
+
availability_method = backend_availability_methods[backend]
|
|
901
|
+
config.filter_run_excluding(negated_tag => true) if deps.public_send(availability_method)
|
|
902
|
+
end
|
|
720
903
|
|
|
721
904
|
# Ruby engines
|
|
722
|
-
config.filter_run_excluding(
|
|
723
|
-
config.filter_run_excluding(
|
|
724
|
-
config.filter_run_excluding(
|
|
725
|
-
|
|
726
|
-
#
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
905
|
+
config.filter_run_excluding(not_mri_engine: true) if deps.mri?
|
|
906
|
+
config.filter_run_excluding(not_jruby_engine: true) if deps.jruby?
|
|
907
|
+
config.filter_run_excluding(not_truffleruby_engine: true) if deps.truffleruby?
|
|
908
|
+
|
|
909
|
+
# Tree-sitter grammars - skip when running isolated backend tests
|
|
910
|
+
if blocked_backends.none?
|
|
911
|
+
config.filter_run_excluding(not_libtree_sitter: true) if deps.libtree_sitter_available?
|
|
912
|
+
config.filter_run_excluding(not_bash_grammar: true) if deps.tree_sitter_bash_available?
|
|
913
|
+
config.filter_run_excluding(not_toml_grammar: true) if deps.tree_sitter_toml_available?
|
|
914
|
+
config.filter_run_excluding(not_json_grammar: true) if deps.tree_sitter_json_available?
|
|
915
|
+
config.filter_run_excluding(not_jsonc_grammar: true) if deps.tree_sitter_jsonc_available?
|
|
916
|
+
|
|
917
|
+
# Language parsing capabilities
|
|
918
|
+
config.filter_run_excluding(not_toml_parsing: true) if deps.any_toml_backend_available?
|
|
919
|
+
config.filter_run_excluding(not_markdown_parsing: true) if deps.any_markdown_backend_available?
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
# Specific libraries
|
|
735
923
|
config.filter_run_excluding(not_toml_rb: true) if deps.toml_rb_available?
|
|
736
|
-
config.filter_run_excluding(not_toml_backend: true) if deps.any_toml_backend_available?
|
|
737
|
-
config.filter_run_excluding(not_markdown_backend: true) if deps.any_markdown_backend_available?
|
|
738
|
-
|
|
739
|
-
# Inner-merge dependencies
|
|
740
|
-
config.filter_run_excluding(not_toml_merge: true) if deps.toml_merge_available?
|
|
741
|
-
config.filter_run_excluding(not_json_merge: true) if deps.json_merge_available?
|
|
742
|
-
config.filter_run_excluding(not_prism_merge: true) if deps.prism_merge_available?
|
|
743
|
-
config.filter_run_excluding(not_psych_merge: true) if deps.psych_merge_available?
|
|
744
924
|
end
|