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
@@ -28,6 +28,19 @@ module TreeHaver
28
28
  # TreeHaver::BackendAPI.validate_node!(node_instance)
29
29
  #
30
30
  module BackendAPI
31
+ # Descriptive levels for backend comment support.
32
+ #
33
+ # - :full - backend can expose comment nodes plus strong attachment hints
34
+ # - :partial - backend can expose comments, but ownership/attachment is incomplete
35
+ # - :nodes_only - backend can surface comment nodes/tokens only
36
+ # - :none - backend does not expose comments through TreeHaver
37
+ COMMENT_SUPPORT_LEVELS = %i[
38
+ full
39
+ partial
40
+ nodes_only
41
+ none
42
+ ].freeze
43
+
31
44
  # Required methods for Language class/instances
32
45
  #
33
46
  # All backends MUST implement `from_library` for API consistency.
@@ -120,7 +133,7 @@ module TreeHaver
120
133
  has_error?: %i[has_error],
121
134
  missing?: %i[is_missing? is_missing],
122
135
  next_sibling: %i[next_named_sibling],
123
- prev_sibling: %i[previous_sibling previous_named_sibling prev_named_sibling],
136
+ prev_sibling: %i[previous_sibling previous_named_sibling prev_named_sibling]
124
137
  }.freeze
125
138
 
126
139
  class << self
@@ -134,7 +147,7 @@ module TreeHaver
134
147
  valid: true,
135
148
  errors: [],
136
149
  warnings: [],
137
- capabilities: {},
150
+ capabilities: {}
138
151
  }
139
152
 
140
153
  # Check module-level methods
@@ -144,7 +157,7 @@ module TreeHaver
144
157
  if backend_module.const_defined?(:Language)
145
158
  validate_language(backend_module::Language, results)
146
159
  else
147
- results[:errors] << "Missing Language class"
160
+ results[:errors] << 'Missing Language class'
148
161
  results[:valid] = false
149
162
  end
150
163
 
@@ -152,7 +165,7 @@ module TreeHaver
152
165
  if backend_module.const_defined?(:Parser)
153
166
  validate_parser(backend_module::Parser, results)
154
167
  else
155
- results[:errors] << "Missing Parser class"
168
+ results[:errors] << 'Missing Parser class'
156
169
  results[:valid] = false
157
170
  end
158
171
 
@@ -160,20 +173,18 @@ module TreeHaver
160
173
  if backend_module.const_defined?(:Tree)
161
174
  validate_tree(backend_module::Tree, results)
162
175
  else
163
- results[:warnings] << "No Tree class (backend returns raw trees)"
176
+ results[:warnings] << 'No Tree class (backend returns raw trees)'
164
177
  end
165
178
 
166
179
  # Check Node class if present (wrapper backends)
167
180
  if backend_module.const_defined?(:Node)
168
181
  validate_node_class(backend_module::Node, results, strict: strict)
169
182
  else
170
- results[:warnings] << "No Node class (backend returns raw nodes, TreeHaver::Node will wrap)"
183
+ results[:warnings] << 'No Node class (backend returns raw nodes, TreeHaver::Node will wrap)'
171
184
  end
172
185
 
173
186
  # Fail on warnings in strict mode
174
- if strict && results[:warnings].any?
175
- results[:valid] = false
176
- end
187
+ results[:valid] = false if strict && results[:warnings].any?
177
188
 
178
189
  results
179
190
  end
@@ -188,9 +199,9 @@ module TreeHaver
188
199
  results = validate(backend_module, strict: strict)
189
200
  unless results[:valid]
190
201
  raise TreeHaver::Error,
191
- "Backend #{backend_module.name} API validation failed:\n " \
192
- "Errors: #{results[:errors].join(", ")}\n " \
193
- "Warnings: #{results[:warnings].join(", ")}"
202
+ "Backend #{backend_module.name} API validation failed:\n " \
203
+ "Errors: #{results[:errors].join(', ')}\n " \
204
+ "Warnings: #{results[:warnings].join(', ')}"
194
205
  end
195
206
  results
196
207
  end
@@ -205,7 +216,7 @@ module TreeHaver
205
216
  errors: [],
206
217
  warnings: [],
207
218
  supported_methods: [],
208
- unsupported_methods: [],
219
+ unsupported_methods: []
209
220
  }
210
221
 
211
222
  # Check required methods
@@ -235,13 +246,51 @@ module TreeHaver
235
246
 
236
247
  def validate_module_methods(mod, results)
237
248
  unless mod.singleton_class.method_defined?(:available?)
238
- results[:errors] << "Missing module method: available?"
249
+ results[:errors] << 'Missing module method: available?'
239
250
  results[:valid] = false
240
251
  end
241
252
 
242
253
  unless mod.singleton_class.method_defined?(:capabilities)
243
- results[:warnings] << "Missing module method: capabilities"
254
+ results[:warnings] << 'Missing module method: capabilities'
255
+ return
244
256
  end
