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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +299 -660
  5. data/lib/tree_haver/backend_api.rb +78 -35
  6. data/lib/tree_haver/backend_context.rb +28 -0
  7. data/lib/tree_haver/backend_registry.rb +119 -382
  8. data/lib/tree_haver/backends/citrus.rb +55 -53
  9. data/lib/tree_haver/backends/ffi.rb +105 -101
  10. data/lib/tree_haver/backends/java.rb +92 -76
  11. data/lib/tree_haver/backends/mri.rb +44 -39
  12. data/lib/tree_haver/backends/parslet.rb +53 -48
  13. data/lib/tree_haver/backends/prism.rb +140 -43
  14. data/lib/tree_haver/backends/psych.rb +30 -26
  15. data/lib/tree_haver/backends/rust.rb +21 -17
  16. data/lib/tree_haver/backends/tslp.rb +274 -0
  17. data/lib/tree_haver/base/comment.rb +320 -0
  18. data/lib/tree_haver/base/language.rb +2 -2
  19. data/lib/tree_haver/base/node.rb +39 -31
  20. data/lib/tree_haver/base/parser.rb +4 -0
  21. data/lib/tree_haver/base/point.rb +3 -3
  22. data/lib/tree_haver/base/tree.rb +1 -1
  23. data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
  24. data/lib/tree_haver/contracts.rb +1025 -0
  25. data/lib/tree_haver/grammar_finder.rb +125 -70
  26. data/lib/tree_haver/kaitai_backend.rb +30 -0
  27. data/lib/tree_haver/language.rb +36 -37
  28. data/lib/tree_haver/language_pack.rb +41 -0
  29. data/lib/tree_haver/language_registry.rb +34 -3
  30. data/lib/tree_haver/library_path_utils.rb +7 -7
  31. data/lib/tree_haver/node.rb +40 -31
  32. data/lib/tree_haver/parser.rb +44 -37
  33. data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
  34. data/lib/tree_haver/path_validator.rb +39 -36
  35. data/lib/tree_haver/peg_backends.rb +76 -0
  36. data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
  37. data/lib/tree_haver/rspec.rb +1 -31
  38. data/lib/tree_haver/tree.rb +16 -7
  39. data/lib/tree_haver/version.rb +5 -14
  40. data/lib/tree_haver.rb +536 -1240
  41. data/sig/tree_haver.rbs +1 -229
  42. data.tar.gz.sig +0 -0
  43. metadata +154 -70
  44. metadata.gz.sig +0 -0
  45. data/CHANGELOG.md +0 -1393
  46. data/CITATION.cff +0 -20
  47. data/CODE_OF_CONDUCT.md +0 -134
  48. data/CONTRIBUTING.md +0 -359
  49. data/FUNDING.md +0 -74
  50. data/LICENSE.txt +0 -21
  51. data/REEK +0 -0
  52. data/RUBOCOP.md +0 -71
  53. data/SECURITY.md +0 -21
  54. data/lib/tree_haver/base.rb +0 -12
  55. data/lib/tree_haver/compat.rb +0 -43
  56. data/lib/tree_haver/rspec/testable_node.rb +0 -217
  57. data/sig/tree_haver/backends.rbs +0 -352
  58. data/sig/tree_haver/grammar_finder.rbs +0 -29
  59. data/sig/tree_haver/path_validator.rbs +0 -32
@@ -33,21 +33,21 @@ module TreeHaver
33
33
  return unless path
34
34
 
35
35
  # Extract filename without extension: "libtree-sitter-toml" or "toml"
36
- filename = File.basename(path, ".*")
36
+ filename = File.basename(path, '.*')
37
37
 
38
38
  # Handle multi-part extensions like .so.0.24
39
- filename = filename.sub(/\.so(\.\d+)*\z/, "")
39
+ filename = filename.sub(/\.so(\.\d+)*\z/, '')
40
40
 
41
41
  # Match patterns and normalize to tree_sitter_<lang>
42
42
  case filename
43
43
  when /\Alib[-_]?tree[-_]sitter[-_](.+)\z/
44
- "tree_sitter_#{Regexp.last_match(1).tr("-", "_")}"
44
+ "tree_sitter_#{Regexp.last_match(1).tr('-', '_')}"
45
45
  when /\Atree[-_]sitter[-_](.+)\z/
