tree_haver 5.0.5 → 7.1.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/LICENSE.md +13 -0
- data/README.md +299 -660
- data/lib/tree_haver/backend_api.rb +78 -35
- data/lib/tree_haver/backend_context.rb +28 -0
- data/lib/tree_haver/backend_registry.rb +119 -382
- data/lib/tree_haver/backends/citrus.rb +55 -53
- data/lib/tree_haver/backends/ffi.rb +105 -101
- data/lib/tree_haver/backends/java.rb +92 -76
- data/lib/tree_haver/backends/mri.rb +44 -39
- data/lib/tree_haver/backends/parslet.rb +53 -48
- data/lib/tree_haver/backends/prism.rb +140 -43
- data/lib/tree_haver/backends/psych.rb +30 -26
- data/lib/tree_haver/backends/rust.rb +21 -17
- data/lib/tree_haver/backends/tslp.rb +274 -0
- data/lib/tree_haver/base/comment.rb +320 -0
- data/lib/tree_haver/base/language.rb +2 -2
- data/lib/tree_haver/base/node.rb +39 -31
- data/lib/tree_haver/base/parser.rb +4 -0
- data/lib/tree_haver/base/point.rb +3 -3
- data/lib/tree_haver/base/tree.rb +1 -1
- data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
- data/lib/tree_haver/contracts.rb +1025 -0
- data/lib/tree_haver/grammar_finder.rb +125 -70
- data/lib/tree_haver/kaitai_backend.rb +30 -0
- data/lib/tree_haver/language.rb +36 -37
- data/lib/tree_haver/language_pack.rb +41 -0
- data/lib/tree_haver/language_registry.rb +34 -3
- data/lib/tree_haver/library_path_utils.rb +7 -7
- data/lib/tree_haver/node.rb +40 -31
- data/lib/tree_haver/parser.rb +44 -37
- data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
- data/lib/tree_haver/path_validator.rb +39 -36
- data/lib/tree_haver/peg_backends.rb +76 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
- data/lib/tree_haver/rspec.rb +1 -31
- data/lib/tree_haver/tree.rb +16 -7
- data/lib/tree_haver/version.rb +5 -14
- data/lib/tree_haver.rb +536 -1240
- data/sig/tree_haver.rbs +1 -229
- data.tar.gz.sig +0 -0
- metadata +154 -70
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -1393
- 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/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/tree_haver/base.rb +0 -12
- data/lib/tree_haver/compat.rb +0 -43
- data/lib/tree_haver/rspec/testable_node.rb +0 -217
- 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
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
module Base
|
|
5
|
+
# Base class for backend comment wrappers.
|
|
6
|
+
#
|
|
7
|
+
# This defines the parser-facing contract for normalized comment wrappers in
|
|
8
|
+
# TreeHaver. Backends that can expose comment objects should subclass this and
|
|
9
|
+
# implement text/type/location accessors using their native parser data.
|
|
10
|
+
class Comment
|
|
11
|
+
ATTACHMENT_HINTS = %i[leading inline trailing].freeze
|
|
12
|
+
|
|
13
|
+
# The underlying backend-specific comment object.
|
|
14
|
+
# @return [Object]
|
|
15
|
+
attr_reader :inner_comment
|
|
16
|
+
|
|
17
|
+
# The source text used for fallback range extraction.
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
attr_reader :source
|
|
20
|
+
|
|
21
|
+
# Optional parser-provided attachment hint.
|
|
22
|
+
# @return [Symbol, nil]
|
|
23
|
+
attr_reader :attachment_hint
|
|
24
|
+
|
|
25
|
+
def initialize(comment, source: nil, attachment_hint: nil)
|
|
26
|
+
@inner_comment = comment
|
|
27
|
+
@source = source
|
|
28
|
+
@attachment_hint = normalize_attachment_hint(attachment_hint)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get the normalized comment type.
|
|
32
|
+
# Examples: "inline_comment", "block_comment".
|
|
33
|
+
# @return [String]
|
|
34
|
+
def type
|
|
35
|
+
raise NotImplementedError, "#{self.class}#type must be implemented"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get the comment text including delimiters when appropriate.
|
|
39
|
+
# @return [String]
|
|
40
|
+
def text
|
|
41
|
+
raise NotImplementedError, "#{self.class}#text must be implemented"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get the start byte offset of the comment.
|
|
45
|
+
# @return [Integer]
|
|
46
|
+
def start_byte
|
|
47
|
+
raise NotImplementedError, "#{self.class}#start_byte must be implemented"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get the end byte offset of the comment.
|
|
51
|
+
# @return [Integer]
|
|
52
|
+
def end_byte
|
|
53
|
+
raise NotImplementedError, "#{self.class}#end_byte must be implemented"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get the start position (row/column, 0-based).
|
|
57
|
+
# @return [Hash{Symbol => Integer}]
|
|
58
|
+
def start_point
|
|
59
|
+
{ row: 0, column: 0 }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get the end position (row/column, 0-based).
|
|
63
|
+
# @return [Hash{Symbol => Integer}]
|
|
64
|
+
def end_point
|
|
65
|
+
{ row: 0, column: 0 }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get the normalized delimiter style.
|
|
69
|
+
#
|
|
70
|
+
# @return [Symbol, nil]
|
|
71
|
+
def style
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get the opening delimiter for the comment, when the backend can provide it.
|
|
76
|
+
#
|
|
77
|
+
# Examples:
|
|
78
|
+
# - `#` for hash comments
|
|
79
|
+
# - `//` for C-style line comments
|
|
80
|
+
# - `<!--` for HTML/XML block comments
|
|
81
|
+
#
|
|
82
|
+
# @return [String, nil]
|
|
83
|
+
def opening_delimiter
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get the closing delimiter for the comment, when applicable.
|
|
88
|
+
#
|
|
89
|
+
# This is typically `nil` for line comments.
|
|
90
|
+
#
|
|
91
|
+
# @return [String, nil]
|
|
92
|
+
def closing_delimiter
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get the comment body with outer delimiters removed when possible.
|
|
97
|
+
#
|
|
98
|
+
# Backends with richer native comment models should override this when they
|
|
99
|
+
# can provide a more semantically accurate body than simple delimiter
|
|
100
|
+
# trimming.
|
|
101
|
+
#
|
|
102
|
+
# @return [String]
|
|
103
|
+
def body_text
|
|
104
|
+
extract_body_text(text.to_s)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Get a matching-friendly normalized comment body.
|
|
108
|
+
#
|
|
109
|
+
# This strips leading and trailing whitespace from {#body_text} while
|
|
110
|
+
# preserving the raw rendered form in {#text}.
|
|
111
|
+
#
|
|
112
|
+
# @return [String]
|
|
113
|
+
def normalized_text
|
|
114
|
+
body_text.strip
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get delimiter/body metadata in one normalized hash.
|
|
118
|
+
#
|
|
119
|
+
# @return [Hash{Symbol => String, nil}]
|
|
120
|
+
def delimiter_metadata
|
|
121
|
+
{
|
|
122
|
+
opening: opening_delimiter,
|
|
123
|
+
closing: closing_delimiter,
|
|
124
|
+
body: body_text
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Whether this comment uses a line-comment style.
|
|
129
|
+
#
|
|
130
|
+
# @return [Boolean]
|
|
131
|
+
def line?
|
|
132
|
+
style == :line
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Whether this comment uses a block-comment style.
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean]
|
|
138
|
+
def block?
|
|
139
|
+
style == :block
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Whether this comment spans multiple source lines.
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean]
|
|
145
|
+
def multiline?
|
|
146
|
+
start_line != end_line
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Whether this comment has a parser-provided attachment hint.
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
def attachment_hint?
|
|
153
|
+
!attachment_hint.nil?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Whether this comment is hinted as leading its owner.
|
|
157
|
+
#
|
|
158
|
+
# @return [Boolean]
|
|
159
|
+
def leading?
|
|
160
|
+
attachment_hint == :leading
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Whether this comment is hinted as inline with its owner.
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
def inline?
|
|
167
|
+
attachment_hint == :inline
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Whether this comment is hinted as trailing its owner.
|
|
171
|
+
#
|
|
172
|
+
# @return [Boolean]
|
|
173
|
+
def trailing?
|
|
174
|
+
attachment_hint == :trailing
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Get the 1-based start line.
|
|
178
|
+
#
|
|
179
|
+
# @return [Integer]
|
|
180
|
+
def start_line
|
|
181
|
+
start_point[:row] + 1
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Get the 1-based end line.
|
|
185
|
+
#
|
|
186
|
+
# @return [Integer]
|
|
187
|
+
def end_line
|
|
188
|
+
end_point[:row] + 1
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Get a normalized source-position hash.
|
|
192
|
+
#
|
|
193
|
+
# @return [Hash{Symbol => Integer}]
|
|
194
|
+
def source_position
|
|
195
|
+
{
|
|
196
|
+
start_line: start_line,
|
|
197
|
+
end_line: end_line,
|
|
198
|
+
start_column: start_point[:column],
|
|
199
|
+
end_column: end_point[:column]
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Whether this comment starts at the first line of the available source.
|
|
204
|
+
#
|
|
205
|
+
# @return [Boolean]
|
|
206
|
+
def at_file_start?
|
|
207
|
+
start_line == 1
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Whether this comment ends at the final line of the available source.
|
|
211
|
+
#
|
|
212
|
+
# @return [Boolean]
|
|
213
|
+
def at_file_end?
|
|
214
|
+
return false if source_lines.none?
|
|
215
|
+
|
|
216
|
+
end_line >= source_lines.length
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Return the contiguous blank source lines immediately before this comment.
|
|
220
|
+
#
|
|
221
|
+
# This is parser-facing layout metadata. It does not decide ownership; it
|
|
222
|
+
# simply exposes spacing adjacent to the comment so merge layers can reason
|
|
223
|
+
# about floating vs attached behavior without rescanning source text.
|
|
224
|
+
#
|
|
225
|
+
# @return [Array<String>]
|
|
226
|
+
def blank_lines_before
|
|
227
|
+
return [] if at_file_start?
|
|
228
|
+
|
|
229
|
+
line_num = start_line - 1
|
|
230
|
+
blanks = []
|
|
231
|
+
|
|
232
|
+
while line_num >= 1
|
|
233
|
+
line = source_line(line_num)
|
|
234
|
+
break unless line && line.strip.empty?
|
|
235
|
+
|
|
236
|
+
blanks.unshift(line)
|
|
237
|
+
line_num -= 1
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
blanks
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Return the contiguous blank source lines immediately after this comment.
|
|
244
|
+
#
|
|
245
|
+
# @return [Array<String>]
|
|
246
|
+
def blank_lines_after
|
|
247
|
+
return [] if at_file_end?
|
|
248
|
+
|
|
249
|
+
line_num = end_line + 1
|
|
250
|
+
blanks = []
|
|
251
|
+
|
|
252
|
+
while line_num <= source_lines.length
|
|
253
|
+
line = source_line(line_num)
|
|
254
|
+
break unless line && line.strip.empty?
|
|
255
|
+
|
|
256
|
+
blanks << line
|
|
257
|
+
line_num += 1
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
blanks
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Return the count of immediately preceding blank source lines.
|
|
264
|
+
#
|
|
265
|
+
# @return [Integer]
|
|
266
|
+
def blank_line_count_before
|
|
267
|
+
blank_lines_before.length
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Return the count of immediately following blank source lines.
|
|
271
|
+
#
|
|
272
|
+
# @return [Integer]
|
|
273
|
+
def blank_line_count_after
|
|
274
|
+
blank_lines_after.length
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def inspect
|
|
278
|
+
"#<#{self.class} type=#{type.inspect} range=#{start_byte}...#{end_byte}>"
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
private
|
|
282
|
+
|
|
283
|
+
def source_lines
|
|
284
|
+
@source_lines ||= begin
|
|
285
|
+
values = source.to_s.split("\n", -1)
|
|
286
|
+
values.pop if values.last&.empty? && source.to_s.end_with?("\n")
|
|
287
|
+
values
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def source_line(line_number)
|
|
292
|
+
return if line_number < 1 || line_number > source_lines.length
|
|
293
|
+
|
|
294
|
+
source_lines[line_number - 1]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def extract_body_text(raw_text)
|
|
298
|
+
text_without_opening = if opening_delimiter
|
|
299
|
+
raw_text.sub(/\A#{Regexp.escape(opening_delimiter)}[ \t]?/, '')
|
|
300
|
+
else
|
|
301
|
+
raw_text
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
return text_without_opening unless closing_delimiter
|
|
305
|
+
|
|
306
|
+
text_without_opening.sub(/[ \t]*#{Regexp.escape(closing_delimiter)}\z/m, '')
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def normalize_attachment_hint(hint)
|
|
310
|
+
return if hint.nil?
|
|
311
|
+
|
|
312
|
+
normalized = hint.to_sym
|
|
313
|
+
return normalized if ATTACHMENT_HINTS.include?(normalized)
|
|
314
|
+
|
|
315
|
+
raise ArgumentError,
|
|
316
|
+
"Unknown comment attachment hint: #{hint.inspect}. Expected one of: #{ATTACHMENT_HINTS.join(', ')}"
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
@@ -36,7 +36,7 @@ module TreeHaver
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Alias for name (tree-sitter compatibility)
|
|
39
|
-
|
|
39
|
+
alias language_name name
|
|
40
40
|
|
|
41
41
|
# -- Shared Implementation ------------------------------------------------
|
|
42
42
|
|
|
@@ -68,7 +68,7 @@ module TreeHaver
|
|
|
68
68
|
# Human-readable representation
|
|
69
69
|
# @return [String]
|
|
70
70
|
def inspect
|
|
71
|
-
opts = options.empty? ?
|
|
71
|
+
opts = options.empty? ? '' : " options=#{options}"
|
|
72
72
|
class_name = self.class.name || "#{self.class.superclass.name}(anonymous)"
|
|
73
73
|
"#<#{class_name} name=#{name} backend=#{backend}#{opts}>"
|
|
74
74
|
end
|
data/lib/tree_haver/base/node.rb
CHANGED
|
@@ -68,6 +68,14 @@ module TreeHaver
|
|
|
68
68
|
raise NotImplementedError, "#{self.class}#type must be implemented"
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
# The parser's original node type before a backend applies any portable
|
|
72
|
+
# vocabulary aliases. Backends without aliases use their public type.
|
|
73
|
+
#
|
|
74
|
+
# @return [String]
|
|
75
|
+
def native_type
|
|
76
|
+
type
|
|
77
|
+
end
|
|
78
|
+
|
|
71
79
|
# Get byte offset where the node starts
|
|
72
80
|
# @return [Integer] Start byte offset
|
|
73
81
|
def start_byte
|
|
@@ -155,7 +163,7 @@ module TreeHaver
|
|
|
155
163
|
end
|
|
156
164
|
|
|
157
165
|
# Alias for named?
|
|
158
|
-
|
|
166
|
+
alias structural? named?
|
|
159
167
|
|
|
160
168
|
# Check if this node represents a syntax error
|
|
161
169
|
# @return [Boolean] true on error
|
|
@@ -172,9 +180,9 @@ module TreeHaver
|
|
|
172
180
|
# Get the text content of this node
|
|
173
181
|
# @return [String] Node text
|
|
174
182
|
def text
|
|
175
|
-
return
|
|
183
|
+
return '' unless source
|
|
176
184
|
|
|
177
|
-
source[start_byte...end_byte] ||
|
|
185
|
+
source[start_byte...end_byte] || ''
|
|
178
186
|
end
|
|
179
187
|
|
|
180
188
|
# Get a child by field name
|
|
@@ -187,13 +195,13 @@ module TreeHaver
|
|
|
187
195
|
# Get start position (row/col) - 0-based
|
|
188
196
|
# @return [Hash{Symbol => Integer}] {row: 0, column: 0}
|
|
189
197
|
def start_point
|
|
190
|
-
{row: 0, column: 0}
|
|
198
|
+
{ row: 0, column: 0 }
|
|
191
199
|
end
|
|
192
200
|
|
|
193
201
|
# Get end position (row/col) - 0-based
|
|
194
202
|
# @return [Hash{Symbol => Integer}] {row: 0, column: 0}
|
|
195
203
|
def end_point
|
|
196
|
-
{row: 0, column: 0}
|
|
204
|
+
{ row: 0, column: 0 }
|
|
197
205
|
end
|
|
198
206
|
|
|
199
207
|
# -- Shared Implementation -----------------------------------------------
|
|
@@ -215,10 +223,10 @@ module TreeHaver
|
|
|
215
223
|
def start_line
|
|
216
224
|
sp = start_point
|
|
217
225
|
row = if sp.is_a?(Hash)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
226
|
+
sp[:row]
|
|
227
|
+
else
|
|
228
|
+
(sp.respond_to?(:row) ? sp.row : 0)
|
|
229
|
+
end
|
|
222
230
|
row + 1
|
|
223
231
|
end
|
|
224
232
|
|
|
@@ -227,10 +235,10 @@ module TreeHaver
|
|
|
227
235
|
def end_line
|
|
228
236
|
ep = end_point
|
|
229
237
|
row = if ep.is_a?(Hash)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
ep[:row]
|
|
239
|
+
else
|
|
240
|
+
(ep.respond_to?(:row) ? ep.row : 0)
|
|
241
|
+
end
|
|
234
242
|
row + 1
|
|
235
243
|
end
|
|
236
244
|
|
|
@@ -241,31 +249,31 @@ module TreeHaver
|
|
|
241
249
|
ep = end_point
|
|
242
250
|
|
|
243
251
|
sp_row = if sp.is_a?(Hash)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
sp[:row]
|
|
253
|
+
else
|
|
254
|
+
(sp.respond_to?(:row) ? sp.row : 0)
|
|
255
|
+
end
|
|
248
256
|
sp_col = if sp.is_a?(Hash)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
sp[:column]
|
|
258
|
+
else
|
|
259
|
+
(sp.respond_to?(:column) ? sp.column : 0)
|
|
260
|
+
end
|
|
253
261
|
ep_row = if ep.is_a?(Hash)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
ep[:row]
|
|
263
|
+
else
|
|
264
|
+
(ep.respond_to?(:row) ? ep.row : 0)
|
|
265
|
+
end
|
|
258
266
|
ep_col = if ep.is_a?(Hash)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
ep[:column]
|
|
268
|
+
else
|
|
269
|
+
(ep.respond_to?(:column) ? ep.column : 0)
|
|
270
|
+
end
|
|
263
271
|
|
|
264
272
|
{
|
|
265
273
|
start_line: sp_row + 1,
|
|
266
274
|
end_line: ep_row + 1,
|
|
267
275
|
start_column: sp_col,
|
|
268
|
-
end_column: ep_col
|
|
276
|
+
end_column: ep_col
|
|
269
277
|
}
|
|
270
278
|
end
|
|
271
279
|
|
|
@@ -276,7 +284,7 @@ module TreeHaver
|
|
|
276
284
|
node_type = begin
|
|
277
285
|
type
|
|
278
286
|
rescue NotImplementedError
|
|
279
|
-
|
|
287
|
+
'(not implemented)'
|
|
280
288
|
end
|
|
281
289
|
"#<#{class_name} type=#{node_type}>"
|
|
282
290
|
end
|
|
@@ -19,9 +19,9 @@ module TreeHaver
|
|
|
19
19
|
# @return [Integer, nil]
|
|
20
20
|
def [](key)
|
|
21
21
|
case key
|
|
22
|
-
when :row,
|
|
22
|
+
when :row, 'row', 0
|
|
23
23
|
row
|
|
24
|
-
when :column,
|
|
24
|
+
when :column, 'column', 1
|
|
25
25
|
column
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -29,7 +29,7 @@ module TreeHaver
|
|
|
29
29
|
# Convert to hash
|
|
30
30
|
# @return [Hash{Symbol => Integer}]
|
|
31
31
|
def to_h
|
|
32
|
-
{row: row, column: column}
|
|
32
|
+
{ row: row, column: column }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# String representation
|
data/lib/tree_haver/base/tree.rb
CHANGED
|
@@ -67,7 +67,7 @@ module TreeHaver
|
|
|
67
67
|
return @available if @load_attempted
|
|
68
68
|
|
|
69
69
|
@load_attempted = true
|
|
70
|
-
debug = ENV[
|
|
70
|
+
debug = ENV['TREE_HAVER_DEBUG']
|
|
71
71
|
|
|
72
72
|
# Guard against nil require_path (can happen if gem_name was nil)
|
|
73
73
|
if @require_path.nil? || @require_path.empty?
|
|
@@ -85,13 +85,13 @@ module TreeHaver
|
|
|
85
85
|
|
|
86
86
|
# Verify it responds to parse
|
|
87
87
|
unless @grammar_module.respond_to?(:parse)
|
|
88
|
-
# :
|
|
88
|
+
# simplecov:disable defensive - requires a gem with malformed grammar module
|
|
89
89
|
# Show what methods ARE available to help diagnose the issue
|
|
90
90
|
if debug
|
|
91
91
|
available_methods = @grammar_module.methods(false).sort.first(20)
|
|
92
92
|
warn("CitrusGrammarFinder: #{@grammar_const} doesn't respond to :parse")
|
|
93
93
|
warn("CitrusGrammarFinder: #{@grammar_const}.class = #{@grammar_module.class}")
|
|
94
|
-
warn("CitrusGrammarFinder: #{@grammar_const} is a #{@grammar_module.is_a?(Module) ?
|
|
94
|
+
warn("CitrusGrammarFinder: #{@grammar_const} is a #{@grammar_module.is_a?(Module) ? 'Module' : 'non-Module'}")
|
|
95
95
|
warn("CitrusGrammarFinder: Available singleton methods (first 20): #{available_methods.inspect}")
|
|
96
96
|
if @grammar_module.respond_to?(:instance_methods)
|
|
97
97
|
instance_methods = @grammar_module.instance_methods(false).sort.first(20)
|
|
@@ -100,49 +100,45 @@ module TreeHaver
|
|
|
100
100
|
end
|
|
101
101
|
@available = false
|
|
102
102
|
return false
|
|
103
|
-
# :
|
|
103
|
+
# simplecov:enable
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
@available = true
|
|
107
107
|
rescue LoadError => e
|
|
108
|
-
# :
|
|
108
|
+
# simplecov:disable defensive - requires gem to not be installed
|
|
109
109
|
# Only show LoadError details when debugging
|
|
110
110
|
if debug
|
|
111
111
|
warn("CitrusGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
|
|
112
112
|
warn("CitrusGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
113
113
|
end
|
|
114
114
|
@available = false
|
|
115
|
-
# :
|
|
115
|
+
# simplecov:enable
|
|
116
116
|
rescue NameError => e
|
|
117
|
-
# :
|
|
117
|
+
# simplecov:disable defensive - requires gem with missing constant
|
|
118
118
|
# Only show NameError details when debugging
|
|
119
119
|
if debug
|
|
120
120
|
warn("CitrusGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
|
|
121
121
|
warn("CitrusGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
122
122
|
end
|
|
123
123
|
@available = false
|
|
124
|
-
# :
|
|
124
|
+
# simplecov:enable
|
|
125
125
|
rescue TypeError => e
|
|
126
|
-
# :
|
|
126
|
+
# simplecov:disable defensive - TruffleRuby-specific edge case
|
|
127
127
|
# TruffleRuby's bundled_gems.rb can raise TypeError when File.path is called on nil
|
|
128
128
|
# This happens in bundled_gems.rb:124 warning? method when caller locations return nil
|
|
129
129
|
# Always warn about TypeError as it indicates a platform-specific issue
|
|
130
130
|
warn("CitrusGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
|
|
131
|
-
warn(
|
|
132
|
-
if debug
|
|
133
|
-
warn("CitrusGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
134
|
-
end
|
|
131
|
+
warn('CitrusGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue')
|
|
132
|
+
warn("CitrusGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
|
|
135
133
|
@available = false
|
|
136
|
-
# :
|
|
137
|
-
rescue => e
|
|
138
|
-
# :
|
|
134
|
+
# simplecov:enable
|
|
135
|
+
rescue StandardError => e
|
|
136
|
+
# simplecov:disable defensive - catch-all for unexpected errors
|
|
139
137
|
# Always warn about unexpected errors
|
|
140
138
|
warn("CitrusGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
|
|
141
|
-
if debug
|
|
142
|
-
warn("CitrusGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
143
|
-
end
|
|
139
|
+
warn("CitrusGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
|
|
144
140
|
@available = false
|
|
145
|
-
# :
|
|
141
|
+
# simplecov:enable
|
|
146
142
|
end
|
|
147
143
|
|
|
148
144
|
@available
|
|
@@ -166,16 +162,15 @@ module TreeHaver
|
|
|
166
162
|
# @raise [NotAvailable] if grammar not available and raise_on_missing is true
|
|
167
163
|
def register!(raise_on_missing: false)
|
|
168
164
|
unless available?
|
|
169
|
-
if raise_on_missing
|
|
170
|
-
|
|
171
|
-
end
|
|
165
|
+
raise NotAvailable, not_found_message if raise_on_missing
|
|
166
|
+
|
|
172
167
|
return false
|
|
173
168
|
end
|
|
174
169
|
|
|
175
170
|
TreeHaver.register_language(
|
|
176
171
|
@language_name,
|
|
177
172
|
grammar_module: @grammar_module,
|
|
178
|
-
gem_name: @gem_name
|
|
173
|
+
gem_name: @gem_name
|
|
179
174
|
)
|
|
180
175
|
true
|
|
181
176
|
end
|
|
@@ -190,7 +185,7 @@ module TreeHaver
|
|
|
190
185
|
grammar_const: @grammar_const,
|
|
191
186
|
require_path: @require_path,
|
|
192
187
|
available: available?,
|
|
193
|
-
grammar_module: @grammar_module&.name
|
|
188
|
+
grammar_module: @grammar_module&.name
|
|
194
189
|
}
|
|
195
190
|
end
|
|
196
191
|
|
|
@@ -210,7 +205,7 @@ module TreeHaver
|
|
|
210
205
|
# @return [Object] the constant
|
|
211
206
|
# @raise [NameError] if constant not found
|
|
212
207
|
def resolve_constant(const_path)
|
|
213
|
-
const_path.split(
|
|
208
|
+
const_path.split('::').reduce(Object) do |mod, const_name|
|
|
214
209
|
mod.const_get(const_name)
|
|
215
210
|
end
|
|
216
211
|
end
|