257
+
258
+ validate_capabilities_hash(mod.capabilities, results)
259
+ end
260
+
261
+ def validate_capabilities_hash(capabilities, results)
262
+ return if capabilities.nil? || capabilities.empty?
263
+
264
+ unless capabilities.is_a?(Hash)
265
+ results[:errors] << 'Backend capabilities must return a Hash'
266
+ results[:valid] = false
267
+ return
268
+ end
269
+
270
+ comment_support = capabilities[:comment_support]
271
+ if comment_support.nil?
272
+ results[:warnings] << 'Capabilities missing :comment_support'
273
+ return
274
+ end
275
+
276
+ unless COMMENT_SUPPORT_LEVELS.include?(comment_support)
277
+ results[:errors] << "Invalid :comment_support #{comment_support.inspect}; expected one of #{COMMENT_SUPPORT_LEVELS.inspect}"
278
+ results[:valid] = false
279
+ return
280
+ end
281
+
282
+ results[:capabilities][:comment_support] = comment_support
283
+
284
+ return unless capabilities.key?(:comment_attachment_hints)
285
+
286
+ attachment_hints = capabilities[:comment_attachment_hints]
287
+ unless [true, false].include?(attachment_hints)
288
+ results[:errors] << "Invalid :comment_attachment_hints #{attachment_hints.inspect}; expected true or false"
289
+ results[:valid] = false
290
+ return
291
+ end
292
+
293
+ results[:capabilities][:comment_attachment_hints] = attachment_hints
245
294
  end
246
295
 
247
296
  def validate_language(klass, results)
@@ -249,19 +298,17 @@ module TreeHaver
249
298
  # Language-specific backends should implement it to ignore path/symbol
250
299
  # and return their single language (for API consistency)
251
300
  unless klass.singleton_class.method_defined?(:from_library)
252
- results[:errors] << "Language missing required class method: from_library"
301
+ results[:errors] << 'Language missing required class method: from_library'
253
302
  results[:valid] = false
254
303
  end
255
304
 
256
305
  # Check for optional convenience methods
257
306
  optional_methods = LANGUAGE_OPTIONAL_CLASS_METHODS.select { |m| klass.singleton_class.method_defined?(m) }
258
- if optional_methods.any?
259
- results[:capabilities][:language_shortcuts] = optional_methods
260
- end
307
+ results[:capabilities][:language_shortcuts] = optional_methods if optional_methods.any?
261
308
 
262
309
  results[:capabilities][:language] = {
263
310
  class_methods: LANGUAGE_CLASS_METHODS.select { |m| klass.singleton_class.method_defined?(m) } +
264
- optional_methods,
311
+ optional_methods
265
312
  }
266
313
  end
267
314
 
@@ -282,9 +329,7 @@ module TreeHaver
282
329
  end
283
330
 
284
331
  PARSER_OPTIONAL_METHODS.each do |method|
285
- unless klass.method_defined?(method)
286
- results[:warnings] << "Parser missing optional method: #{method}"
287
- end
332
+ results[:warnings] << "Parser missing optional method: #{method}" unless klass.method_defined?(method)
288
333
  end
289
334
  end
290
335
 
@@ -297,9 +342,7 @@ module TreeHaver
297
342
  end
298
343
 
299
344
  TREE_OPTIONAL_METHODS.each do |method|
300
- unless klass.method_defined?(method)
301
- results[:warnings] << "Tree missing optional method: #{method}"
302
- end
345
+ results[:warnings] << "Tree missing optional method: #{method}" unless klass.method_defined?(method)
303
346
  end
304
347
  end
305
348
 
@@ -312,20 +355,20 @@ module TreeHaver
312
355
  end
313
356
 
314
357
  NODE_OPTIONAL_METHODS.each do |method|
315
- unless has_method_or_alias?(klass, method)
316
- msg = "Node missing optional method: #{method}"
317
- if strict
318
- results[:errors] << msg
319
- results[:valid] = false
320
- else
321
- results[:warnings] << msg
322
- end
358
+ next if has_method_or_alias?(klass, method)
359
+
360
+ msg = "Node missing optional method: #{method}"
361
+ if strict
362
+ results[:errors] << msg
363
+ results[:valid] = false
364
+ else
365
+ results[:warnings] << msg
323
366
  end
324
367
  end
325
368
 
326
369
  results[:capabilities][:node] = {
327
370
  required: NODE_INSTANCE_METHODS.select { |m| has_method_or_alias?(klass, m) },
328
- optional: NODE_OPTIONAL_METHODS.select { |m| has_method_or_alias?(klass, m) },
371
+ optional: NODE_OPTIONAL_METHODS.select { |m| has_method_or_alias?(klass, m) }
329
372
  }
330
373
  end
331
374
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeHaver
4
+ BACKEND_CONTEXT_KEY = :structured_merge_tree_haver_backend
5
+
6
+ module_function
7
+
8
+ def current_backend_id
9
+ Thread.current[BACKEND_CONTEXT_KEY]
10
+ end
11
+
12
+ def with_backend(backend_id)
13
+ validate_backend_id!(backend_id)
14
+
15
+ previous_backend = Thread.current[BACKEND_CONTEXT_KEY]
16
+ Thread.current[BACKEND_CONTEXT_KEY] = backend_id.to_s
17
+ yield
18
+ ensure
19
+ Thread.current[BACKEND_CONTEXT_KEY] = previous_backend
20
+ end
21
+
22
+ def validate_backend_id!(backend_id)
23
+ return if BackendRegistry.fetch(backend_id.to_s)
24
+
25
+ raise ArgumentError, "Unknown tree_haver backend #{backend_id}."
26
+ end
27
+ private_class_method :validate_backend_id!
28
+ end