46
- "tree_sitter_#{Regexp.last_match(1).tr("-", "_")}"
46
+ "tree_sitter_#{Regexp.last_match(1).tr('-', '_')}"
47
47
  else
48
48
  # Assume filename is just the language name (e.g., "toml.so" -> "tree_sitter_toml")
49
49
  # Also strip "lib" prefix if present (e.g., "libtoml.so" -> "tree_sitter_toml")
50
- lang = filename.sub(/\Alib/, "").tr("-", "_")
50
+ lang = filename.sub(/\Alib/, '').tr('-', '_')
51
51
  "tree_sitter_#{lang}"
52
52
  end
53
53
  end
@@ -64,7 +64,7 @@ module TreeHaver
64
64
  return unless symbol
65
65
 
66
66
  # Strip the "tree_sitter_" prefix to get the language name
67
- symbol.sub(/\Atree_sitter_/, "")
67
+ symbol.sub(/\Atree_sitter_/, '')
68
68
  end
69
69
 
70
70
  # Derive language name from a symbol
@@ -74,7 +74,7 @@ module TreeHaver
74
74
  def derive_language_name_from_symbol(symbol)
75
75
  return unless symbol
76
76
 
77
- symbol.sub(/\Atree_sitter_/, "")
77
+ symbol.sub(/\Atree_sitter_/, '')
78
78
  end
79
79
  end
80
80
  end
@@ -106,7 +106,7 @@ module TreeHaver
106
106
  elsif @inner_node.respond_to?(:kind)
107
107
  @inner_node.kind.to_s
108
108
  else
109
- raise TreeHaver::Error, "Backend node does not support type/kind"
109
+ raise TreeHaver::Error, 'Backend node does not support type/kind'
110
110
  end
111
111
  end
112
112
 
@@ -151,7 +151,7 @@ module TreeHaver
151
151
  Point.new(point.row, point.column)
152
152
  end
153
153
  else
154
- raise TreeHaver::Error, "Backend node does not support start_point/start_position"
154
+ raise TreeHaver::Error, 'Backend node does not support start_point/start_position'
155
155
  end
156
156
  end
157
157
 
@@ -176,7 +176,7 @@ module TreeHaver
176
176
  Point.new(point.row, point.column)
177
177
  end
178
178
  else
179
- raise TreeHaver::Error, "Backend node does not support end_point/end_position"
179
+ raise TreeHaver::Error, 'Backend node does not support end_point/end_position'
180
180
  end
181
181
  end
182
182
 
@@ -213,7 +213,7 @@ module TreeHaver
213
213
  start_line: start_line,
214
214
  end_line: end_line,
215
215
  start_column: start_point.column,
216
- end_column: end_point.column,
216
+ end_column: end_point.column
217
217
  }
218
218
  end
219
219
 
@@ -234,7 +234,7 @@ module TreeHaver
234
234
  # Some backends (like TreeStump) require source as argument
235
235
  # Check arity to determine how to call
236
236
  arity = @inner_node.method(:text).arity
237
- if arity == 0 || arity == -1
237
+ if [0, -1].include?(arity)
238
238
  # No required arguments, or optional arguments only
239
239
  @inner_node.text
240
240
  elsif arity >= 1 && @source
@@ -242,15 +242,15 @@ module TreeHaver
242
242
  @inner_node.text(@source)
243
243
  elsif @source
244
244
  # Fallback to byte extraction
245
- @source[start_byte...end_byte] || ""
245
+ @source[start_byte...end_byte] || ''
246
246
  else
247
- raise TreeHaver::Error, "Cannot extract text: backend requires source but none provided"
247
+ raise TreeHaver::Error, 'Cannot extract text: backend requires source but none provided'
248
248
  end
249
249
  elsif @source
250
250
  # Fallback: extract from source using byte positions
251
- @source[start_byte...end_byte] || ""
251
+ @source[start_byte...end_byte] || ''
252
252
  else
253
- raise TreeHaver::Error, "Cannot extract text: node has no text method and no source provided"
253
+ raise TreeHaver::Error, 'Cannot extract text: node has no text method and no source provided'
254
254
  end
255
255
  end
256
256
 
@@ -311,6 +311,7 @@ module TreeHaver
311
311
  def child(index)
