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.
@@ -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
- # @note Prism nodes don't have built-in parent references.
557
- # This always returns nil. Use tree traversal instead.
558
- # @return [nil]
558
+ # @raise [NotImplementedError] Prism nodes don't have parent references
559
+ # @return [void]
559
560
  def parent
560
- nil # Prism doesn't track parent references
561
+ raise NotImplementedError, "Prism backend does not support parent navigation"
561
562
  end
562
563
 
563
564
  # Get next sibling
564
565
  #
565
- # @note Prism nodes don't have sibling references.
566
- # @return [nil]
566
+ # @raise [NotImplementedError] Prism nodes don't have sibling references
567
+ # @return [void]
567
568
  def next_sibling
568
- nil
569
+ raise NotImplementedError, "Prism backend does not support sibling navigation"
569
570
  end
570
571
 
571
572
  # Get previous sibling
572
573
  #
573
- # @note Prism nodes don't have sibling references.
574
- # @return [nil]
574
+ # @raise [NotImplementedError] Prism nodes don't have sibling references
575
+ # @return [void]
575
576
  def prev_sibling
576
- nil
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/anthropics/tree_stump tree_stump
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 are ignored.
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
- return env_path if @env_rejection_reason.nil?
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
@@ -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