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
|
@@ -39,17 +39,18 @@ module TreeHaver
|
|
|
39
39
|
# end
|
|
40
40
|
class << self
|
|
41
41
|
def available?
|
|
42
|
-
return @loaded if @load_attempted
|
|
43
|
-
|
|
42
|
+
return @loaded if @load_attempted
|
|
43
|
+
|
|
44
|
+
@load_attempted = true
|
|
44
45
|
begin
|
|
45
|
-
require
|
|
46
|
-
@loaded = true
|
|
46
|
+
require 'parslet'
|
|
47
|
+
@loaded = true
|
|
47
48
|
rescue LoadError
|
|
48
|
-
@loaded = false
|
|
49
|
+
@loaded = false
|
|
49
50
|
rescue StandardError
|
|
50
|
-
@loaded = false
|
|
51
|
+
@loaded = false
|
|
51
52
|
end
|
|
52
|
-
@loaded
|
|
53
|
+
@loaded
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
# Reset the load state (primarily for testing)
|
|
@@ -57,8 +58,8 @@ module TreeHaver
|
|
|
57
58
|
# @return [void]
|
|
58
59
|
# @api private
|
|
59
60
|
def reset!
|
|
60
|
-
@load_attempted = false
|
|
61
|
-
@loaded = false
|
|
61
|
+
@load_attempted = false
|
|
62
|
+
@loaded = false
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
# Get capabilities supported by this backend
|
|
@@ -66,15 +67,17 @@ module TreeHaver
|
|
|
66
67
|
# @return [Hash{Symbol => Object}] capability map
|
|
67
68
|
# @example
|
|
68
69
|
# TreeHaver::Backends::Parslet.capabilities
|
|
69
|
-
# # => { backend: :parslet, query: false, bytes_field: true, incremental: false }
|
|
70
|
+
# # => { backend: :parslet, query: false, bytes_field: true, incremental: false, comment_support: :none }
|
|
70
71
|
def capabilities
|
|
71
72
|
return {} unless available?
|
|
73
|
+
|
|
72
74
|
{
|
|
73
75
|
backend: :parslet,
|
|
74
76
|
query: false, # Parslet doesn't have a query API like tree-sitter
|
|
75
77
|
bytes_field: true, # Parslet::Slice provides offset and length
|
|
76
78
|
incremental: false, # Parslet doesn't support incremental parsing
|
|
77
79
|
pure_ruby: true, # Parslet is pure Ruby (portable)
|
|
80
|
+
comment_support: :none
|
|
78
81
|
}
|
|
79
82
|
end
|
|
80
83
|
end
|
|
@@ -102,8 +105,8 @@ module TreeHaver
|
|
|
102
105
|
def initialize(grammar_class)
|
|
103
106
|
unless valid_grammar_class?(grammar_class)
|
|
104
107
|
raise TreeHaver::NotAvailable,
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
'Grammar class must be a Parslet::Parser subclass or respond to :new and return a parser with :parse. ' \
|
|
109
|
+
'Expected a Parslet grammar class (e.g., TOML::Parslet).'
|
|
107
110
|
end
|
|
108
111
|
@grammar_class = grammar_class
|
|
109
112
|
@backend = :parslet
|
|
@@ -118,12 +121,12 @@ module TreeHaver
|
|
|
118
121
|
# Derive name from grammar class (e.g., TOML::Parslet -> :toml)
|
|
119
122
|
return :unknown unless @grammar_class.respond_to?(:name) && @grammar_class.name
|
|
120
123
|
|
|
121
|
-
name = @grammar_class.name.to_s.split(
|
|
124
|
+
name = @grammar_class.name.to_s.split('::').first.downcase
|
|
122
125
|
name.to_sym
|
|
123
126
|
end
|
|
124
127
|
|
|
125
128
|
# Alias for language_name (API compatibility)
|
|
126
|
-
|
|
129
|
+
alias name language_name
|
|
127
130
|
|
|
128
131
|
# Compare languages for equality
|
|
129
132
|
#
|
|
@@ -147,7 +150,7 @@ module TreeHaver
|
|
|
147
150
|
end
|
|
148
151
|
|
|
149
152
|
# Alias eql? to ==
|
|
150
|
-
|
|
153
|
+
alias eql? ==
|
|
151
154
|
|
|
152
155
|
# Load language from library path (API compatibility)
|
|
153
156
|
#
|
|
@@ -169,13 +172,13 @@ module TreeHaver
|
|
|
169
172
|
def from_library(path = nil, symbol: nil, name: nil)
|
|
170
173
|
# Derive language name from path, symbol, or explicit name
|
|
171
174
|
lang_name = name&.to_sym ||
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
symbol&.to_s&.sub(/^tree_sitter_/, '')&.to_sym ||
|
|
176
|
+
path && TreeHaver::LibraryPathUtils.derive_language_name_from_path(path)&.to_sym
|
|
174
177
|
|
|
175
178
|
unless lang_name
|
|
176
179
|
raise TreeHaver::NotAvailable,
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
'Parslet backend requires a language name. ' \
|
|
181
|
+
'Provide name: parameter or register a grammar with TreeHaver.register_language.'
|
|
179
182
|
end
|
|
180
183
|
|
|
181
184
|
# Look up registered Parslet grammar
|
|
@@ -183,15 +186,15 @@ module TreeHaver
|
|
|
183
186
|
|
|
184
187
|
unless registration
|
|
185
188
|
raise TreeHaver::NotAvailable,
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
"No Parslet grammar registered for #{lang_name.inspect}. " \
|
|
190
|
+
"Register one with: TreeHaver.register_language(:#{lang_name}, grammar_class: YourGrammar)"
|
|
188
191
|
end
|
|
189
192
|
|
|
190
193
|
grammar_class = registration[:grammar_class]
|
|
191
194
|
new(grammar_class)
|
|
192
195
|
end
|
|
193
196
|
|
|
194
|
-
|
|
197
|
+
alias from_path from_library
|
|
195
198
|
end
|
|
196
199
|
|
|
197
200
|
private
|
|
@@ -200,9 +203,7 @@ module TreeHaver
|
|
|
200
203
|
return false unless klass.respond_to?(:new)
|
|
201
204
|
|
|
202
205
|
# Check if it's a Parslet::Parser subclass
|
|
203
|
-
if defined?(::Parslet::Parser)
|
|
204
|
-
return true if klass < ::Parslet::Parser
|
|
205
|
-
end
|
|
206
|
+
return true if defined?(::Parslet::Parser) && (klass < ::Parslet::Parser)
|
|
206
207
|
|
|
207
208
|
# Fallback: check if it can create an instance that responds to parse
|
|
208
209
|
begin
|
|
@@ -222,10 +223,14 @@ module TreeHaver
|
|
|
222
223
|
#
|
|
223
224
|
# @raise [TreeHaver::NotAvailable] if parslet gem is not available
|
|
224
225
|
def initialize
|
|
225
|
-
raise TreeHaver::NotAvailable,
|
|
226
|
+
raise TreeHaver::NotAvailable, 'parslet gem not available' unless Parslet.available?
|
|
227
|
+
|
|
226
228
|
@grammar = nil
|
|
229
|
+
@backend = :parslet
|
|
227
230
|
end
|
|
228
231
|
|
|
232
|
+
attr_reader :backend
|
|
233
|
+
|
|
229
234
|
# Set the grammar for this parser
|
|
230
235
|
#
|
|
231
236
|
# Accepts either a Parslet::Language wrapper or a raw Parslet grammar class.
|
|
@@ -241,16 +246,16 @@ module TreeHaver
|
|
|
241
246
|
def language=(grammar)
|
|
242
247
|
# Accept Language wrapper or raw grammar class
|
|
243
248
|
actual_grammar = case grammar
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
when Language
|
|
250
|
+
grammar.grammar_class
|
|
251
|
+
else
|
|
252
|
+
grammar
|
|
253
|
+
end
|
|
249
254
|
|
|
250
255
|
unless actual_grammar.respond_to?(:new)
|
|
251
256
|
raise ArgumentError,
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
'Expected Parslet grammar class with new method or Language wrapper, ' \
|
|
258
|
+
"got #{grammar.class}"
|
|
254
259
|
end
|
|
255
260
|
@grammar = actual_grammar
|
|
256
261
|
end
|
|
@@ -262,7 +267,7 @@ module TreeHaver
|
|
|
262
267
|
# @raise [TreeHaver::NotAvailable] if no grammar is set
|
|
263
268
|
# @raise [::Parslet::ParseFailed] if parsing fails
|
|
264
269
|
def parse(source)
|
|
265
|
-
raise TreeHaver::NotAvailable,
|
|
270
|
+
raise TreeHaver::NotAvailable, 'No grammar loaded' unless @grammar
|
|
266
271
|
|
|
267
272
|
begin
|
|
268
273
|
parser_instance = @grammar.new
|
|
@@ -283,7 +288,7 @@ module TreeHaver
|
|
|
283
288
|
# @param source [String] the source code to parse
|
|
284
289
|
# @return [Tree] raw backend tree (wrapping happens in TreeHaver::Parser)
|
|
285
290
|
def parse_string(old_tree, source) # rubocop:disable Lint/UnusedMethodArgument
|
|
286
|
-
parse(source)
|
|
291
|
+
parse(source) # Parslet doesn't support incremental parsing
|
|
287
292
|
end
|
|
288
293
|
end
|
|
289
294
|
|
|
@@ -307,7 +312,7 @@ module TreeHaver
|
|
|
307
312
|
end
|
|
308
313
|
|
|
309
314
|
def root_node
|
|
310
|
-
Node.new(@parslet_result, @source, type:
|
|
315
|
+
Node.new(@parslet_result, @source, type: 'document')
|
|
311
316
|
end
|
|
312
317
|
end
|
|
313
318
|
|
|
@@ -426,7 +431,7 @@ module TreeHaver
|
|
|
426
431
|
when String
|
|
427
432
|
@value
|
|
428
433
|
when Hash, Array
|
|
429
|
-
@source[start_byte...end_byte] ||
|
|
434
|
+
@source[start_byte...end_byte] || ''
|
|
430
435
|
else
|
|
431
436
|
@value.to_s
|
|
432
437
|
end
|
|
@@ -442,11 +447,13 @@ module TreeHaver
|
|
|
442
447
|
when Hash
|
|
443
448
|
keys = @value.keys
|
|
444
449
|
return if index >= keys.size
|
|
450
|
+
|
|
445
451
|
key = keys[index]
|
|
446
452
|
Node.new(@value[key], @source, key: key)
|
|
447
453
|
when Array
|
|
448
454
|
return if index >= @value.size
|
|
449
|
-
|
|
455
|
+
|
|
456
|
+
Node.new(@value[index], @source, type: 'element')
|
|
450
457
|
end
|
|
451
458
|
end
|
|
452
459
|
|
|
@@ -482,15 +489,13 @@ module TreeHaver
|
|
|
482
489
|
private
|
|
483
490
|
|
|
484
491
|
def calculate_point(offset)
|
|
485
|
-
return {row: 0, column: 0} if offset <= 0
|
|
492
|
+
return { row: 0, column: 0 } if offset <= 0
|
|
486
493
|
|
|
487
494
|
lines_before = @source[0...offset].count("\n")
|
|
488
|
-
line_start = if offset > 0
|
|
489
|
-
@source.rindex("\n", offset - 1)
|
|
490
|
-
end
|
|
495
|
+
line_start = (@source.rindex("\n", offset - 1) if offset > 0)
|
|
491
496
|
line_start ||= -1
|
|
492
497
|
column = offset - line_start - 1
|
|
493
|
-
{row: lines_before, column: column}
|
|
498
|
+
{ row: lines_before, column: column }
|
|
494
499
|
end
|
|
495
500
|
|
|
496
501
|
def infer_type(key)
|
|
@@ -498,15 +503,15 @@ module TreeHaver
|
|
|
498
503
|
|
|
499
504
|
case @value
|
|
500
505
|
when ::Parslet::Slice
|
|
501
|
-
|
|
506
|
+
'slice'
|
|
502
507
|
when Hash
|
|
503
|
-
|
|
508
|
+
'hash'
|
|
504
509
|
when Array
|
|
505
|
-
|
|
510
|
+
'array'
|
|
506
511
|
when String
|
|
507
|
-
|
|
512
|
+
'string'
|
|
508
513
|
else
|
|
509
|
-
|
|
514
|
+
'unknown'
|
|
510
515
|
end
|
|
511
516
|
end
|
|
512
517
|
|
|
@@ -37,19 +37,20 @@ module TreeHaver
|
|
|
37
37
|
# end
|
|
38
38
|
class << self
|
|
39
39
|
def available?
|
|
40
|
-
return @loaded if @load_attempted
|
|
41
|
-
|
|
40
|
+
return @loaded if @load_attempted
|
|
41
|
+
|
|
42
|
+
@load_attempted = true
|
|
42
43
|
begin
|
|
43
|
-
require
|
|
44
|
-
@loaded = true
|
|
44
|
+
require 'prism'
|
|
45
|
+
@loaded = true
|
|
45
46
|
rescue LoadError
|
|
46
|
-
@loaded = false
|
|
47
|
+
@loaded = false
|
|
47
48
|
rescue StandardError
|
|
48
|
-
# :
|
|
49
|
-
@loaded = false
|
|
50
|
-
# :
|
|
49
|
+
# simplecov:disable defensive code - StandardError during require is extremely rare
|
|
50
|
+
@loaded = false
|
|
51
|
+
# simplecov:enable
|
|
51
52
|
end
|
|
52
|
-
@loaded
|
|
53
|
+
@loaded
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
# Reset the load state (primarily for testing)
|
|
@@ -57,8 +58,8 @@ module TreeHaver
|
|
|
57
58
|
# @return [void]
|
|
58
59
|
# @api private
|
|
59
60
|
def reset!
|
|
60
|
-
@load_attempted = false
|
|
61
|
-
@loaded = false
|
|
61
|
+
@load_attempted = false
|
|
62
|
+
@loaded = false
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
# Get capabilities supported by this backend
|
|
@@ -66,9 +67,10 @@ module TreeHaver
|
|
|
66
67
|
# @return [Hash{Symbol => Object}] capability map
|
|
67
68
|
# @example
|
|
68
69
|
# TreeHaver::Backends::Prism.capabilities
|
|
69
|
-
# # => { backend: :prism, query: false, bytes_field: true, incremental: false, ruby_only: true }
|
|
70
|
+
# # => { backend: :prism, query: false, bytes_field: true, incremental: false, ruby_only: true, comment_support: :partial }
|
|
70
71
|
def capabilities
|
|
71
72
|
return {} unless available?
|
|
73
|
+
|
|
72
74
|
{
|
|
73
75
|
backend: :prism,
|
|
74
76
|
query: false, # Prism doesn't have tree-sitter-style queries (has pattern matching)
|
|
@@ -77,6 +79,8 @@ module TreeHaver
|
|
|
77
79
|
pure_ruby: false, # Prism has native C extension (but also pure Ruby mode)
|
|
78
80
|
ruby_only: true, # Prism only parses Ruby source code
|
|
79
81
|
error_tolerant: true, # Prism has excellent error recovery
|
|
82
|
+
comment_support: :partial,
|
|
83
|
+
comment_attachment_hints: true
|
|
80
84
|
}
|
|
81
85
|
end
|
|
82
86
|
end
|
|
@@ -96,11 +100,11 @@ module TreeHaver
|
|
|
96
100
|
def initialize(name = :ruby, options: {})
|
|
97
101
|
super(name, backend: :prism, options: options)
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
return if self.name == :ruby
|
|
104
|
+
|
|
105
|
+
raise TreeHaver::NotAvailable,
|
|
106
|
+
'Prism only supports Ruby parsing. ' \
|
|
107
|
+
"Got language: #{name.inspect}"
|
|
104
108
|
end
|
|
105
109
|
|
|
106
110
|
# Compare languages for equality by options (since name is always :ruby)
|
|
@@ -143,14 +147,14 @@ module TreeHaver
|
|
|
143
147
|
|
|
144
148
|
unless lang_name == :ruby
|
|
145
149
|
raise TreeHaver::NotAvailable,
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
"Prism backend only supports Ruby, not #{lang_name}. " \
|
|
151
|
+
"Use a tree-sitter backend for #{lang_name} support."
|
|
148
152
|
end
|
|
149
153
|
|
|
150
154
|
ruby
|
|
151
155
|
end
|
|
152
156
|
|
|
153
|
-
|
|
157
|
+
alias from_path from_library
|
|
154
158
|
end
|
|
155
159
|
end
|
|
156
160
|
|
|
@@ -163,7 +167,8 @@ module TreeHaver
|
|
|
163
167
|
# @raise [TreeHaver::NotAvailable] if prism is not available
|
|
164
168
|
def initialize
|
|
165
169
|
super
|
|
166
|
-
raise TreeHaver::NotAvailable,
|
|
170
|
+
raise TreeHaver::NotAvailable, 'prism not available' unless TreeHaver::Backends::Prism.available?
|
|
171
|
+
|
|
167
172
|
@options = {}
|
|
168
173
|
end
|
|
169
174
|
|
|
@@ -185,11 +190,11 @@ module TreeHaver
|
|
|
185
190
|
@options = {}
|
|
186
191
|
else
|
|
187
192
|
raise ArgumentError,
|
|
188
|
-
|
|
193
|
+
"Prism only supports Ruby parsing. Got: #{lang.inspect}"
|
|
189
194
|
end
|
|
190
195
|
else
|
|
191
196
|
raise ArgumentError,
|
|
192
|
-
|
|
197
|
+
"Expected Prism::Language or :ruby, got #{lang.class}"
|
|
193
198
|
end
|
|
194
199
|
end
|
|
195
200
|
|
|
@@ -199,7 +204,7 @@ module TreeHaver
|
|
|
199
204
|
# @return [Tree] raw backend tree (wrapping happens in TreeHaver::Parser)
|
|
200
205
|
# @raise [TreeHaver::NotAvailable] if no language is set
|
|
201
206
|
def parse(source)
|
|
202
|
-
raise TreeHaver::NotAvailable,
|
|
207
|
+
raise TreeHaver::NotAvailable, 'No language loaded (use parser.language = :ruby)' unless @language
|
|
203
208
|
|
|
204
209
|
# Use Prism.parse with options
|
|
205
210
|
prism_result = ::Prism.parse(source, **@options)
|
|
@@ -214,7 +219,7 @@ module TreeHaver
|
|
|
214
219
|
# @param source [String] the Ruby source code to parse
|
|
215
220
|
# @return [Tree] raw backend tree (wrapping happens in TreeHaver::Parser)
|
|
216
221
|
def parse_string(old_tree, source) # rubocop:disable Lint/UnusedMethodArgument
|
|
217
|
-
parse(source)
|
|
222
|
+
parse(source) # Prism doesn't support incremental parsing
|
|
218
223
|
end
|
|
219
224
|
end
|
|
220
225
|
|
|
@@ -262,9 +267,14 @@ module TreeHaver
|
|
|
262
267
|
|
|
263
268
|
# Get comments from the parse
|
|
264
269
|
#
|
|
265
|
-
# @return [Array
|
|
270
|
+
# @return [Array<Comment>]
|
|
266
271
|
def comments
|
|
267
|
-
@
|
|
272
|
+
@comments ||= begin
|
|
273
|
+
hint_map = comment_hint_map
|
|
274
|
+
@parse_result.comments.map do |comment|
|
|
275
|
+
Comment.new(comment, source: source, attachment_hint: hint_map[comment.object_id])
|
|
276
|
+
end
|
|
277
|
+
end
|
|
268
278
|
end
|
|
269
279
|
|
|
270
280
|
# Get magic comments (e.g., frozen_string_literal)
|
|
@@ -280,6 +290,87 @@ module TreeHaver
|
|
|
280
290
|
def data_loc
|
|
281
291
|
@parse_result.data_loc
|
|
282
292
|
end
|
|
293
|
+
|
|
294
|
+
private
|
|
295
|
+
|
|
296
|
+
def comment_hint_map
|
|
297
|
+
comments = @parse_result.comments
|
|
298
|
+
lines = source&.lines || []
|
|
299
|
+
|
|
300
|
+
comments.each_with_object({}) do |comment, hints|
|
|
301
|
+
hints[comment.object_id] = classify_comment_hint(comment, lines)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def classify_comment_hint(comment, lines)
|
|
306
|
+
line = lines[comment.location.start_line - 1].to_s
|
|
307
|
+
prefix = line[0...comment.location.start_column]
|
|
308
|
+
return :inline if prefix.match?(/\S/)
|
|
309
|
+
|
|
310
|
+
remaining_nonempty = Array(lines[comment.location.end_line..]).reject { |candidate| candidate.strip.empty? }
|
|
311
|
+
return :trailing if remaining_nonempty.empty? || remaining_nonempty.all? do |candidate|
|
|
312
|
+
candidate.lstrip.start_with?('#')
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
:leading
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Prism comment wrapper.
|
|
320
|
+
#
|
|
321
|
+
# Prism exposes native Ruby comments via Prism::Comment subclasses and
|
|
322
|
+
# Prism::Location objects. This wrapper normalizes those objects onto a
|
|
323
|
+
# small parser-facing contract for downstream consumers.
|
|
324
|
+
class Comment < TreeHaver::Base::Comment
|
|
325
|
+
def location
|
|
326
|
+
inner_comment.location
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def type
|
|
330
|
+
self.class.comment_type_for(inner_comment.class)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def text
|
|
334
|
+
inner_comment.slice
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def start_byte
|
|
338
|
+
location.start_offset
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def end_byte
|
|
342
|
+
location.end_offset
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def start_point
|
|
346
|
+
{
|
|
347
|
+
row: location.start_line - 1,
|
|
348
|
+
column: location.start_column
|
|
349
|
+
}
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def end_point
|
|
353
|
+
{
|
|
354
|
+
row: location.end_line - 1,
|
|
355
|
+
column: location.end_column
|
|
356
|
+
}
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def style
|
|
360
|
+
:line
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def opening_delimiter
|
|
364
|
+
'#'
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
class << self
|
|
368
|
+
def comment_type_for(klass)
|
|
369
|
+
klass.name.split('::').last
|
|
370
|
+
.gsub(/([a-z\d])([A-Z])/, '\\1_\\2')
|
|
371
|
+
.downcase
|
|
372
|
+
end
|
|
373
|
+
end
|
|
283
374
|
end
|
|
284
375
|
|
|
285
376
|
# Prism node wrapper
|
|
@@ -305,11 +396,11 @@ module TreeHaver
|
|
|
305
396
|
#
|
|
306
397
|
# @return [String] node type in snake_case
|
|
307
398
|
def type
|
|
308
|
-
return
|
|
399
|
+
return 'nil' if inner_node.nil?
|
|
309
400
|
|
|
310
401
|
# Convert class name to snake_case type
|
|
311
|
-
class_name = inner_node.class.name.split(
|
|
312
|
-
class_name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/,
|
|
402
|
+
class_name = inner_node.class.name.split('::').last
|
|
403
|
+
class_name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
|
|
313
404
|
end
|
|
314
405
|
|
|
315
406
|
# Alias for type (API compatibility)
|
|
@@ -323,6 +414,7 @@ module TreeHaver
|
|
|
323
414
|
# @return [Integer]
|
|
324
415
|
def start_byte
|
|
325
416
|
return 0 if inner_node.nil? || !inner_node.respond_to?(:location)
|
|
417
|
+
|
|
326
418
|
loc = inner_node.location
|
|
327
419
|
loc&.start_offset || 0
|
|
328
420
|
end
|
|
@@ -332,6 +424,7 @@ module TreeHaver
|
|
|
332
424
|
# @return [Integer]
|
|
333
425
|
def end_byte
|
|
334
426
|
return 0 if inner_node.nil? || !inner_node.respond_to?(:location)
|
|
427
|
+
|
|
335
428
|
loc = inner_node.location
|
|
336
429
|
loc&.end_offset || 0
|
|
337
430
|
end
|
|
@@ -340,22 +433,24 @@ module TreeHaver
|
|
|
340
433
|
#
|
|
341
434
|
# @return [Hash{Symbol => Integer}]
|
|
342
435
|
def start_point
|
|
343
|
-
return {row: 0, column: 0} if inner_node.nil? || !inner_node.respond_to?(:location)
|
|
436
|
+
return { row: 0, column: 0 } if inner_node.nil? || !inner_node.respond_to?(:location)
|
|
437
|
+
|
|
344
438
|
loc = inner_node.location
|
|
345
|
-
return {row: 0, column: 0} unless loc
|
|
439
|
+
return { row: 0, column: 0 } unless loc
|
|
346
440
|
|
|
347
|
-
{row: (loc.start_line - 1), column: loc.start_column}
|
|
441
|
+
{ row: (loc.start_line - 1), column: loc.start_column }
|
|
348
442
|
end
|
|
349
443
|
|
|
350
444
|
# Get the end position as row/column (0-based)
|
|
351
445
|
#
|
|
352
446
|
# @return [Hash{Symbol => Integer}]
|
|
353
447
|
def end_point
|
|
354
|
-
return {row: 0, column: 0} if inner_node.nil? || !inner_node.respond_to?(:location)
|
|
448
|
+
return { row: 0, column: 0 } if inner_node.nil? || !inner_node.respond_to?(:location)
|
|
449
|
+
|
|
355
450
|
loc = inner_node.location
|
|
356
|
-
return {row: 0, column: 0} unless loc
|
|
451
|
+
return { row: 0, column: 0 } unless loc
|
|
357
452
|
|
|
358
|
-
{row: (loc.end_line - 1), column: loc.end_column}
|
|
453
|
+
{ row: (loc.end_line - 1), column: loc.end_column }
|
|
359
454
|
end
|
|
360
455
|
|
|
361
456
|
# Get all child nodes
|
|
@@ -372,7 +467,7 @@ module TreeHaver
|
|
|
372
467
|
#
|
|
373
468
|
# @return [String]
|
|
374
469
|
def text
|
|
375
|
-
return
|
|
470
|
+
return '' if inner_node.nil?
|
|
376
471
|
|
|
377
472
|
if inner_node.respond_to?(:slice)
|
|
378
473
|
inner_node.slice
|
|
@@ -382,7 +477,7 @@ module TreeHaver
|
|
|
382
477
|
end
|
|
383
478
|
|
|
384
479
|
# Alias for Prism compatibility
|
|
385
|
-
|
|
480
|
+
alias slice text
|
|
386
481
|
|
|
387
482
|
# Check if this node has errors
|
|
388
483
|
#
|
|
@@ -391,11 +486,12 @@ module TreeHaver
|
|
|
391
486
|
return false if inner_node.nil?
|
|
392
487
|
|
|
393
488
|
# Check if this is an error node type
|
|
394
|
-
return true if type.include?(
|
|
489
|
+
return true if type.include?('missing') || type.include?('error')
|
|
395
490
|
|
|
396
491
|
# Check children recursively (Prism error nodes are usually children)
|
|
397
492
|
return false unless inner_node.respond_to?(:child_nodes)
|
|
398
|
-
|
|
493
|
+
|
|
494
|
+
inner_node.child_nodes.compact.any? { |n| n.class.name.to_s.include?('Missing') }
|
|
399
495
|
end
|
|
400
496
|
|
|
401
497
|
# Check if this node is a "missing" node (error recovery)
|
|
@@ -403,7 +499,8 @@ module TreeHaver
|
|
|
403
499
|
# @return [Boolean]
|
|
404
500
|
def missing?
|
|
405
501
|
return false if inner_node.nil?
|
|
406
|
-
|
|
502
|
+
|
|
503
|
+
type.include?('missing')
|
|
407
504
|
end
|
|
408
505
|
|
|
409
506
|
# Get a child by field name (Prism node accessor)
|
|
@@ -423,7 +520,7 @@ module TreeHaver
|
|
|
423
520
|
result.is_a?(::Prism::Node) ? Node.new(result, source) : nil
|
|
424
521
|
end
|
|
425
522
|
|
|
426
|
-
|
|
523
|
+
alias field child_by_field_name
|
|
427
524
|
|
|
428
525
|
# String representation
|
|
429
526
|
#
|
|
@@ -439,6 +536,7 @@ module TreeHaver
|
|
|
439
536
|
# @return [Boolean]
|
|
440
537
|
def respond_to_missing?(method_name, include_private = false)
|
|
441
538
|
return false if inner_node.nil?
|
|
539
|
+
|
|
442
540
|
inner_node.respond_to?(method_name, include_private) || super
|
|
443
541
|
end
|
|
444
542
|
|
|
@@ -468,4 +566,3 @@ module TreeHaver
|
|
|
468
566
|
end
|
|
469
567
|
end
|
|
470
568
|
end
|
|
471
|
-
|