312
312
  child_node = @inner_node.child(index)
313
313
  return if child_node.nil?
314
+
314
315
  Node.new(child_node, source: @source)
315
316
  rescue IndexError
316
317
  # Some backends (e.g., MRI w/ ruby_tree_sitter) raise IndexError for out of bounds
@@ -329,6 +330,7 @@ module TreeHaver
329
330
  if @inner_node.respond_to?(:named_child)
330
331
  child_node = @inner_node.named_child(index)
331
332
  return if child_node.nil?
333
+
332
334
  return Node.new(child_node, source: @source)
333
335
  end
334
336
 
@@ -340,20 +342,20 @@ module TreeHaver
340
342
 
341
343
  # Check if this child is named
342
344
  is_named = if child_node.respond_to?(:named?)
343
- child_node.named?
344
- elsif child_node.respond_to?(:is_named?)
345
- child_node.is_named?
346
- else
347
- true # Assume named if we can't determine
348
- end
345
+ child_node.named?
346
+ elsif child_node.respond_to?(:is_named?)
347
+ child_node.is_named?
348
+ else
349
+ true # Assume named if we can't determine
350
+ end
349
351
 
350
- if is_named
351
- return Node.new(child_node, source: @source) if named_count == index
352
- named_count += 1
353
- end
352
+ next unless is_named
353
+ return Node.new(child_node, source: @source) if named_count == index
354
+
355
+ named_count += 1
354
356
  end
355
357
 
356
- nil # Index out of bounds
358
+ nil # Index out of bounds
357
359
  end
358
360
 
359
361
  # Get the count of named children
@@ -363,9 +365,7 @@ module TreeHaver
363
365
  # @return [Integer] Number of named children
364
366
  def named_child_count
365
367
  # Try native implementation first
366
- if @inner_node.respond_to?(:named_child_count)
367
- return @inner_node.named_child_count
368
- end
368
+ return @inner_node.named_child_count if @inner_node.respond_to?(:named_child_count)
369
369
 
370
370
  # Fallback: count named children manually
371
371
  count = 0
@@ -375,12 +375,12 @@ module TreeHaver
375
375
 
376
376
  # Check if this child is named
377
377
  is_named = if child_node.respond_to?(:named?)
378
- child_node.named?
379
- elsif child_node.respond_to?(:is_named?)
380
- child_node.is_named?
381
- else
382
- true # Assume named if we can't determine
383
- end
378
+ child_node.named?
379
+ elsif child_node.respond_to?(:is_named?)
380
+ child_node.is_named?
381
+ else
382
+ true # Assume named if we can't determine
383
+ end
384
384
 
385
385
  count += 1 if is_named
386
386
  end
@@ -408,6 +408,7 @@ module TreeHaver
408
408
  # @return [Enumerator, nil]
409
409
  def each(&block)
410
410
  return to_enum(__method__) unless block_given?
411
+
411
412
  children.each(&block)
412
413
  end
413
414
 
@@ -419,6 +420,7 @@ module TreeHaver
419
420
  if @inner_node.respond_to?(:child_by_field_name)
420
421
  child_node = @inner_node.child_by_field_name(name.to_s)
421
422
  return if child_node.nil?
423
+
422
424
  Node.new(child_node, source: @source)
423
425
  else
424
426
  # Not all backends support field names
@@ -427,15 +429,17 @@ module TreeHaver
427
429
  end
428
430
 
429
431
  # Alias for child_by_field_name
430
- alias_method :field, :child_by_field_name
432
+ alias field child_by_field_name
431
433
 
432
434
  # Get the parent node
433
435
  #
434
436
  # @return [Node, nil] The parent node
435
437
  def parent
436
438
  return unless @inner_node.respond_to?(:parent)
439
+
437
440
  parent_node = @inner_node.parent
438
441
  return if parent_node.nil?
442
+
439
443
  Node.new(parent_node, source: @source)
440
444
  end
441
445
 
@@ -444,8 +448,10 @@ module TreeHaver
444
448
  # @return [Node, nil]
445
449
  def next_sibling
446
450
  return unless @inner_node.respond_to?(:next_sibling)
451
+
447
452
  sibling = @inner_node.next_sibling
448
453
  return if sibling.nil?
454
+
449
455
  Node.new(sibling, source: @source)
