tree_haver 3.1.2 → 3.2.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/CHANGELOG.md +146 -2
- data/README.md +391 -357
- data/lib/tree_haver/backends/citrus.rb +7 -1
- data/lib/tree_haver/backends/ffi.rb +73 -61
- data/lib/tree_haver/backends/java.rb +6 -1
- data/lib/tree_haver/backends/mri.rb +14 -1
- data/lib/tree_haver/backends/rust.rb +14 -1
- data/lib/tree_haver/citrus_grammar_finder.rb +57 -9
- data/lib/tree_haver/node.rb +4 -1
- data/lib/tree_haver/rspec/dependency_tags.rb +163 -200
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +122 -15
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
|
@@ -451,8 +451,14 @@ module TreeHaver
|
|
|
451
451
|
private
|
|
452
452
|
|
|
453
453
|
def calculate_point(offset)
|
|
454
|
+
return {row: 0, column: 0} if offset <= 0
|
|
455
|
+
|
|
454
456
|
lines_before = @source[0...offset].count("\n")
|
|
455
|
-
|
|
457
|
+
# Find the newline before this offset (or -1 if we're on line 0)
|
|
458
|
+
line_start = if offset > 0
|
|
459
|
+
@source.rindex("\n", offset - 1)
|
|
460
|
+
end
|
|
461
|
+
line_start ||= -1
|
|
456
462
|
column = offset - line_start - 1
|
|
457
463
|
{row: lines_before, column: column}
|
|
458
464
|
end
|
|
@@ -5,8 +5,7 @@ module TreeHaver
|
|
|
5
5
|
# FFI-based backend for calling libtree-sitter directly
|
|
6
6
|
#
|
|
7
7
|
# This backend uses Ruby FFI (JNR-FFI on JRuby) to call the native tree-sitter
|
|
8
|
-
# C library without requiring MRI C extensions.
|
|
9
|
-
# JRuby, TruffleRuby, and other Ruby implementations that support FFI.
|
|
8
|
+
# C library without requiring MRI C extensions.
|
|
10
9
|
#
|
|
11
10
|
# The FFI backend currently supports:
|
|
12
11
|
# - Parsing source code
|
|
@@ -16,28 +15,85 @@ module TreeHaver
|
|
|
16
15
|
# Not yet supported:
|
|
17
16
|
# - Query API (tree-sitter queries/patterns)
|
|
18
17
|
#
|
|
18
|
+
# == Platform Compatibility
|
|
19
|
+
#
|
|
20
|
+
# - MRI Ruby: ✓ Full support
|
|
21
|
+
# - JRuby: ✓ Full support (uses JNR-FFI)
|
|
22
|
+
# - TruffleRuby: ✗ TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
23
|
+
# (used by ts_tree_root_node, ts_node_child, ts_node_start_point, etc.)
|
|
24
|
+
#
|
|
19
25
|
# @note Requires the `ffi` gem and libtree-sitter shared library to be installed
|
|
20
26
|
# @see https://github.com/ffi/ffi Ruby FFI
|
|
21
27
|
# @see https://tree-sitter.github.io/tree-sitter/ tree-sitter
|
|
22
28
|
module FFI
|
|
23
|
-
#
|
|
29
|
+
# Module-level availability and capability methods
|
|
24
30
|
#
|
|
25
|
-
#
|
|
26
|
-
# polluting the environment at load time.
|
|
31
|
+
# These methods provide API consistency with other backends.
|
|
27
32
|
class << self
|
|
28
|
-
# Check if the FFI
|
|
29
|
-
#
|
|
33
|
+
# Check if the FFI backend is available
|
|
34
|
+
#
|
|
35
|
+
# The FFI backend requires:
|
|
36
|
+
# - The ffi gem to be installed
|
|
37
|
+
# - NOT running on TruffleRuby (STRUCT_BY_VALUE limitation)
|
|
38
|
+
# - MRI backend (ruby_tree_sitter) not already loaded (symbol conflicts)
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean] true if FFI backend can be used
|
|
41
|
+
# @example
|
|
42
|
+
# if TreeHaver::Backends::FFI.available?
|
|
43
|
+
# puts "FFI backend is ready"
|
|
44
|
+
# end
|
|
45
|
+
def available?
|
|
46
|
+
return false unless ffi_gem_available?
|
|
47
|
+
|
|
48
|
+
# Check if MRI backend has been loaded (which blocks FFI)
|
|
49
|
+
!defined?(::TreeSitter::Parser)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Check if the FFI gem can be loaded and is usable for tree-sitter
|
|
53
|
+
#
|
|
54
|
+
# @return [Boolean] true if FFI gem can be loaded and works with tree-sitter
|
|
30
55
|
# @api private
|
|
56
|
+
# @note Returns false on TruffleRuby because TruffleRuby's FFI doesn't support
|
|
57
|
+
# STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)
|
|
31
58
|
def ffi_gem_available?
|
|
32
59
|
return @ffi_gem_available if defined?(@ffi_gem_available)
|
|
33
60
|
|
|
34
61
|
@ffi_gem_available = begin
|
|
62
|
+
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
63
|
+
# which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
|
|
64
|
+
return false if RUBY_ENGINE == "truffleruby"
|
|
65
|
+
|
|
35
66
|
require "ffi"
|
|
36
67
|
true
|
|
37
68
|
rescue LoadError
|
|
38
69
|
false
|
|
39
70
|
end
|
|
40
71
|
end
|
|
72
|
+
|
|
73
|
+
# Reset the load state (primarily for testing)
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
# @api private
|
|
77
|
+
def reset!
|
|
78
|
+
@ffi_gem_available = nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get capabilities supported by this backend
|
|
82
|
+
#
|
|
83
|
+
# @return [Hash{Symbol => Object}] capability map
|
|
84
|
+
# @example
|
|
85
|
+
# TreeHaver::Backends::FFI.capabilities
|
|
86
|
+
# # => { backend: :ffi, parse: true, query: false, bytes_field: true }
|
|
87
|
+
def capabilities
|
|
88
|
+
return {} unless available?
|
|
89
|
+
{
|
|
90
|
+
backend: :ffi,
|
|
91
|
+
parse: true,
|
|
92
|
+
query: false, # Query API not yet implemented in FFI backend
|
|
93
|
+
bytes_field: true,
|
|
94
|
+
incremental: false,
|
|
95
|
+
}
|
|
96
|
+
end
|
|
41
97
|
end
|
|
42
98
|
|
|
43
99
|
# Native FFI bindings to libtree-sitter
|
|
@@ -151,15 +207,16 @@ module TreeHaver
|
|
|
151
207
|
|
|
152
208
|
last_error = nil
|
|
153
209
|
candidates = lib_candidates
|
|
210
|
+
lib_loaded = false
|
|
154
211
|
candidates.each do |name|
|
|
155
212
|
ffi_lib(name)
|
|
156
|
-
|
|
213
|
+
lib_loaded = true
|
|
157
214
|
break
|
|
158
215
|
rescue ::FFI::NotFoundError, LoadError => e
|
|
159
216
|
last_error = e
|
|
160
217
|
end
|
|
161
218
|
|
|
162
|
-
unless
|
|
219
|
+
unless lib_loaded
|
|
163
220
|
# :nocov:
|
|
164
221
|
tried = candidates.join(", ")
|
|
165
222
|
env_hint = ENV["TREE_SITTER_RUNTIME_LIB"] ? " TREE_SITTER_RUNTIME_LIB=#{ENV["TREE_SITTER_RUNTIME_LIB"]}." : ""
|
|
@@ -173,6 +230,8 @@ module TreeHaver
|
|
|
173
230
|
end
|
|
174
231
|
|
|
175
232
|
# Attach functions after lib is selected
|
|
233
|
+
# Note: TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types,
|
|
234
|
+
# so these attach_function calls will fail on TruffleRuby.
|
|
176
235
|
attach_function(:ts_parser_new, [], :pointer)
|
|
177
236
|
attach_function(:ts_parser_delete, [:pointer], :void)
|
|
178
237
|
attach_function(:ts_parser_set_language, [:pointer, :pointer], :bool)
|
|
@@ -190,6 +249,9 @@ module TreeHaver
|
|
|
190
249
|
attach_function(:ts_node_end_point, [:ts_node], :ts_point)
|
|
191
250
|
attach_function(:ts_node_is_null, [:ts_node], :bool)
|
|
192
251
|
attach_function(:ts_node_is_named, [:ts_node], :bool)
|
|
252
|
+
|
|
253
|
+
# Only mark as fully loaded after all attach_function calls succeed
|
|
254
|
+
@loaded = true
|
|
193
255
|
end
|
|
194
256
|
|
|
195
257
|
def loaded?
|
|
@@ -198,57 +260,6 @@ module TreeHaver
|
|
|
198
260
|
end
|
|
199
261
|
end
|
|
200
262
|
|
|
201
|
-
class << self
|
|
202
|
-
# Check if the FFI backend is available
|
|
203
|
-
#
|
|
204
|
-
# Returns true if:
|
|
205
|
-
# 1. The `ffi` gem is present
|
|
206
|
-
# 2. MRI backend (ruby_tree_sitter) has NOT been loaded
|
|
207
|
-
#
|
|
208
|
-
# FFI and MRI backends conflict at the libtree-sitter level.
|
|
209
|
-
# Once MRI loads, using FFI will cause segfaults.
|
|
210
|
-
#
|
|
211
|
-
# @return [Boolean] true if FFI backend can be used
|
|
212
|
-
# @example
|
|
213
|
-
# if TreeHaver::Backends::FFI.available?
|
|
214
|
-
# puts "FFI backend is ready"
|
|
215
|
-
# end
|
|
216
|
-
def available?
|
|
217
|
-
return false unless TreeHaver::Backends::FFI.ffi_gem_available?
|
|
218
|
-
|
|
219
|
-
# Check if MRI backend has been loaded (which blocks FFI)
|
|
220
|
-
!defined?(::TreeSitter::Parser)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# Reset the load state (primarily for testing)
|
|
224
|
-
#
|
|
225
|
-
# Note: FFI backend doesn't maintain load state like other backends,
|
|
226
|
-
# but this method is provided for API consistency.
|
|
227
|
-
#
|
|
228
|
-
# @return [void]
|
|
229
|
-
# @api private
|
|
230
|
-
def reset!
|
|
231
|
-
# FFI backend uses constant-time availability check, no state to reset
|
|
232
|
-
nil
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Get capabilities supported by this backend
|
|
236
|
-
#
|
|
237
|
-
# @return [Hash{Symbol => Object}] capability map
|
|
238
|
-
# @example
|
|
239
|
-
# TreeHaver::Backends::FFI.capabilities
|
|
240
|
-
# # => { backend: :ffi, parse: true, query: false, bytes_field: true }
|
|
241
|
-
def capabilities
|
|
242
|
-
return {} unless available?
|
|
243
|
-
{
|
|
244
|
-
backend: :ffi,
|
|
245
|
-
parse: true,
|
|
246
|
-
query: false,
|
|
247
|
-
bytes_field: true,
|
|
248
|
-
}
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
263
|
# Represents a tree-sitter language loaded via FFI
|
|
253
264
|
#
|
|
254
265
|
# Holds a pointer to a TSLanguage struct from a loaded shared library.
|
|
@@ -378,7 +389,8 @@ module TreeHaver
|
|
|
378
389
|
end
|
|
379
390
|
|
|
380
391
|
dl = ::FFI::DynamicLibrary.open(path, flags)
|
|
381
|
-
rescue LoadError => e
|
|
392
|
+
rescue LoadError, RuntimeError => e
|
|
393
|
+
# TruffleRuby raises RuntimeError instead of LoadError when a shared library cannot be opened
|
|
382
394
|
raise TreeHaver::NotAvailable, "Could not open language library at #{path}: #{e.message}"
|
|
383
395
|
end
|
|
384
396
|
|
|
@@ -13,6 +13,12 @@ module TreeHaver
|
|
|
13
13
|
# - The Query API for pattern matching
|
|
14
14
|
# - Tree editing for incremental re-parsing
|
|
15
15
|
#
|
|
16
|
+
# == Platform Compatibility
|
|
17
|
+
#
|
|
18
|
+
# - MRI Ruby: ✗ Not available (no JVM)
|
|
19
|
+
# - JRuby: ✓ Full support (native Java integration)
|
|
20
|
+
# - TruffleRuby: ✗ Not available (java-tree-sitter requires JRuby's Java interop)
|
|
21
|
+
#
|
|
16
22
|
# == Installation
|
|
17
23
|
#
|
|
18
24
|
# 1. Download the JAR from Maven Central:
|
|
@@ -24,7 +30,6 @@ module TreeHaver
|
|
|
24
30
|
# 3. Use JRuby to run your code:
|
|
25
31
|
# jruby -e "require 'tree_haver'; puts TreeHaver::Backends::Java.available?"
|
|
26
32
|
#
|
|
27
|
-
# @note Only available on JRuby
|
|
28
33
|
# @see https://github.com/tree-sitter/java-tree-sitter source
|
|
29
34
|
# @see https://tree-sitter.github.io/java-tree-sitter java-tree-sitter documentation
|
|
30
35
|
# @see https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter Maven Central
|
|
@@ -8,7 +8,12 @@ module TreeHaver
|
|
|
8
8
|
# for MRI Ruby. It provides the most feature-complete tree-sitter integration
|
|
9
9
|
# on MRI, including support for the Query API.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
11
|
+
# == Platform Compatibility
|
|
12
|
+
#
|
|
13
|
+
# - MRI Ruby: ✓ Full support (fastest tree-sitter backend on MRI)
|
|
14
|
+
# - JRuby: ✗ Cannot load native C extensions (runs on JVM)
|
|
15
|
+
# - TruffleRuby: ✗ C extension not compatible with TruffleRuby
|
|
16
|
+
#
|
|
12
17
|
# @see https://github.com/Faveod/ruby-tree-sitter ruby_tree_sitter
|
|
13
18
|
module MRI
|
|
14
19
|
@load_attempted = false
|
|
@@ -37,6 +42,14 @@ module TreeHaver
|
|
|
37
42
|
def available?
|
|
38
43
|
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
39
44
|
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
45
|
+
|
|
46
|
+
# ruby_tree_sitter is a C extension that only works on MRI
|
|
47
|
+
# It doesn't work on JRuby or TruffleRuby
|
|
48
|
+
unless RUBY_ENGINE == "ruby"
|
|
49
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
50
|
+
return @loaded
|
|
51
|
+
end
|
|
52
|
+
|
|
40
53
|
begin
|
|
41
54
|
require "tree_sitter" # Note: gem is ruby_tree_sitter but requires tree_sitter
|
|
42
55
|
|
|
@@ -11,7 +11,12 @@ module TreeHaver
|
|
|
11
11
|
# tree_stump supports incremental parsing and the Query API, making it
|
|
12
12
|
# suitable for editor/IDE use cases where performance is critical.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# == Platform Compatibility
|
|
15
|
+
#
|
|
16
|
+
# - MRI Ruby: ✓ Full support
|
|
17
|
+
# - JRuby: ✗ Cannot load native extensions (runs on JVM)
|
|
18
|
+
# - TruffleRuby: ✗ magnus/rb-sys incompatible with TruffleRuby's C API emulation
|
|
19
|
+
#
|
|
15
20
|
# @see https://github.com/joker1007/tree_stump tree_stump
|
|
16
21
|
module Rust
|
|
17
22
|
@load_attempted = false
|
|
@@ -30,6 +35,14 @@ module TreeHaver
|
|
|
30
35
|
def available?
|
|
31
36
|
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
32
37
|
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
38
|
+
|
|
39
|
+
# tree_stump uses magnus which requires MRI's C API
|
|
40
|
+
# It doesn't work on JRuby or TruffleRuby
|
|
41
|
+
unless RUBY_ENGINE == "ruby"
|
|
42
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
43
|
+
return @loaded
|
|
44
|
+
end
|
|
45
|
+
|
|
33
46
|
begin
|
|
34
47
|
require "tree_stump"
|
|
35
48
|
|
|
@@ -46,12 +46,12 @@ module TreeHaver
|
|
|
46
46
|
# @param language [Symbol, String] the language name (e.g., :toml, :json)
|
|
47
47
|
# @param gem_name [String] the gem name (e.g., "toml-rb")
|
|
48
48
|
# @param grammar_const [String] constant path to grammar (e.g., "TomlRB::Document")
|
|
49
|
-
# @param require_path [String, nil] custom require path (defaults to gem_name
|
|
49
|
+
# @param require_path [String, nil] custom require path (defaults to gem_name as-is)
|
|
50
50
|
def initialize(language:, gem_name:, grammar_const:, require_path: nil)
|
|
51
51
|
@language_name = language.to_sym
|
|
52
52
|
@gem_name = gem_name
|
|
53
53
|
@grammar_const = grammar_const
|
|
54
|
-
@require_path = require_path || gem_name
|
|
54
|
+
@require_path = require_path || gem_name
|
|
55
55
|
@load_attempted = false
|
|
56
56
|
@available = false
|
|
57
57
|
@grammar_module = nil
|
|
@@ -67,6 +67,15 @@ module TreeHaver
|
|
|
67
67
|
return @available if @load_attempted
|
|
68
68
|
|
|
69
69
|
@load_attempted = true
|
|
70
|
+
debug = ENV["TREE_HAVER_DEBUG"]
|
|
71
|
+
|
|
72
|
+
# Guard against nil require_path (can happen if gem_name was nil)
|
|
73
|
+
if @require_path.nil? || @require_path.empty?
|
|
74
|
+
warn("CitrusGrammarFinder: require_path is nil or empty for #{@language_name}") if debug
|
|
75
|
+
@available = false
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
70
79
|
begin
|
|
71
80
|
# Try to require the gem
|
|
72
81
|
require @require_path
|
|
@@ -76,25 +85,64 @@ module TreeHaver
|
|
|
76
85
|
|
|
77
86
|
# Verify it responds to parse
|
|
78
87
|
unless @grammar_module.respond_to?(:parse)
|
|
79
|
-
|
|
88
|
+
# :nocov: defensive - requires a gem with malformed grammar module
|
|
89
|
+
# Show what methods ARE available to help diagnose the issue
|
|
90
|
+
if debug
|
|
91
|
+
available_methods = @grammar_module.methods(false).sort.first(20)
|
|
92
|
+
warn("CitrusGrammarFinder: #{@grammar_const} doesn't respond to :parse")
|
|
93
|
+
warn("CitrusGrammarFinder: #{@grammar_const}.class = #{@grammar_module.class}")
|
|
94
|
+
warn("CitrusGrammarFinder: #{@grammar_const} is a #{@grammar_module.is_a?(Module) ? "Module" : "non-Module"}")
|
|
95
|
+
warn("CitrusGrammarFinder: Available singleton methods (first 20): #{available_methods.inspect}")
|
|
96
|
+
if @grammar_module.respond_to?(:instance_methods)
|
|
97
|
+
instance_methods = @grammar_module.instance_methods(false).sort.first(20)
|
|
98
|
+
warn("CitrusGrammarFinder: Available instance methods (first 20): #{instance_methods.inspect}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
80
101
|
@available = false
|
|
81
102
|
return false
|
|
103
|
+
# :nocov:
|
|
82
104
|
end
|
|
83
105
|
|
|
84
106
|
@available = true
|
|
85
107
|
rescue LoadError => e
|
|
86
|
-
#
|
|
87
|
-
|
|
108
|
+
# :nocov: defensive - requires gem to not be installed
|
|
109
|
+
# Only show LoadError details when debugging
|
|
110
|
+
if debug
|
|
111
|
+
warn("CitrusGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
|
|
112
|
+
warn("CitrusGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
113
|
+
end
|
|
88
114
|
@available = false
|
|
115
|
+
# :nocov:
|
|
89
116
|
rescue NameError => e
|
|
90
|
-
#
|
|
91
|
-
|
|
117
|
+
# :nocov: defensive - requires gem with missing constant
|
|
118
|
+
# Only show NameError details when debugging
|
|
119
|
+
if debug
|
|
120
|
+
warn("CitrusGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
|
|
121
|
+
warn("CitrusGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
122
|
+
end
|
|
92
123
|
@available = false
|
|
124
|
+
# :nocov:
|
|
125
|
+
rescue TypeError => e
|
|
126
|
+
# :nocov: defensive - TruffleRuby-specific edge case
|
|
127
|
+
# TruffleRuby's bundled_gems.rb can raise TypeError when File.path is called on nil
|
|
128
|
+
# This happens in bundled_gems.rb:124 warning? method when caller locations return nil
|
|
129
|
+
# Always warn about TypeError as it indicates a platform-specific issue
|
|
130
|
+
warn("CitrusGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
|
|
131
|
+
warn("CitrusGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue")
|
|
132
|
+
if debug
|
|
133
|
+
warn("CitrusGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
134
|
+
end
|
|
135
|
+
@available = false
|
|
136
|
+
# :nocov:
|
|
93
137
|
rescue => e
|
|
94
|
-
#
|
|
138
|
+
# :nocov: defensive - catch-all for unexpected errors
|
|
139
|
+
# Always warn about unexpected errors
|
|
95
140
|
warn("CitrusGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
|
|
96
|
-
|
|
141
|
+
if debug
|
|
142
|
+
warn("CitrusGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
143
|
+
end
|
|
97
144
|
@available = false
|
|
145
|
+
# :nocov:
|
|
98
146
|
end
|
|
99
147
|
|
|
100
148
|
@available
|
data/lib/tree_haver/node.rb
CHANGED
|
@@ -290,11 +290,14 @@ module TreeHaver
|
|
|
290
290
|
# Get a child by index
|
|
291
291
|
#
|
|
292
292
|
# @param index [Integer] Child index
|
|
293
|
-
# @return [Node, nil] Wrapped child node
|
|
293
|
+
# @return [Node, nil] Wrapped child node, or nil if index out of bounds
|
|
294
294
|
def child(index)
|
|
295
295
|
child_node = @inner_node.child(index)
|
|
296
296
|
return if child_node.nil?
|
|
297
297
|
Node.new(child_node, source: @source)
|
|
298
|
+
rescue IndexError
|
|
299
|
+
# Some backends (e.g., MRI w/ ruby_tree_sitter) raise IndexError for out of bounds
|
|
300
|
+
nil
|
|
298
301
|
end
|
|
299
302
|
|
|
300
303
|
# Get a named child by index
|