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,217 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Ensure TreeHaver::Node and TreeHaver::Point are loaded
|
|
4
|
-
require "tree_haver"
|
|
5
|
-
|
|
6
|
-
module TreeHaver
|
|
7
|
-
module RSpec
|
|
8
|
-
# A mock inner node that provides the minimal interface TreeHaver::Node expects.
|
|
9
|
-
#
|
|
10
|
-
# This is what TreeHaver::Node wraps - it simulates the backend-specific node
|
|
11
|
-
# (like tree-sitter's Node, Markly::Node, etc.)
|
|
12
|
-
#
|
|
13
|
-
# @api private
|
|
14
|
-
class MockInnerNode
|
|
15
|
-
attr_reader :type, :start_byte, :end_byte, :children_data
|
|
16
|
-
|
|
17
|
-
def initialize(
|
|
18
|
-
type:,
|
|
19
|
-
text: nil,
|
|
20
|
-
start_byte: 0,
|
|
21
|
-
end_byte: nil,
|
|
22
|
-
start_row: 0,
|
|
23
|
-
start_column: 0,
|
|
24
|
-
end_row: nil,
|
|
25
|
-
end_column: nil,
|
|
26
|
-
children: []
|
|
27
|
-
)
|
|
28
|
-
@type = type.to_s
|
|
29
|
-
@text_content = text
|
|
30
|
-
@start_byte = start_byte
|
|
31
|
-
@end_byte = end_byte || (text ? start_byte + text.length : start_byte)
|
|
32
|
-
@start_row = start_row
|
|
33
|
-
@start_column = start_column
|
|
34
|
-
@end_row = end_row || start_row
|
|
35
|
-
@end_column = end_column || (text ? start_column + text.length : start_column)
|
|
36
|
-
@children_data = children
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def start_point
|
|
40
|
-
TreeHaver::Point.new(@start_row, @start_column)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def end_point
|
|
44
|
-
TreeHaver::Point.new(@end_row, @end_column)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def child_count
|
|
48
|
-
@children_data.length
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def child(index)
|
|
52
|
-
return if index.nil? || index < 0 || index >= @children_data.length
|
|
53
|
-
|
|
54
|
-
@children_data[index]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Return children array (for enumerable behavior)
|
|
58
|
-
def children
|
|
59
|
-
@children_data
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def first_child
|
|
63
|
-
@children_data.first
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def last_child
|
|
67
|
-
@children_data.last
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Iterate over children
|
|
71
|
-
def each(&block)
|
|
72
|
-
return enum_for(:each) unless block
|
|
73
|
-
|
|
74
|
-
@children_data.each(&block)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def named?
|
|
78
|
-
true
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Test nodes are always valid (no parse errors)
|
|
82
|
-
def has_error?
|
|
83
|
-
false
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Test nodes are never missing (not error recovery insertions)
|
|
87
|
-
def missing?
|
|
88
|
-
false
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Some backends provide text directly
|
|
92
|
-
def text
|
|
93
|
-
@text_content
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# For backends that use string_content (like Markly/Commonmarker)
|
|
97
|
-
def string_content
|
|
98
|
-
@text_content
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# A real TreeHaver::Node that wraps a MockInnerNode.
|
|
103
|
-
#
|
|
104
|
-
# This gives us full TreeHaver::Node behavior (#text, #type, #source_position, etc.)
|
|
105
|
-
# while allowing us to control the underlying data for testing.
|
|
106
|
-
#
|
|
107
|
-
# TestableNode is designed for testing code that works with TreeHaver nodes
|
|
108
|
-
# without requiring an actual parser backend. It creates real TreeHaver::Node
|
|
109
|
-
# instances with controlled, predictable data.
|
|
110
|
-
#
|
|
111
|
-
# @example Creating a testable node
|
|
112
|
-
# node = TreeHaver::RSpec::TestableNode.create(
|
|
113
|
-
# type: :heading,
|
|
114
|
-
# text: "## My Heading",
|
|
115
|
-
# start_line: 1
|
|
116
|
-
# )
|
|
117
|
-
# node.text # => "## My Heading"
|
|
118
|
-
# node.type # => "heading"
|
|
119
|
-
# node.start_line # => 1
|
|
120
|
-
#
|
|
121
|
-
# @example Creating with children
|
|
122
|
-
# parent = TreeHaver::RSpec::TestableNode.create(
|
|
123
|
-
# type: :document,
|
|
124
|
-
# text: "# Title\n\nParagraph",
|
|
125
|
-
# children: [
|
|
126
|
-
# { type: :heading, text: "# Title", start_line: 1 },
|
|
127
|
-
# { type: :paragraph, text: "Paragraph", start_line: 3 },
|
|
128
|
-
# ]
|
|
129
|
-
# )
|
|
130
|
-
#
|
|
131
|
-
# @example Using the convenience constant
|
|
132
|
-
# # After requiring tree_haver/rspec/testable_node, you can use:
|
|
133
|
-
# node = TestableNode.create(type: :paragraph, text: "Hello")
|
|
134
|
-
#
|
|
135
|
-
class TestableNode < TreeHaver::Node
|
|
136
|
-
class << self
|
|
137
|
-
# Create a TestableNode with the given attributes.
|
|
138
|
-
#
|
|
139
|
-
# @param type [Symbol, String] Node type (e.g., :heading, :paragraph)
|
|
140
|
-
# @param text [String] The text content of the node
|
|
141
|
-
# @param start_line [Integer] 1-based start line number (default: 1)
|
|
142
|
-
# @param end_line [Integer, nil] 1-based end line number (default: calculated from text)
|
|
143
|
-
# @param start_column [Integer] 0-based start column (default: 0)
|
|
144
|
-
# @param end_column [Integer, nil] 0-based end column (default: calculated from text)
|
|
145
|
-
# @param start_byte [Integer] Start byte offset (default: 0)
|
|
146
|
-
# @param end_byte [Integer, nil] End byte offset (default: calculated from text)
|
|
147
|
-
# @param children [Array<Hash>] Child node specifications
|
|
148
|
-
# @param source [String, nil] Full source text (default: uses text param)
|
|
149
|
-
# @return [TestableNode]
|
|
150
|
-
def create(
|
|
151
|
-
type:,
|
|
152
|
-
text: "",
|
|
153
|
-
start_line: 1,
|
|
154
|
-
end_line: nil,
|
|
155
|
-
start_column: 0,
|
|
156
|
-
end_column: nil,
|
|
157
|
-
start_byte: 0,
|
|
158
|
-
end_byte: nil,
|
|
159
|
-
children: [],
|
|
160
|
-
source: nil
|
|
161
|
-
)
|
|
162
|
-
# Convert 1-based line to 0-based row
|
|
163
|
-
start_row = start_line - 1
|
|
164
|
-
end_row = end_line ? end_line - 1 : start_row + text.count("\n")
|
|
165
|
-
|
|
166
|
-
# Calculate end_column if not provided
|
|
167
|
-
if end_column.nil?
|
|
168
|
-
lines = text.split("\n", -1)
|
|
169
|
-
end_column = lines.last&.length || 0
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Build children as MockInnerNodes
|
|
173
|
-
child_nodes = children.map do |child_spec|
|
|
174
|
-
MockInnerNode.new(**child_spec)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
inner = MockInnerNode.new(
|
|
178
|
-
type: type,
|
|
179
|
-
text: text,
|
|
180
|
-
start_byte: start_byte,
|
|
181
|
-
end_byte: end_byte,
|
|
182
|
-
start_row: start_row,
|
|
183
|
-
start_column: start_column,
|
|
184
|
-
end_row: end_row,
|
|
185
|
-
end_column: end_column,
|
|
186
|
-
children: child_nodes,
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# Create a real TreeHaver::Node wrapping our mock
|
|
190
|
-
# Pass source so TreeHaver::Node can extract text if needed
|
|
191
|
-
new(inner, source: source || text)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# Create multiple nodes from an array of specifications.
|
|
195
|
-
#
|
|
196
|
-
# @param specs [Array<Hash>] Array of node specifications
|
|
197
|
-
# @return [Array<TestableNode>]
|
|
198
|
-
def create_list(*specs)
|
|
199
|
-
specs.flatten.map { |spec| create(**spec) }
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# Additional test helper methods
|
|
204
|
-
|
|
205
|
-
# Check if this is a testable node (for test assertions)
|
|
206
|
-
#
|
|
207
|
-
# @return [Boolean] true
|
|
208
|
-
def testable?
|
|
209
|
-
true
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Make TestableNode available at top level for convenience in specs.
|
|
216
|
-
# This allows specs to use `TestableNode.create(...)` without the full namespace.
|
|
217
|
-
TestableNode = TreeHaver::RSpec::TestableNode
|
data/lib/tree_haver/rspec.rb
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# TreeHaver RSpec Integration
|
|
4
|
-
#
|
|
5
|
-
# This file provides RSpec helpers and configuration for testing
|
|
6
|
-
# code that uses TreeHaver. Require this in your spec_helper.rb:
|
|
7
|
-
#
|
|
8
|
-
# require "tree_haver/rspec"
|
|
9
|
-
#
|
|
10
|
-
# This will load:
|
|
11
|
-
# - Dependency tags for conditional test execution
|
|
12
|
-
# - TestableNode for creating mock nodes in tests
|
|
13
|
-
#
|
|
14
|
-
# @example spec_helper.rb
|
|
15
|
-
# require "tree_haver/rspec"
|
|
16
|
-
#
|
|
17
|
-
# RSpec.configure do |config|
|
|
18
|
-
# # Your additional configuration...
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# @example Using TestableNode
|
|
22
|
-
# node = TestableNode.create(
|
|
23
|
-
# type: :heading,
|
|
24
|
-
# text: "## My Heading",
|
|
25
|
-
# start_line: 1
|
|
26
|
-
# )
|
|
27
|
-
# expect(node.type).to eq("heading")
|
|
28
|
-
#
|
|
29
|
-
# @see TreeHaver::RSpec::DependencyTags
|
|
30
|
-
# @see TreeHaver::RSpec::TestableNode
|
|
31
|
-
|
|
32
|
-
require_relative "rspec/dependency_tags"
|
|
33
|
-
require_relative "rspec/testable_node"
|
data/lib/tree_haver/tree.rb
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module TreeHaver
|
|
4
|
-
# Unified Tree wrapper providing a consistent API across all backends
|
|
5
|
-
#
|
|
6
|
-
# This class wraps backend-specific tree objects and provides a unified interface.
|
|
7
|
-
# It stores the source text to enable text extraction from nodes.
|
|
8
|
-
#
|
|
9
|
-
# == Wrapping/Unwrapping Contract
|
|
10
|
-
#
|
|
11
|
-
# TreeHaver follows a consistent pattern for object wrapping:
|
|
12
|
-
#
|
|
13
|
-
# 1. **TreeHaver::Parser** (top level) handles ALL wrapping/unwrapping
|
|
14
|
-
# 2. **Backends** work exclusively with raw backend objects
|
|
15
|
-
# 3. **User-facing API** uses only TreeHaver wrapper classes
|
|
16
|
-
#
|
|
17
|
-
# Specifically for trees:
|
|
18
|
-
# - Backend Parser#parse returns raw backend tree (TreeSitter::Tree, TreeStump::Tree, etc.)
|
|
19
|
-
# - TreeHaver::Parser#parse wraps it in TreeHaver::Tree
|
|
20
|
-
# - TreeHaver::Parser#parse_string unwraps old_tree before passing to backend
|
|
21
|
-
# - Backend Parser#parse_string receives raw backend tree, returns raw backend tree
|
|
22
|
-
# - TreeHaver::Parser#parse_string wraps the returned tree
|
|
23
|
-
#
|
|
24
|
-
# This ensures:
|
|
25
|
-
# - Backends are simple and consistent
|
|
26
|
-
# - All complexity is in one place (TreeHaver top level)
|
|
27
|
-
# - Users always work with TreeHaver wrapper classes
|
|
28
|
-
#
|
|
29
|
-
# @example Basic usage
|
|
30
|
-
# parser = TreeHaver::Parser.new
|
|
31
|
-
# parser.language = TreeHaver::Language.toml
|
|
32
|
-
# tree = parser.parse(source)
|
|
33
|
-
# root = tree.root_node
|
|
34
|
-
# puts root.type
|
|
35
|
-
#
|
|
36
|
-
# @example Incremental parsing (if backend supports it)
|
|
37
|
-
# tree = parser.parse("x = 1")
|
|
38
|
-
# # Edit the source: "x = 1" → "x = 42"
|
|
39
|
-
# tree.edit(
|
|
40
|
-
# start_byte: 4,
|
|
41
|
-
# old_end_byte: 5,
|
|
42
|
-
# new_end_byte: 6,
|
|
43
|
-
# start_point: { row: 0, column: 4 },
|
|
44
|
-
# old_end_point: { row: 0, column: 5 },
|
|
45
|
-
# new_end_point: { row: 0, column: 6 }
|
|
46
|
-
# )
|
|
47
|
-
# new_tree = parser.parse_string(tree, "x = 42")
|
|
48
|
-
#
|
|
49
|
-
# @example Accessing backend-specific features
|
|
50
|
-
# # Via passthrough (method_missing delegates to inner_tree)
|
|
51
|
-
# tree.some_backend_specific_method # Automatically delegated
|
|
52
|
-
#
|
|
53
|
-
# # Or explicitly via inner_tree
|
|
54
|
-
# tree.inner_tree.some_backend_specific_method
|
|
55
|
-
class Tree < Base::Tree
|
|
56
|
-
# The wrapped backend-specific tree object
|
|
57
|
-
#
|
|
58
|
-
# This provides direct access to the underlying backend tree for advanced usage
|
|
59
|
-
# when you need backend-specific features not exposed by the unified API.
|
|
60
|
-
#
|
|
61
|
-
# @return [Object] The underlying tree (TreeSitter::Tree, TreeStump::Tree, etc.)
|
|
62
|
-
# @example Accessing backend-specific methods
|
|
63
|
-
# # Print DOT graph (TreeStump-specific)
|
|
64
|
-
# if tree.inner_tree.respond_to?(:print_dot_graph)
|
|
65
|
-
# File.open("tree.dot", "w") do |f|
|
|
66
|
-
# tree.inner_tree.print_dot_graph(f)
|
|
67
|
-
# end
|
|
68
|
-
# end
|
|
69
|
-
# NOTE: inner_tree is inherited from Base::Tree
|
|
70
|
-
|
|
71
|
-
# The source text
|
|
72
|
-
#
|
|
73
|
-
# Stored to enable text extraction from nodes via byte offsets.
|
|
74
|
-
#
|
|
75
|
-
# @return [String] The original source code
|
|
76
|
-
# NOTE: source is inherited from Base::Tree
|
|
77
|
-
|
|
78
|
-
# @param tree [Object] Backend-specific tree object
|
|
79
|
-
# @param source [String] Source text for node text extraction
|
|
80
|
-
def initialize(tree, source: nil)
|
|
81
|
-
super(tree, source: source)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Get the root node of the tree
|
|
85
|
-
#
|
|
86
|
-
# @return [Node] Wrapped root node
|
|
87
|
-
def root_node
|
|
88
|
-
root = @inner_tree.root_node
|
|
89
|
-
return if root.nil?
|
|
90
|
-
Node.new(root, source: @source)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Mark the tree as edited for incremental re-parsing
|
|
94
|
-
#
|
|
95
|
-
# Call this method after the source code has been modified but before
|
|
96
|
-
# re-parsing. This tells tree-sitter which parts of the tree are
|
|
97
|
-
# invalidated so it can efficiently re-parse only the affected regions.
|
|
98
|
-
#
|
|
99
|
-
# Not all backends support incremental parsing. Use {#supports_editing?}
|
|
100
|
-
# to check before calling this method.
|
|
101
|
-
#
|
|
102
|
-
# @param start_byte [Integer] byte offset where the edit starts
|
|
103
|
-
# @param old_end_byte [Integer] byte offset where the old text ended
|
|
104
|
-
# @param new_end_byte [Integer] byte offset where the new text ends
|
|
105
|
-
# @param start_point [Hash] starting position as `{ row:, column: }`
|
|
106
|
-
# @param old_end_point [Hash] old ending position as `{ row:, column: }`
|
|
107
|
-
# @param new_end_point [Hash] new ending position as `{ row:, column: }`
|
|
108
|
-
# @return [void]
|
|
109
|
-
# @raise [TreeHaver::NotAvailable] if the backend doesn't support incremental parsing
|
|
110
|
-
# @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing
|
|
111
|
-
#
|
|
112
|
-
# @example Incremental parsing workflow
|
|
113
|
-
# # Original source: "x = 1"
|
|
114
|
-
# tree = parser.parse("x = 1")
|
|
115
|
-
#
|
|
116
|
-
# # Edit the source: replace "1" with "42" at byte offset 4
|
|
117
|
-
# tree.edit(
|
|
118
|
-
# start_byte: 4,
|
|
119
|
-
# old_end_byte: 5, # "1" ends at byte 5
|
|
120
|
-
# new_end_byte: 6, # "42" ends at byte 6
|
|
121
|
-
# start_point: { row: 0, column: 4 },
|
|
122
|
-
# old_end_point: { row: 0, column: 5 },
|
|
123
|
-
# new_end_point: { row: 0, column: 6 }
|
|
124
|
-
# )
|
|
125
|
-
#
|
|
126
|
-
# # Re-parse with the edited tree for incremental parsing
|
|
127
|
-
# new_tree = parser.parse_string(tree, "x = 42")
|
|
128
|
-
def edit(start_byte:, old_end_byte:, new_end_byte:, start_point:, old_end_point:, new_end_point:)
|
|
129
|
-
# MRI backend (ruby_tree_sitter) requires an InputEdit object
|
|
130
|
-
if defined?(::TreeSitter::InputEdit) && @inner_tree.is_a?(::TreeSitter::Tree)
|
|
131
|
-
input_edit = ::TreeSitter::InputEdit.new
|
|
132
|
-
input_edit.start_byte = start_byte
|
|
133
|
-
input_edit.old_end_byte = old_end_byte
|
|
134
|
-
input_edit.new_end_byte = new_end_byte
|
|
135
|
-
|
|
136
|
-
# Convert hash points to Point objects if needed
|
|
137
|
-
input_edit.start_point = make_point(start_point)
|
|
138
|
-
input_edit.old_end_point = make_point(old_end_point)
|
|
139
|
-
input_edit.new_end_point = make_point(new_end_point)
|
|
140
|
-
|
|
141
|
-
@inner_tree.edit(input_edit)
|
|
142
|
-
else
|
|
143
|
-
# Other backends may accept keyword arguments directly
|
|
144
|
-
@inner_tree.edit(
|
|
145
|
-
start_byte: start_byte,
|
|
146
|
-
old_end_byte: old_end_byte,
|
|
147
|
-
new_end_byte: new_end_byte,
|
|
148
|
-
start_point: start_point,
|
|
149
|
-
old_end_point: old_end_point,
|
|
150
|
-
new_end_point: new_end_point,
|
|
151
|
-
)
|
|
152
|
-
end
|
|
153
|
-
rescue NoMethodError => e
|
|
154
|
-
# Re-raise as NotAvailable if it's about the edit method
|
|
155
|
-
raise unless e.name == :edit || e.message.include?("edit")
|
|
156
|
-
raise TreeHaver::NotAvailable,
|
|
157
|
-
"Incremental parsing not supported by current backend. " \
|
|
158
|
-
"Use MRI (ruby_tree_sitter), Rust (tree_stump), or Java (java-tree-sitter / jtreesitter) backend."
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
private
|
|
162
|
-
|
|
163
|
-
# Convert a point hash to a TreeSitter::Point if available
|
|
164
|
-
# @api private
|
|
165
|
-
def make_point(point_hash)
|
|
166
|
-
if defined?(::TreeSitter::Point)
|
|
167
|
-
pt = ::TreeSitter::Point.new
|
|
168
|
-
pt.row = point_hash[:row]
|
|
169
|
-
pt.column = point_hash[:column]
|
|
170
|
-
pt
|
|
171
|
-
else
|
|
172
|
-
point_hash
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
public
|
|
177
|
-
|
|
178
|
-
# Check if the current backend supports incremental parsing
|
|
179
|
-
#
|
|
180
|
-
# Incremental parsing allows tree-sitter to reuse unchanged nodes when
|
|
181
|
-
# re-parsing edited source code, improving performance for large files
|
|
182
|
-
# with small edits.
|
|
183
|
-
#
|
|
184
|
-
# @return [Boolean] true if {#edit} can be called on this tree
|
|
185
|
-
# @example
|
|
186
|
-
# if tree.supports_editing?
|
|
187
|
-
# tree.edit(...)
|
|
188
|
-
# new_tree = parser.parse_string(tree, edited_source)
|
|
189
|
-
# else
|
|
190
|
-
# # Fall back to full re-parse
|
|
191
|
-
# new_tree = parser.parse(edited_source)
|
|
192
|
-
# end
|
|
193
|
-
def supports_editing?
|
|
194
|
-
# Try to get the edit method to verify it exists
|
|
195
|
-
# This is more reliable than respond_to? with Delegator wrappers
|
|
196
|
-
@inner_tree.method(:edit)
|
|
197
|
-
true
|
|
198
|
-
rescue NameError
|
|
199
|
-
# NameError is the parent class of NoMethodError, so this catches both
|
|
200
|
-
false
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# String representation
|
|
204
|
-
# @return [String]
|
|
205
|
-
def inspect
|
|
206
|
-
inner_class = @inner_tree ? @inner_tree.class.name : "nil"
|
|
207
|
-
"#<#{self.class} source_length=#{@source&.bytesize || "unknown"} inner_tree=#{inner_class}>"
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# Check if tree responds to a method (includes delegation to inner_tree)
|
|
211
|
-
#
|
|
212
|
-
# @param method_name [Symbol] method to check
|
|
213
|
-
# @param include_private [Boolean] include private methods
|
|
214
|
-
# @return [Boolean]
|
|
215
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
216
|
-
@inner_tree.respond_to?(method_name, include_private) || super
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# Delegate unknown methods to the underlying backend-specific tree
|
|
220
|
-
#
|
|
221
|
-
# This provides passthrough access for advanced usage when you need
|
|
222
|
-
# backend-specific features not exposed by TreeHaver's unified API.
|
|
223
|
-
#
|
|
224
|
-
# The delegation is automatic and transparent - you can call backend-specific
|
|
225
|
-
# methods directly on the TreeHaver::Tree and they'll be forwarded to the
|
|
226
|
-
# underlying tree implementation.
|
|
227
|
-
#
|
|
228
|
-
# @param method_name [Symbol] method to call
|
|
229
|
-
# @param args [Array] arguments to pass
|
|
230
|
-
# @param block [Proc] block to pass
|
|
231
|
-
# @return [Object] result from the underlying tree
|
|
232
|
-
#
|
|
233
|
-
# @example Using TreeStump-specific methods
|
|
234
|
-
# # print_dot_graph is TreeStump-specific
|
|
235
|
-
# File.open("tree.dot", "w") do |f|
|
|
236
|
-
# tree.print_dot_graph(f) # Delegated to inner_tree
|
|
237
|
-
# end
|
|
238
|
-
#
|
|
239
|
-
# @example Safe usage with respond_to? check
|
|
240
|
-
# if tree.respond_to?(:print_dot_graph)
|
|
241
|
-
# File.open("tree.dot", "w") { |f| tree.print_dot_graph(f) }
|
|
242
|
-
# end
|
|
243
|
-
#
|
|
244
|
-
# @example Equivalent explicit access
|
|
245
|
-
# tree.print_dot_graph(file) # Via passthrough (method_missing)
|
|
246
|
-
# tree.inner_tree.print_dot_graph(file) # Explicit access (same result)
|
|
247
|
-
#
|
|
248
|
-
# @note This maintains backward compatibility with code written for
|
|
249
|
-
# specific backends while providing the benefits of the unified API
|
|
250
|
-
def method_missing(method_name, *args, **kwargs, &block)
|
|
251
|
-
if @inner_tree.respond_to?(method_name)
|
|
252
|
-
@inner_tree.public_send(method_name, *args, **kwargs, &block)
|
|
253
|
-
else
|
|
254
|
-
super
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
end
|