450
456
  end
451
457
 
@@ -454,8 +460,10 @@ module TreeHaver
454
460
  # @return [Node, nil]
455
461
  def prev_sibling
456
462
  return unless @inner_node.respond_to?(:prev_sibling)
463
+
457
464
  sibling = @inner_node.prev_sibling
458
465
  return if sibling.nil?
466
+
459
467
  Node.new(sibling, source: @source)
460
468
  end
461
469
 
@@ -512,11 +520,12 @@ module TreeHaver
512
520
  # @return [Boolean] true if both nodes wrap the same inner_node
513
521
  def ==(other)
514
522
  return false unless other.is_a?(Node)
523
+
515
524
  @inner_node == other.inner_node
516
525
  end
517
526
 
518
527
  # Alias for == to support both styles
519
- alias_method :eql?, :==
528
+ alias eql? ==
520
529
 
521
530
  # Generate hash value for this node
522
531
  #
@@ -82,7 +82,7 @@ module TreeHaver
82
82
  # @example Explicit backend
83
83
  # parser = TreeHaver::Parser.new(backend: :citrus)
84
84
  def initialize(backend: nil)
85
- super() # Initialize @language from Base::Parser
85
+ super() # Initialize @language from Base::Parser
86
86
 
87
87
  # Convert string backend names to symbols for consistency
88
88
  backend = backend.to_sym if backend.is_a?(String)
@@ -90,20 +90,19 @@ module TreeHaver
90
90
  mod = TreeHaver.resolve_backend_module(backend)
91
91
 
92
92
  if mod.nil?
93
- if backend
94
- raise NotAvailable, "Requested backend #{backend.inspect} is not available"
95
- else
96
- raise NotAvailable, "No TreeHaver backend is available"
97
- end
93
+ raise NotAvailable, "Requested backend #{backend.inspect} is not available" if backend
94
+
95
+ raise NotAvailable, 'No TreeHaver backend is available'
96
+
98
97
  end
99
98
 
100
99
  # Try to create the parser, with fallback to pure Ruby if tree-sitter fails
101
100
  # This enables auto-fallback when tree-sitter runtime isn't available
102
101
  begin
103
102
  @impl = mod::Parser.new
104
- @explicit_backend = backend # Remember for introspection (always a Symbol or nil)
103
+ @explicit_backend = backend # Remember for introspection (always a Symbol or nil)
105
104
  rescue NoMethodError, LoadError => e
106
- # Note: FFI::NotFoundError inherits from LoadError, so it's caught here too
105
+ # NOTE: FFI::NotFoundError inherits from LoadError, so it's caught here too
107
106
  handle_parser_creation_failure(e, backend)
108
107
  end
109
108
  end
@@ -117,22 +116,21 @@ module TreeHaver
117
116
  def handle_parser_creation_failure(error, backend)
118
117
  # Tree-sitter backend failed (likely missing runtime library)
119
118
  # Try Citrus or Parslet as fallback if we weren't explicitly asked for a specific backend
120
- if backend.nil? || backend == :auto
121
- if Backends::Citrus.available?
122
- @impl = Backends::Citrus::Parser.new
123
- @explicit_backend = :citrus
124
- elsif Backends::Parslet.available?
125
- @impl = Backends::Parslet::Parser.new
126
- @explicit_backend = :parslet
127
- else
128
- # No fallback available, re-raise original error
129
- raise NotAvailable, "Tree-sitter backend failed: #{error.message}. " \
130
- "Citrus/Parslet fallback not available. Install tree-sitter runtime, citrus gem, or parslet gem."
131
- end
119
+ raise error unless backend.nil? || backend == :auto
120
+
121
+ if Backends::Citrus.available?
122
+ @impl = Backends::Citrus::Parser.new
123
+ @explicit_backend = :citrus
124
+ elsif Backends::Parslet.available?
125
+ @impl = Backends::Parslet::Parser.new
126
+ @explicit_backend = :parslet
132
127
  else
133
- # Explicit backend was requested, don't fallback
134
- raise error
128
+ # No fallback available, re-raise original error
129
+ raise NotAvailable, "Tree-sitter backend failed: #{error.message}. " \
130
+ 'Citrus/Parslet fallback not available. Install tree-sitter runtime, citrus gem, or parslet gem.'
135
131
  end
