tree_haver 3.1.0 → 3.1.2
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 +78 -1
- data/README.md +187 -26
- data/lib/tree_haver/backends/citrus.rb +3 -0
- data/lib/tree_haver/backends/commonmarker.rb +2 -1
- data/lib/tree_haver/backends/ffi.rb +51 -10
- data/lib/tree_haver/backends/java.rb +2 -1
- data/lib/tree_haver/backends/markly.rb +2 -1
- data/lib/tree_haver/backends/mri.rb +10 -0
- data/lib/tree_haver/backends/prism.rb +11 -10
- data/lib/tree_haver/backends/psych.rb +25 -0
- data/lib/tree_haver/backends/rust.rb +1 -1
- data/lib/tree_haver/grammar_finder.rb +24 -2
- data/lib/tree_haver/language_registry.rb +6 -0
- data/lib/tree_haver/node.rb +1 -34
- data/lib/tree_haver/point.rb +65 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +744 -0
- data/lib/tree_haver/rspec.rb +23 -0
- data/lib/tree_haver/tree.rb +1 -1
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +108 -0
- data.tar.gz.sig +0 -0
- metadata +28 -5
- metadata.gz.sig +3 -2
|
@@ -319,6 +319,8 @@ module TreeHaver
|
|
|
319
319
|
#
|
|
320
320
|
# @api private
|
|
321
321
|
class Node
|
|
322
|
+
include Enumerable
|
|
323
|
+
|
|
322
324
|
# @return [::Prism::Node] the underlying Prism node
|
|
323
325
|
attr_reader :inner_node
|
|
324
326
|
|
|
@@ -553,27 +555,26 @@ module TreeHaver
|
|
|
553
555
|
|
|
554
556
|
# Get the parent node
|
|
555
557
|
#
|
|
556
|
-
# @
|
|
557
|
-
#
|
|
558
|
-
# @return [nil]
|
|
558
|
+
# @raise [NotImplementedError] Prism nodes don't have parent references
|
|
559
|
+
# @return [void]
|
|
559
560
|
def parent
|
|
560
|
-
|
|
561
|
+
raise NotImplementedError, "Prism backend does not support parent navigation"
|
|
561
562
|
end
|
|
562
563
|
|
|
563
564
|
# Get next sibling
|
|
564
565
|
#
|
|
565
|
-
# @
|
|
566
|
-
# @return [
|
|
566
|
+
# @raise [NotImplementedError] Prism nodes don't have sibling references
|
|
567
|
+
# @return [void]
|
|
567
568
|
def next_sibling
|
|
568
|
-
|
|
569
|
+
raise NotImplementedError, "Prism backend does not support sibling navigation"
|
|
569
570
|
end
|
|
570
571
|
|
|
571
572
|
# Get previous sibling
|
|
572
573
|
#
|
|
573
|
-
# @
|
|
574
|
-
# @return [
|
|
574
|
+
# @raise [NotImplementedError] Prism nodes don't have sibling references
|
|
575
|
+
# @return [void]
|
|
575
576
|
def prev_sibling
|
|
576
|
-
|
|
577
|
+
raise NotImplementedError, "Prism backend does not support sibling navigation"
|
|
577
578
|
end
|
|
578
579
|
|
|
579
580
|
# String representation for debugging
|
|
@@ -231,6 +231,7 @@ module TreeHaver
|
|
|
231
231
|
# - Alias: YAML anchor reference
|
|
232
232
|
class Node
|
|
233
233
|
include Comparable
|
|
234
|
+
include Enumerable
|
|
234
235
|
|
|
235
236
|
# @return [::Psych::Nodes::Node] The underlying Psych node
|
|
236
237
|
attr_reader :inner_node
|
|
@@ -446,6 +447,30 @@ module TreeHaver
|
|
|
446
447
|
"#<TreeHaver::Backends::Psych::Node type=#{type} children=#{child_count}>"
|
|
447
448
|
end
|
|
448
449
|
|
|
450
|
+
# Get the next sibling
|
|
451
|
+
#
|
|
452
|
+
# @raise [NotImplementedError] Psych nodes don't have sibling references
|
|
453
|
+
# @return [void]
|
|
454
|
+
def next_sibling
|
|
455
|
+
raise NotImplementedError, "Psych backend does not support sibling navigation"
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Get the previous sibling
|
|
459
|
+
#
|
|
460
|
+
# @raise [NotImplementedError] Psych nodes don't have sibling references
|
|
461
|
+
# @return [void]
|
|
462
|
+
def prev_sibling
|
|
463
|
+
raise NotImplementedError, "Psych backend does not support sibling navigation"
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Get the parent node
|
|
467
|
+
#
|
|
468
|
+
# @raise [NotImplementedError] Psych nodes don't have parent references
|
|
469
|
+
# @return [void]
|
|
470
|
+
def parent
|
|
471
|
+
raise NotImplementedError, "Psych backend does not support parent navigation"
|
|
472
|
+
end
|
|
473
|
+
|
|
449
474
|
# Psych-specific: Get the anchor name for Alias/anchored nodes
|
|
450
475
|
#
|
|
451
476
|
# @return [String, nil] Anchor name
|
|
@@ -12,7 +12,7 @@ module TreeHaver
|
|
|
12
12
|
# suitable for editor/IDE use cases where performance is critical.
|
|
13
13
|
#
|
|
14
14
|
# @note This backend works on MRI Ruby. JRuby/TruffleRuby support is unknown.
|
|
15
|
-
# @see https://github.com/
|
|
15
|
+
# @see https://github.com/joker1007/tree_stump tree_stump
|
|
16
16
|
module Rust
|
|
17
17
|
@load_attempted = false
|
|
18
18
|
@loaded = false
|
|
@@ -135,12 +135,14 @@ module TreeHaver
|
|
|
135
135
|
# 3. Common system installation paths
|
|
136
136
|
#
|
|
137
137
|
# @note Paths from ENV are validated using {PathValidator.safe_library_path?}
|
|
138
|
-
# to prevent path traversal and other attacks. Invalid ENV paths
|
|
138
|
+
# to prevent path traversal and other attacks. Invalid ENV paths cause
|
|
139
|
+
# an error to be raised (Principle of Least Surprise - explicit paths must work).
|
|
139
140
|
#
|
|
140
141
|
# @note Setting the ENV variable to an empty string explicitly disables
|
|
141
142
|
# this grammar. This allows fallback to alternative backends (e.g., Citrus).
|
|
142
143
|
#
|
|
143
144
|
# @return [String, nil] the path to the library, or nil if not found
|
|
145
|
+
# @raise [TreeHaver::NotAvailable] if ENV variable is set to an invalid path
|
|
144
146
|
# @see #find_library_path_safe For stricter validation (trusted directories only)
|
|
145
147
|
def find_library_path
|
|
146
148
|
# Check environment variable first (highest priority)
|
|
@@ -148,6 +150,13 @@ module TreeHaver
|
|
|
148
150
|
if ENV.key?(env_var_name)
|
|
149
151
|
env_path = ENV[env_var_name]
|
|
150
152
|
|
|
153
|
+
# :nocov: defensive - ENV.key? true with nil value is rare edge case
|
|
154
|
+
if env_path.nil?
|
|
155
|
+
@env_rejection_reason = "explicitly disabled (set to nil)"
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
# :nocov:
|
|
159
|
+
|
|
151
160
|
# Empty string means "explicitly skip this grammar"
|
|
152
161
|
# This allows users to disable tree-sitter for specific languages
|
|
153
162
|
# and fall back to alternative backends like Citrus
|
|
@@ -158,7 +167,17 @@ module TreeHaver
|
|
|
158
167
|
|
|
159
168
|
# Store why env path was rejected for better error messages
|
|
160
169
|
@env_rejection_reason = validate_env_path(env_path)
|
|
161
|
-
|
|
170
|
+
|
|
171
|
+
# Principle of Least Surprise: If user explicitly sets an ENV variable
|
|
172
|
+
# to a path, that path MUST work. Don't silently fall back to auto-discovery.
|
|
173
|
+
if @env_rejection_reason
|
|
174
|
+
raise TreeHaver::NotAvailable,
|
|
175
|
+
"#{env_var_name} is set to #{env_path.inspect} but #{@env_rejection_reason}. " \
|
|
176
|
+
"Either fix the path, unset the variable to use auto-discovery, " \
|
|
177
|
+
"or set it to empty string to explicitly disable this grammar."
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
return env_path
|
|
162
181
|
end
|
|
163
182
|
|
|
164
183
|
# Search all paths (these are constructed from trusted base dirs)
|
|
@@ -321,6 +340,9 @@ module TreeHaver
|
|
|
321
340
|
env_value = ENV[env_var_name]
|
|
322
341
|
msg += if env_value && @env_rejection_reason
|
|
323
342
|
" #{env_var_name} is set to #{env_value.inspect} but #{@env_rejection_reason}."
|
|
343
|
+
elsif env_value && File.exist?(env_value) && !self.class.tree_sitter_runtime_usable?
|
|
344
|
+
" #{env_var_name} is set and file exists, but no tree-sitter runtime is available. " \
|
|
345
|
+
"Add ruby_tree_sitter, ffi, or tree_stump gem to your Gemfile."
|
|
324
346
|
elsif env_value
|
|
325
347
|
" #{env_var_name} is set but was not used (file may have been removed)."
|
|
326
348
|
else
|
|
@@ -11,20 +11,26 @@ module TreeHaver
|
|
|
11
11
|
# switching, benchmarking, and fallback scenarios.
|
|
12
12
|
#
|
|
13
13
|
# Registration structure:
|
|
14
|
+
# ```ruby
|
|
14
15
|
# @registrations = {
|
|
15
16
|
# toml: {
|
|
16
17
|
# tree_sitter: { path: "/path/to/lib.so", symbol: "tree_sitter_toml" },
|
|
17
18
|
# citrus: { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
|
|
18
19
|
# }
|
|
19
20
|
# }
|
|
21
|
+
# ```
|
|
20
22
|
#
|
|
21
23
|
# @example Register tree-sitter grammar
|
|
24
|
+
# ```ruby
|
|
22
25
|
# TreeHaver::LanguageRegistry.register(:toml, :tree_sitter,
|
|
23
26
|
# path: "/path/to/lib.so", symbol: "tree_sitter_toml")
|
|
27
|
+
# ```
|
|
24
28
|
#
|
|
25
29
|
# @example Register Citrus grammar
|
|
30
|
+
# ```ruby
|
|
26
31
|
# TreeHaver::LanguageRegistry.register(:toml, :citrus,
|
|
27
32
|
# grammar_module: TomlRB::Document, gem_name: "toml-rb")
|
|
33
|
+
# ```
|
|
28
34
|
#
|
|
29
35
|
# @api private
|
|
30
36
|
module LanguageRegistry
|
data/lib/tree_haver/node.rb
CHANGED
|
@@ -1,40 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module TreeHaver
|
|
4
|
-
# Point class that works as both a Hash and an object with row/column accessors
|
|
5
|
-
#
|
|
6
|
-
# This provides compatibility with code expecting either:
|
|
7
|
-
# - Hash access: point[:row], point[:column]
|
|
8
|
-
# - Method access: point.row, point.column
|
|
9
|
-
class Point
|
|
10
|
-
attr_reader :row, :column
|
|
11
|
-
|
|
12
|
-
def initialize(row, column)
|
|
13
|
-
@row = row
|
|
14
|
-
@column = column
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Hash-like access for compatibility
|
|
18
|
-
def [](key)
|
|
19
|
-
case key
|
|
20
|
-
when :row, "row" then @row
|
|
21
|
-
when :column, "column" then @column
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def to_h
|
|
26
|
-
{row: @row, column: @column}
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def to_s
|
|
30
|
-
"(#{@row}, #{@column})"
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def inspect
|
|
34
|
-
"#<TreeHaver::Point row=#{@row} column=#{@column}>"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
4
|
# Unified Node wrapper providing a consistent API across all backends
|
|
39
5
|
#
|
|
40
6
|
# This class wraps backend-specific node objects (TreeSitter::Node, TreeStump::Node, etc.)
|
|
@@ -95,6 +61,7 @@ module TreeHaver
|
|
|
95
61
|
# @note This is the key to tree_haver's "write once, run anywhere" promise
|
|
96
62
|
class Node
|
|
97
63
|
include Comparable
|
|
64
|
+
include Enumerable
|
|
98
65
|
|
|
99
66
|
# The wrapped backend-specific node object
|
|
100
67
|
#
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
# Point class that works as both a Hash and an object with row/column accessors
|
|
5
|
+
#
|
|
6
|
+
# This provides compatibility with code expecting either:
|
|
7
|
+
# - Hash access: point[:row], point[:column]
|
|
8
|
+
# - Method access: point.row, point.column
|
|
9
|
+
#
|
|
10
|
+
# @example Method access
|
|
11
|
+
# point = TreeHaver::Point.new(5, 10)
|
|
12
|
+
# point.row # => 5
|
|
13
|
+
# point.column # => 10
|
|
14
|
+
#
|
|
15
|
+
# @example Hash-like access
|
|
16
|
+
# point[:row] # => 5
|
|
17
|
+
# point[:column] # => 10
|
|
18
|
+
#
|
|
19
|
+
# @example Converting to hash
|
|
20
|
+
# point.to_h # => {row: 5, column: 10}
|
|
21
|
+
class Point
|
|
22
|
+
attr_reader :row, :column
|
|
23
|
+
|
|
24
|
+
# Create a new Point
|
|
25
|
+
#
|
|
26
|
+
# @param row [Integer] the row (line) number, 0-indexed
|
|
27
|
+
# @param column [Integer] the column number, 0-indexed
|
|
28
|
+
def initialize(row, column)
|
|
29
|
+
@row = row
|
|
30
|
+
@column = column
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Hash-like access for compatibility
|
|
34
|
+
#
|
|
35
|
+
# @param key [Symbol, String] :row or :column
|
|
36
|
+
# @return [Integer, nil] the value or nil if key not recognized
|
|
37
|
+
def [](key)
|
|
38
|
+
case key
|
|
39
|
+
when :row, "row" then @row
|
|
40
|
+
when :column, "column" then @column
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert to a hash
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash{Symbol => Integer}]
|
|
47
|
+
def to_h
|
|
48
|
+
{row: @row, column: @column}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# String representation
|
|
52
|
+
#
|
|
53
|
+
# @return [String]
|
|
54
|
+
def to_s
|
|
55
|
+
"(#{@row}, #{@column})"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Inspect representation
|
|
59
|
+
#
|
|
60
|
+
# @return [String]
|
|
61
|
+
def inspect
|
|
62
|
+
"#<TreeHaver::Point row=#{@row} column=#{@column}>"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|