132
+
133
+ # Explicit backend was requested, don't fallback
136
134
  end
137
135
 
138
136
  # Get the backend this parser is using (for introspection)
@@ -234,20 +232,27 @@ module TreeHaver
234
232
  # @example First parse (no old tree)
235
233
  # tree = parser.parse_string(nil, "x = 1")
236
234
  # @example Incremental parse
237
- # tree.edit(start_byte: 4, old_end_byte: 5, new_end_byte: 6, ...)
235
+ # tree.edit(
236
+ # start_byte: 4,
237
+ # old_end_byte: 5,
238
+ # new_end_byte: 6,
239
+ # start_point: {row: 0, column: 4},
240
+ # old_end_point: {row: 0, column: 5},
241
+ # new_end_point: {row: 0, column: 6}
242
+ # )
238
243
  # new_tree = parser.parse_string(tree, "x = 42")
239
244
  def parse_string(old_tree, source)
240
245
  # Pass through to backend if it supports incremental parsing
241
246
  if old_tree && @impl.respond_to?(:parse_string)
242
247
  # Extract the underlying implementation from our Tree wrapper
243
248
  old_impl = if old_tree.respond_to?(:inner_tree)
244
- old_tree.inner_tree
245
- elsif old_tree.respond_to?(:instance_variable_get)
246
- # Fallback for compatibility
247
- old_tree.instance_variable_get(:@inner_tree) || old_tree.instance_variable_get(:@impl) || old_tree
248
- else
249
- old_tree
250
- end
249
+ old_tree.inner_tree
250
+ elsif old_tree.respond_to?(:instance_variable_get)
251
+ # Fallback for compatibility
252
+ old_tree.instance_variable_get(:@inner_tree) || old_tree.instance_variable_get(:@impl) || old_tree
253
+ else
254
+ old_tree
255
+ end
251
256
  tree_impl = @impl.parse_string(old_impl, source)
252
257
  # Wrap backend tree with source so Node#text works
253
258
  Tree.new(tree_impl, source: source)
@@ -335,9 +340,9 @@ module TreeHaver
335
340
  else
336
341
  # Couldn't reload - this is an error
337
342
  raise TreeHaver::Error,
338
- "Language backend mismatch: language is for #{lang.backend}, parser is #{current_backend}. " \
339
- "Cannot reload language for correct backend. " \
340
- "Create a new language with TreeHaver::Language.from_library when backend is #{current_backend}."
343
+ "Language backend mismatch: language is for #{lang.backend}, parser is #{current_backend}. " \
344
+ 'Cannot reload language for correct backend. ' \
345
+ "Create a new language with TreeHaver::Language.from_library when backend is #{current_backend}."
341
346
  end
342
347
  end
343
348
 
@@ -358,17 +363,19 @@ module TreeHaver
358
363
  # This shouldn't happen - all our wrappers have backend attribute
359
364
  # If we get here, it's likely a raw backend object that was passed directly
360
365
  raise TreeHaver::Error,
361
- "Expected TreeHaver Language wrapper with backend attribute, got #{lang.class}. " \
362
- "Use TreeHaver::Language.from_library to create language objects."
366
+ "Expected TreeHaver Language wrapper with backend attribute, got #{lang.class}. " \
367
+ 'Use TreeHaver::Language.from_library to create language objects.'
363
368
  end
364
369
 
365
370
  case lang.backend
366
371
  when :mri
367
372
  return lang.to_language if lang.respond_to?(:to_language)
368
373
  return lang.inner_language if lang.respond_to?(:inner_language)
374
+
369
375
  lang
370
376
  when :rust
371
377
  return lang.name if lang.respond_to?(:name)
378
+
372
379
  lang
373
380
  when :ffi
374
381
  lang # FFI needs wrapper for to_ptr
@@ -422,9 +429,9 @@ module TreeHaver
422
429
  return Language.from_library(
423
430
  lang.path,
424
431
  symbol: lang.respond_to?(:symbol) ? lang.symbol : nil,
425
- name: lang.respond_to?(:name) ? lang.name : nil,
432
+ name: lang.respond_to?(:name) ? lang.name : nil
426
433
  )
427
- rescue => e
434
+ rescue StandardError => e
428
435
  # Reload failed, continue with original
429
436
  warn("TreeHaver: Failed to reload language for backend #{target_backend}: #{e.message}") if $VERBOSE
430
437
  return
@@ -68,7 +68,7 @@ module TreeHaver
68
68
  return @available if @load_attempted
69
69
 
70
70
  @load_attempted = true
71
- debug = ENV["TREE_HAVER_DEBUG"]
71
+ debug = ENV['TREE_HAVER_DEBUG']
72
72
 
73
73
  # Guard against nil require_path (can happen if gem_name was nil)
74
74
  if @require_path.nil? || @require_path.empty?
@@ -96,38 +96,34 @@ module TreeHaver
96
96
 
97
97
  @available = true
98
98
  rescue LoadError => e
99
- # :nocov: defensive - requires gem to not be installed
99
+ # simplecov:disable defensive - requires gem to not be installed
100
100
  if debug
101
101
  warn("ParsletGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
102
102
  warn("ParsletGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
103
103
  end
104
104
  @available = false
105
- # :nocov:
105
+ # simplecov:enable
106
106
  rescue NameError => e
107
- # :nocov: defensive - requires gem with missing constant
107
+ # simplecov:disable defensive - requires gem with missing constant
108
108
  if debug
109
109
  warn("ParsletGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
110
110
  warn("ParsletGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
111
111
  end
112
112
  @available = false
113
- # :nocov:
113
+ # simplecov:enable
114
114
  rescue TypeError => e
115
- # :nocov: defensive - TruffleRuby-specific edge case
115
+ # simplecov:disable defensive - TruffleRuby-specific edge case
116
116
  warn("ParsletGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
117
- warn("ParsletGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue")
118
- if debug
119
- warn("ParsletGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
120
- end
117
+ warn('ParsletGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue')
118
+ warn("ParsletGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
121
119
  @available = false
122
- # :nocov:
123
- rescue => e
124
- # :nocov: defensive - catch-all for unexpected errors
120
+ # simplecov:enable
121
+ rescue StandardError => e
122
+ # simplecov:disable defensive - catch-all for unexpected errors
125
123
  warn("ParsletGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
126
- if debug
127
- warn("ParsletGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
128
- end
124
+ warn("ParsletGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
129
125
  @available = false
130
- # :nocov:
126
+ # simplecov:enable
131
127
  end
132
128
 
133
129
  @available
@@ -151,16 +147,15 @@ module TreeHaver
151
147
  # @raise [NotAvailable] if grammar not available and raise_on_missing is true
152
148
  def register!(raise_on_missing: false)
153
149
  unless available?
154
- if raise_on_missing
155
- raise NotAvailable, not_found_message
156
- end
150
+ raise NotAvailable, not_found_message if raise_on_missing
151
+
157
152
  return false
158
153
  end
159
154
 
160
155
  TreeHaver.register_language(
161
156
  @language_name,
162
157
  grammar_class: @grammar_class,
163
- gem_name: @gem_name,
158
+ gem_name: @gem_name
164
159
  )
165
160
  true
166
161
  end
@@ -175,7 +170,7 @@ module TreeHaver
175
170
  grammar_const: @grammar_const,
176
171
  require_path: @require_path,
177
172
  available: available?,
178
- grammar_class: @grammar_class&.name,
173
+ grammar_class: @grammar_class&.name
179
174
  }
180
175
  end
181
176
 
@@ -195,7 +190,7 @@ module TreeHaver
195
190
  # @return [Object] the constant
196
191
  # @raise [NameError] if constant not found
197
192
  def resolve_constant(const_path)
198
- const_path.split("::").reduce(Object) do |mod, const_name|
193
+ const_path.split('::').reduce(Object) do |mod, const_name|
199
194
  mod.const_get(const_name)
200
195
  end
201
196
  end
@@ -208,9 +203,7 @@ module TreeHaver
208
203
  return false unless klass.respond_to?(:new)
209
204
 
210
205
  # Check if it's a Parslet::Parser subclass
211
- if defined?(::Parslet::Parser)
212
- return true if klass < ::Parslet::Parser
213
- end
206
+ return true if defined?(::Parslet::Parser) && (klass < ::Parslet::Parser)
214
207
 
215
208
  # Fallback: check if it can create an instance that responds to parse
216
209
  begin