tree_haver 7.0.0 → 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 (44) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +1959 -0
  5. data/lib/tree_haver/backend_api.rb +392 -0
  6. data/lib/tree_haver/backend_registry.rb +153 -3
  7. data/lib/tree_haver/backends/citrus.rb +489 -0
  8. data/lib/tree_haver/backends/ffi.rb +1013 -0
  9. data/lib/tree_haver/backends/java.rb +909 -0
  10. data/lib/tree_haver/backends/mri.rb +367 -0
  11. data/lib/tree_haver/backends/parslet.rb +565 -0
  12. data/lib/tree_haver/backends/prism.rb +568 -0
  13. data/lib/tree_haver/backends/psych.rb +379 -0
  14. data/lib/tree_haver/backends/rust.rb +243 -0
  15. data/lib/tree_haver/backends/tslp.rb +274 -0
  16. data/lib/tree_haver/base/comment.rb +320 -0
  17. data/lib/tree_haver/base/language.rb +98 -0
  18. data/lib/tree_haver/base/node.rb +330 -0
  19. data/lib/tree_haver/base/parser.rb +28 -0
  20. data/lib/tree_haver/base/point.rb +48 -0
  21. data/lib/tree_haver/base/tree.rb +128 -0
  22. data/lib/tree_haver/citrus_grammar_finder.rb +213 -0
  23. data/lib/tree_haver/contracts.rb +661 -96
  24. data/lib/tree_haver/grammar_finder.rb +429 -0
  25. data/lib/tree_haver/kaitai_backend.rb +2 -2
  26. data/lib/tree_haver/language.rb +294 -0
  27. data/lib/tree_haver/language_pack.rb +17 -166
  28. data/lib/tree_haver/language_registry.rb +221 -0
  29. data/lib/tree_haver/library_path_utils.rb +80 -0
  30. data/lib/tree_haver/node.rb +588 -0
  31. data/lib/tree_haver/parser.rb +445 -0
  32. data/lib/tree_haver/parslet_grammar_finder.rb +217 -0
  33. data/lib/tree_haver/path_validator.rb +356 -0
  34. data/lib/tree_haver/peg_backends.rb +7 -7
  35. data/lib/tree_haver/point.rb +27 -0
  36. data/lib/tree_haver/rspec/dependency_tags.rb +52 -0
  37. data/lib/tree_haver/rspec.rb +3 -0
  38. data/lib/tree_haver/tree.rb +267 -0
  39. data/lib/tree_haver/version.rb +5 -3
  40. data/lib/tree_haver.rb +613 -8
  41. data/sig/tree_haver.rbs +6 -0
  42. data.tar.gz.sig +0 -0
  43. metadata +314 -13
  44. metadata.gz.sig +0 -0
@@ -51,42 +51,236 @@ module TreeHaver
51
51
  end
52
52
  end
53
53
 
54
- ParserDiagnostics = Struct.new(:backend, :backend_ref, :diagnostics, keyword_init: true) do
54
+ ParserIdentity = Struct.new(:name, :version, :implementation, keyword_init: true) do
55
55
  def to_h
56
56
  {
57
- backend: backend,
58
- **(backend_ref ? { backend_ref: backend_ref.to_h } : {}),
59
- diagnostics: deep_dup(diagnostics || [])
57
+ name: name,
58
+ version: version,
59
+ implementation: implementation
60
60
  }
61
61
  end
62
+ end
62
63
 
63
- private
64
+ LanguageVersion = Struct.new(:version, :dialect, keyword_init: true) do
65
+ def to_h
66
+ {
67
+ version: version,
68
+ dialect: dialect
69
+ }
70
+ end
71
+ end
64
72
 
65
- def deep_dup(value)
66
- Marshal.load(Marshal.dump(value))
73
+ BackendCapability = Struct.new(
74
+ :backend_ref,
75
+ :language,
76
+ :parser_identity,
77
+ :language_version,
78
+ :parse_error_behavior,
79
+ :source_span_support,
80
+ :source_fragment_support,
81
+ :render_strategies,
82
+ :semantic_role_support,
83
+ :normalized_tree_support,
84
+ :native_node_access,
85
+ :known_node_kinds,
86
+ :known_fields,
87
+ :grammar_inventory,
88
+ :diagnostics,
89
+ keyword_init: true
90
+ ) do
91
+ def to_h
92
+ {
93
+ backend_ref: backend_ref.to_h,
94
+ language: language,
95
+ parser_identity: parser_identity.to_h,
96
+ language_version: language_version.to_h,
97
+ parse_error_behavior: parse_error_behavior,
98
+ source_span_support: source_span_support,
99
+ source_fragment_support: source_fragment_support,
100
+ render_strategies: render_strategies,
101
+ semantic_role_support: semantic_role_support,
102
+ normalized_tree_support: normalized_tree_support,
103
+ native_node_access: native_node_access,
104
+ known_node_kinds: known_node_kinds || [],
105
+ known_fields: known_fields || [],
106
+ grammar_inventory: grammar_inventory,
107
+ diagnostics: diagnostics
108
+ }
67
109
  end
68
110
  end
69
111
 
70
- ProcessRequest = Struct.new(:source, :language, keyword_init: true) do
112
+ ParseErrorNode = Struct.new(:kind, :span, :message, keyword_init: true) do
71
113
  def to_h
72
114
  {
73
- source: source,
74
- language: language
115
+ kind: kind,
116
+ span: span.to_h,
117
+ message: message
75
118
  }
76
119
  end
77
120
  end
78
121
 
79
- ProcessSpan = Struct.new(:start_byte, :end_byte, :start_row, :start_col, :end_row, :end_col, keyword_init: true) do
122
+ ParseErrorTolerance = Struct.new(:backend_ref, :language, :behavior, :tolerates_errors, :error_nodes, :diagnostics,
123
+ keyword_init: true) do
80
124
  def to_h
81
125
  {
82
- start_byte: start_byte,
83
- end_byte: end_byte,
84
- start_row: start_row,
85
- start_col: start_col,
86
- end_row: end_row,
87
- end_col: end_col
126
+ backend_ref: backend_ref.to_h,
127
+ language: language,
128
+ behavior: behavior,
129
+ tolerates_errors: tolerates_errors,
130
+ error_nodes: (error_nodes || []).map(&:to_h),
131
+ diagnostics: diagnostics
132
+ }
133
+ end
134
+ end
135
+
136
+ NativeParserProvider = Struct.new(:id, :family, :language, :operations, :retains_native_tree,
137
+ :native_tree_visibility, :metadata_policy, keyword_init: true) do
138
+ def to_h
139
+ {
140
+ id: id,
141
+ family: family,
142
+ language: language,
143
+ operations: operations || [],
144
+ retains_native_tree: retains_native_tree,
145
+ native_tree_visibility: native_tree_visibility,
146
+ metadata_policy: metadata_policy
147
+ }
148
+ end
149
+ end
150
+
151
+ NativeProviderMetadata = Struct.new(
152
+ :provider_id,
153
+ :family,
154
+ :host_language,
155
+ :target_language,
156
+ :parser_name,
157
+ :parser_version,
158
+ :language_version,
159
+ :dialect,
160
+ :parse_error_behavior,
161
+ :source_span_support,
162
+ :render_support,
163
+ :semantic_role_support,
164
+ :retains_native_tree,
165
+ :native_tree_visibility,
166
+ :metadata_policy,
167
+ :diagnostics,
168
+ keyword_init: true
169
+ ) do
170
+ def to_h
171
+ {
172
+ provider_id: provider_id,
173
+ family: family,
174
+ host_language: host_language,
175
+ target_language: target_language,
176
+ parser_name: parser_name,
177
+ parser_version: parser_version,
178
+ language_version: language_version,
179
+ dialect: dialect,
180
+ parse_error_behavior: parse_error_behavior,
181
+ source_span_support: source_span_support,
182
+ render_support: render_support,
183
+ semantic_role_support: semantic_role_support,
184
+ retains_native_tree: retains_native_tree,
185
+ native_tree_visibility: native_tree_visibility,
186
+ metadata_policy: metadata_policy,
187
+ diagnostics: diagnostics || []
188
+ }
189
+ end
190
+ end
191
+
192
+ NormalizedParseResult = Struct.new(
193
+ :ok,
194
+ :backend_capability,
195
+ :root_id,
196
+ :nodes,
197
+ :parse_error_tolerance,
198
+ :source_fragments_available,
199
+ :diagnostics,
200
+ :metadata,
201
+ keyword_init: true
202
+ ) do
203
+ def to_h
204
+ {
205
+ ok: ok,
206
+ backend_capability: backend_capability.to_h,
207
+ root_id: root_id,
208
+ nodes: (nodes || []).map(&:to_h),
209
+ parse_error_tolerance: parse_error_tolerance.to_h,
210
+ source_fragments_available: source_fragments_available,
211
+ diagnostics: diagnostics || [],
212
+ metadata: metadata || {}
213
+ }
214
+ end
215
+ end
216
+
217
+ TreeHaverProfile = Struct.new(
218
+ :profile_id,
219
+ :language,
220
+ :backend_ref,
221
+ :provider_id,
222
+ :node_roles,
223
+ :normalized_node_fields,
224
+ :optional_node_features,
225
+ :unsupported_defaults,
226
+ :capability,
227
+ :fixture_slices,
228
+ :diagnostics,
229
+ keyword_init: true
230
+ ) do
231
+ def to_h
232
+ {
233
+ profile_id: profile_id,
234
+ language: language,
235
+ backend_ref: backend_ref.to_h,
236
+ provider_id: provider_id,
237
+ node_roles: node_roles || [],
238
+ normalized_node_fields: normalized_node_fields || [],
239
+ optional_node_features: optional_node_features || [],
240
+ unsupported_defaults: unsupported_defaults || {},
241
+ capability: capability.to_h,
242
+ fixture_slices: fixture_slices || [],
243
+ diagnostics: diagnostics || []
244
+ }
245
+ end
246
+ end
247
+
248
+ OrderedSiblingEdge = Struct.new(:parent_id, :node_id, :previous_sibling_id, :next_sibling_id, keyword_init: true) do
249
+ def to_h
250
+ {
251
+ parent_id: parent_id,
252
+ node_id: node_id,
253
+ previous_sibling_id: previous_sibling_id,
254
+ next_sibling_id: next_sibling_id
255
+ }
256
+ end
257
+ end
258
+
259
+ OrderedTreePrimitives = Struct.new(:root_id, :child_order, :sibling_edges, :diagnostics, keyword_init: true) do
260
+ def to_h
261
+ {
262
+ root_id: root_id,
263
+ child_order: child_order || {},
264
+ sibling_edges: (sibling_edges || []).map(&:to_h),
265
+ diagnostics: diagnostics || []
266
+ }
267
+ end
268
+ end
269
+
270
+ ParserDiagnostics = Struct.new(:backend, :backend_ref, :diagnostics, keyword_init: true) do
271
+ def to_h
272
+ {
273
+ backend: backend,
274
+ **(backend_ref ? { backend_ref: backend_ref.to_h } : {}),
275
+ diagnostics: deep_dup(diagnostics || [])
88
276
  }
89
277
  end
278
+
279
+ private
280
+
281
+ def deep_dup(value)
282
+ Marshal.load(Marshal.dump(value))
283
+ end
90
284
  end
91
285
 
92
286
  ByteRange = Struct.new(:start_byte, :end_byte, keyword_init: true) do
@@ -118,6 +312,199 @@ module TreeHaver
118
312
  end
119
313
  end
120
314
 
315
+ EditProjectionSupport = Struct.new(
316
+ :backend_ref,
317
+ :language,
318
+ :supports_edit_projection,
319
+ :native_edit_target,
320
+ :normalized_edit_target,
321
+ :supported_operations,
322
+ :required_node_fields,
323
+ :correlation_keys,
324
+ :preserves_source_fragments,
325
+ :unsupported_reason,
326
+ :diagnostics,
327
+ keyword_init: true
328
+ ) do
329
+ def to_h
330
+ {
331
+ backend_ref: backend_ref.to_h,
332
+ language: language,
333
+ supports_edit_projection: supports_edit_projection,
334
+ native_edit_target: native_edit_target,
335
+ normalized_edit_target: normalized_edit_target,
336
+ supported_operations: supported_operations || [],
337
+ required_node_fields: required_node_fields || [],
338
+ correlation_keys: correlation_keys || [],
339
+ preserves_source_fragments: preserves_source_fragments,
340
+ unsupported_reason: unsupported_reason,
341
+ diagnostics: diagnostics || []
342
+ }
343
+ end
344
+ end
345
+
346
+ LibraryPathValidation = Struct.new(:path, :valid, :errors, keyword_init: true) do
347
+ def to_h
348
+ {
349
+ path: path,
350
+ valid: valid,
351
+ errors: errors || []
352
+ }
353
+ end
354
+ end
355
+
356
+ BackendAvailabilityCheck = Struct.new(:name, :status, :required, :diagnostics, keyword_init: true) do
357
+ def to_h
358
+ {
359
+ name: name,
360
+ status: status,
361
+ required: required,
362
+ diagnostics: diagnostics || []
363
+ }
364
+ end
365
+ end
366
+
367
+ BackendAvailabilityReport = Struct.new(:backend_ref, :status, :checks, :diagnostics, keyword_init: true) do
368
+ def to_h
369
+ {
370
+ backend_ref: backend_ref.to_h,
371
+ status: status,
372
+ checks: (checks || []).map(&:to_h),
373
+ diagnostics: diagnostics || []
374
+ }
375
+ end
376
+ end
377
+
378
+ ProviderDiagnostic = Struct.new(:severity, :category, :code, :message, :path, :blocking, keyword_init: true) do
379
+ def to_h
380
+ {
381
+ severity: severity,
382
+ category: category,
383
+ code: code,
384
+ message: message,
385
+ path: path,
386
+ blocking: blocking
387
+ }
388
+ end
389
+ end
390
+
391
+ ProviderDiagnosticsReport = Struct.new(:provider_id, :backend_ref, :language, :status, :diagnostics,
392
+ keyword_init: true) do
393
+ def to_h
394
+ {
395
+ provider_id: provider_id,
396
+ backend_ref: backend_ref.to_h,
397
+ language: language,
398
+ status: status,
399
+ diagnostics: (diagnostics || []).map(&:to_h)
400
+ }
401
+ end
402
+ end
403
+
404
+ EditProjectionOperationRequest = Struct.new(:operation, :target_node_id, :target_node_path, :replacement_source,
405
+ keyword_init: true) do
406
+ def to_h
407
+ {
408
+ operation: operation,
409
+ target_node_id: target_node_id,
410
+ target_node_path: target_node_path,
411
+ replacement_source: replacement_source
412
+ }
413
+ end
414
+ end
415
+
416
+ EditProjectionExecutionRequest = Struct.new(:provider_id, :backend_ref, :language, :source, :operations,
417
+ keyword_init: true) do
418
+ def to_h
419
+ {
420
+ provider_id: provider_id,
421
+ backend_ref: backend_ref.to_h,
422
+ language: language,
423
+ source: source,
424
+ operations: (operations || []).map(&:to_h)
425
+ }
426
+ end
427
+ end
428
+
429
+ AppliedEditProjectionOperation = Struct.new(:operation, :target_node_id, :correlation_key, :correlation_value,
430
+ keyword_init: true) do
431
+ def to_h
432
+ {
433
+ operation: operation,
434
+ target_node_id: target_node_id,
435
+ correlation_key: correlation_key,
436
+ correlation_value: correlation_value
437
+ }
438
+ end
439
+ end
440
+
441
+ EditProjectionExecutionResult = Struct.new(:ok, :status, :source, :applied_operations, :diagnostics,
442
+ keyword_init: true) do
443
+ def to_h
444
+ {
445
+ ok: ok,
446
+ status: status,
447
+ source: source,
448
+ applied_operations: (applied_operations || []).map(&:to_h),
449
+ diagnostics: (diagnostics || []).map(&:to_h)
450
+ }
451
+ end
452
+ end
453
+
454
+ EditProjectionProviderOperation = Struct.new(
455
+ :operation,
456
+ :status,
457
+ :node_scope,
458
+ :correlation_keys,
459
+ :fixture_slices,
460
+ :formatting_preservation,
461
+ :diagnostics,
462
+ keyword_init: true
463
+ ) do
464
+ def to_h
465
+ {
466
+ operation: operation,
467
+ status: status,
468
+ node_scope: node_scope,
469
+ correlation_keys: correlation_keys || [],
470
+ fixture_slices: fixture_slices || [],
471
+ formatting_preservation: formatting_preservation,
472
+ diagnostics: diagnostics || []
473
+ }
474
+ end
475
+ end
476
+
477
+ EditProjectionProviderMatrixEntry = Struct.new(
478
+ :provider_id,
479
+ :backend_ref,
480
+ :language,
481
+ :formatting_preservation,
482
+ :preserves_source_fragments,
483
+ :operations,
484
+ keyword_init: true
485
+ ) do
486
+ def to_h
487
+ {
488
+ provider_id: provider_id,
489
+ backend_ref: backend_ref.to_h,
490
+ language: language,
491
+ formatting_preservation: formatting_preservation,
492
+ preserves_source_fragments: preserves_source_fragments,
493
+ operations: (operations || []).map(&:to_h)
494
+ }
495
+ end
496
+ end
497
+
498
+ EditProjectionProviderMatrix = Struct.new(:operations, :providers, :diagnostics, keyword_init: true) do
499
+ def to_h
500
+ {
501
+ operations: operations || [],
502
+ providers: (providers || []).map(&:to_h),
503
+ diagnostics: diagnostics || []
504
+ }
505
+ end
506
+ end
507
+
121
508
  SourcePoint = Struct.new(:row, :column, keyword_init: true) do
122
509
  def to_h
123
510
  {
@@ -137,7 +524,223 @@ module TreeHaver
137
524
  end
138
525
  end
139
526
 
140
- ByteEditSpan = Struct.new(:start_byte, :old_end_byte, :new_end_byte, :start_point, :old_end_point, :new_end_point, keyword_init: true) do
527
+ NODE_ROLES = %w[
528
+ structural
529
+ token
530
+ trivia
531
+ comment
532
+ delimiter
533
+ separator
534
+ virtual
535
+ error
536
+ opaque
537
+ ].freeze
538
+
539
+ NormalizedTreeNode = Struct.new(
540
+ :id,
541
+ :kind,
542
+ :role,
543
+ :parent_id,
544
+ :child_ids,
545
+ :span,
546
+ :field_name,
547
+ :named,
548
+ :anonymous,
549
+ :has_source_text,
550
+ :source_fragment,
551
+ :backend_kind,
552
+ :semantic_roles,
553
+ :backend_roles,
554
+ :unsupported_features,
555
+ :metadata,
556
+ keyword_init: true
557
+ ) do
558
+ def to_h
559
+ {
560
+ id: id,
561
+ kind: kind,
562
+ role: role,
563
+ parent_id: parent_id,
564
+ child_ids: child_ids,
565
+ span: span.to_h,
566
+ field_name: field_name,
567
+ named: named,
568
+ anonymous: anonymous,
569
+ has_source_text: has_source_text,
570
+ source_fragment: source_fragment,
571
+ backend_kind: backend_kind,
572
+ semantic_roles: semantic_roles || [],
573
+ backend_roles: backend_roles || [],
574
+ unsupported_features: unsupported_features || [],
575
+ metadata: metadata || {}
576
+ }
577
+ end
578
+ end
579
+
580
+ SourceFragment = Struct.new(:text, :span, :available, :strategy, :byte_length, :diagnostics, keyword_init: true) do
581
+ def to_h
582
+ {
583
+ text: text,
584
+ span: span.to_h,
585
+ available: available,
586
+ strategy: strategy,
587
+ byte_length: byte_length,
588
+ diagnostics: diagnostics
589
+ }
590
+ end
591
+ end
592
+
593
+ def node_roles
594
+ NODE_ROLES.dup
595
+ end
596
+ module_function :node_roles
597
+
598
+ MAX_LIBRARY_PATH_LENGTH = 4096
599
+ VALID_LIBRARY_FILENAME_PATTERN = /\A[A-Za-z0-9][A-Za-z0-9._-]*\z/
600
+ VALID_LANGUAGE_NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
601
+ VALID_SYMBOL_NAME_PATTERN = /\A[A-Za-z_][A-Za-z0-9_]*\z/
602
+ VERSIONED_SHARED_OBJECT_PATTERN = /\.so\.\d+\z/
603
+
604
+ def validate_library_path(path)
605
+ errors = library_path_errors(path)
606
+ LibraryPathValidation.new(path: path, valid: errors.empty?, errors: errors)
607
+ end
608
+ module_function :validate_library_path
609
+
610
+ def library_path_errors(path)
611
+ value = path.to_s
612
+ errors = []
613
+
614
+ errors << 'path_empty' if value.empty?
615
+ errors << 'path_too_long' if value.length > MAX_LIBRARY_PATH_LENGTH
616
+ errors << 'path_contains_null_byte' if value.include?("\0")
617
+ errors << 'path_not_absolute' unless value.start_with?('/') || windows_absolute_path?(value)
618
+
619
+ segments = value.split(%r{[/\\]})
620
+ errors << 'path_contains_parent_traversal' if segments.include?('..')
621
+ errors << 'path_contains_current_directory_traversal' if segments.include?('.')
622
+ errors << 'path_extension_not_allowed' unless allowed_library_extension?(value)
623
+ unless VALID_LIBRARY_FILENAME_PATTERN.match?(library_filename(value))
624
+ errors << 'filename_contains_invalid_characters'
625
+ end
626
+
627
+ errors
628
+ end
629
+ module_function :library_path_errors
630
+
631
+ def safe_language_name?(name)
632
+ VALID_LANGUAGE_NAME_PATTERN.match?(name.to_s)
633
+ end
634
+ module_function :safe_language_name?
635
+
636
+ def sanitize_language_name(name)
637
+ sanitized = name.to_s.downcase.gsub(/[^a-z0-9_]/, '')
638
+ safe_language_name?(sanitized) ? sanitized : nil
639
+ end
640
+ module_function :sanitize_language_name
641
+
642
+ def safe_symbol_name?(symbol)
643
+ VALID_SYMBOL_NAME_PATTERN.match?(symbol.to_s)
644
+ end
645
+ module_function :safe_symbol_name?
646
+
647
+ def safe_backend_name?(name)
648
+ name.to_s == 'auto' || !BackendRegistry.fetch(name.to_s).nil?
649
+ end
650
+ module_function :safe_backend_name?
651
+
652
+ def build_backend_availability_report(backend_ref, checks)
653
+ if checks.empty?
654
+ return BackendAvailabilityReport.new(
655
+ backend_ref: backend_ref,
656
+ status: 'unknown',
657
+ checks: [],
658
+ diagnostics: ['backend availability unknown: no checks supplied']
659
+ )
660
+ end
661
+
662
+ diagnostics = []
663
+ status = 'available'
664
+ checks.each do |check|
665
+ next unless check.required && check.status != 'available'
666
+
667
+ status = 'unavailable'
668
+ diagnostics << "backend unavailable: required check #{check.name} is #{check.status}"
669
+ end
670
+ BackendAvailabilityReport.new(backend_ref: backend_ref, status: status, checks: checks, diagnostics: diagnostics)
671
+ end
672
+ module_function :build_backend_availability_report
673
+
674
+ def build_provider_diagnostics_report(provider_id, backend_ref, language, diagnostics)
675
+ status = 'clean'
676
+ diagnostics.each do |diagnostic|
677
+ if diagnostic.blocking
678
+ status = 'blocked'
679
+ break
680
+ end
681
+ status = 'warning' if diagnostic.severity == 'warning'
682
+ end
683
+ ProviderDiagnosticsReport.new(
684
+ provider_id: provider_id,
685
+ backend_ref: backend_ref,
686
+ language: language,
687
+ status: status,
688
+ diagnostics: diagnostics
689
+ )
690
+ end
691
+ module_function :build_provider_diagnostics_report
692
+
693
+ def build_edit_projection_execution_result(source, applied_operations, diagnostics)
694
+ if diagnostics.any?(&:blocking)
695
+ return EditProjectionExecutionResult.new(
696
+ ok: false,
697
+ status: 'rejected',
698
+ source: source,
699
+ applied_operations: [],
700
+ diagnostics: diagnostics
701
+ )
702
+ end
703
+
704
+ EditProjectionExecutionResult.new(
705
+ ok: true,
706
+ status: 'applied',
707
+ source: source,
708
+ applied_operations: applied_operations,
709
+ diagnostics: diagnostics
710
+ )
711
+ end
712
+ module_function :build_edit_projection_execution_result
713
+
714
+ def build_edit_projection_provider_matrix(operations, providers, diagnostics)
715
+ EditProjectionProviderMatrix.new(
716
+ operations: operations || [],
717
+ providers: providers || [],
718
+ diagnostics: diagnostics || []
719
+ )
720
+ end
721
+ module_function :build_edit_projection_provider_matrix
722
+
723
+ def windows_absolute_path?(path)
724
+ %r{\A[A-Za-z]:[/\\]}.match?(path)
725
+ end
726
+ module_function :windows_absolute_path?
727
+ private_class_method :windows_absolute_path?
728
+
729
+ def allowed_library_extension?(path)
730
+ normalized_path = path.downcase
731
+ normalized_path.end_with?('.so', '.dylib', '.dll') || VERSIONED_SHARED_OBJECT_PATTERN.match?(normalized_path)
732
+ end
733
+ module_function :allowed_library_extension?
734
+ private_class_method :allowed_library_extension?
735
+
736
+ def library_filename(path)
737
+ path.split(%r{[/\\]}).last.to_s
738
+ end
739
+ module_function :library_filename
740
+ private_class_method :library_filename
741
+
742
+ ByteEditSpan = Struct.new(:start_byte, :old_end_byte, :new_end_byte, :start_point, :old_end_point, :new_end_point,
743
+ keyword_init: true) do
141
744
  def old_range
142
745
  ByteRange.new(start_byte: start_byte, end_byte: old_end_byte)
143
746
  end
@@ -162,7 +765,8 @@ module TreeHaver
162
765
  end
163
766
  end
164
767
 
165
- BinaryScalarValue = Struct.new(:kind, :value, :symbol, :raw_value, :encoding, :format, :description, keyword_init: true) do
768
+ BinaryScalarValue = Struct.new(:kind, :value, :symbol, :raw_value, :encoding, :format, :description,
769
+ keyword_init: true) do
166
770
  def to_h
167
771
  {
168
772
  kind: kind,
@@ -232,7 +836,8 @@ module TreeHaver
232
836
  end
233
837
  end
234
838
 
235
- BinaryMergeReport = Struct.new(:format, :schema, :matched_schema_paths, :preserved_ranges, :rewritten_nodes, :checksum_updates, :nested_dispatches, :diagnostics, keyword_init: true) do
839
+ BinaryMergeReport = Struct.new(:format, :schema, :matched_schema_paths, :preserved_ranges, :rewritten_nodes,
840
+ :checksum_updates, :nested_dispatches, :diagnostics, keyword_init: true) do
236
841
  def to_h
237
842
  {
238
843
  format: format,
@@ -264,7 +869,8 @@ module TreeHaver
264
869
  end
265
870
  end
266
871
 
267
- ZipArchiveEntry = Struct.new(:path, :normalized_path, :directory, :compression, :compressed_size, :uncompressed_size, :crc32, :local_header_range, :data_range, :central_directory_range, keyword_init: true) do
872
+ ZipArchiveEntry = Struct.new(:path, :normalized_path, :directory, :compression, :compressed_size, :uncompressed_size,
873
+ :crc32, :local_header_range, :data_range, :central_directory_range, keyword_init: true) do
268
874
  def to_h
269
875
  {
270
876
  path: path,
@@ -281,7 +887,8 @@ module TreeHaver
281
887
  end
282
888
  end
283
889
 
284
- ZipMemberDecision = Struct.new(:normalized_path, :operation, :disposition, :nested_family, :reason, keyword_init: true) do
890
+ ZipMemberDecision = Struct.new(:normalized_path, :operation, :disposition, :nested_family, :reason,
891
+ keyword_init: true) do
285
892
  def to_h
286
893
  {
287
894
  normalized_path: normalized_path,
@@ -304,7 +911,8 @@ module TreeHaver
304
911
  end
305
912
  end
306
913
 
307
- ZipFamilyReport = Struct.new(:archive, :entries, :member_decisions, :merge_report, :unsafe_entries, keyword_init: true) do
914
+ ZipFamilyReport = Struct.new(:archive, :entries, :member_decisions, :merge_report, :unsafe_entries,
915
+ keyword_init: true) do
308
916
  def to_h
309
917
  {
310
918
  archive: archive.to_h,
@@ -319,14 +927,39 @@ module TreeHaver
319
927
  def self.slice_byte_range(source, byte_range)
320
928
  source_bytesize = source.to_s.bytesize
321
929
  unless byte_range.valid? && byte_range.end_byte.to_i <= source_bytesize
322
- raise RangeError, "invalid byte range [#{byte_range.start_byte}, #{byte_range.end_byte}) for source length #{source_bytesize}"
930
+ raise RangeError,
931
+ "invalid byte range [#{byte_range.start_byte}, #{byte_range.end_byte}) for source length #{source_bytesize}"
323
932
  end
324
933
 
325
934
  source.to_s.byteslice(byte_range.start_byte.to_i...byte_range.end_byte.to_i)
326
935
  end
327
936
 
937
+ def self.extract_source_fragment(source, span, strategy)
938
+ text = slice_byte_range(source, span.range)
939
+ SourceFragment.new(
940
+ text: text,
941
+ span: span,
942
+ available: true,
943
+ strategy: strategy,
944
+ byte_length: text.bytesize,
945
+ diagnostics: []
946
+ )
947
+ rescue RangeError => e
948
+ SourceFragment.new(
949
+ text: '',
950
+ span: span,
951
+ available: false,
952
+ strategy: strategy,
953
+ byte_length: 0,
954
+ diagnostics: [e.message]
955
+ )
956
+ end
957
+
328
958
  def self.byte_offset_for_point(source, point)
329
- raise RangeError, "invalid source point (#{point.row}, #{point.column})" if point.row.to_i.negative? || point.column.to_i.negative?
959
+ if point.row.to_i.negative? || point.column.to_i.negative?
960
+ raise RangeError,
961
+ "invalid source point (#{point.row}, #{point.column})"
962
+ end
330
963
 
331
964
  row = 0
332
965
  column = 0
@@ -345,75 +978,6 @@ module TreeHaver
345
978
  raise RangeError, "source point (#{point.row}, #{point.column}) is outside source"
346
979
  end
347
980
 
348
- ProcessStructureItem = Struct.new(:kind, :name, :span, keyword_init: true) do
349
- def to_h
350
- {
351
- kind: kind,
352
- **(name ? { name: name } : {}),
353
- span: span.to_h
354
- }
355
- end
356
- end
357
-
358
- ProcessImportInfo = Struct.new(:source, :items, :span, keyword_init: true) do
359
- def to_h
360
- {
361
- source: source,
362
- items: deep_dup(items || []),
363
- span: span.to_h
364
- }
365
- end
366
-
367
- private
368
-
369
- def deep_dup(value)
370
- Marshal.load(Marshal.dump(value))
371
- end
372
- end
373
-
374
- ProcessDiagnostic = Struct.new(:message, :severity, keyword_init: true) do
375
- def to_h
376
- {
377
- message: message,
378
- severity: severity
379
- }
380
- end
381
- end
382
-
383
- LanguagePackAnalysis = Struct.new(:language, :dialect, :root_type, :has_error, :backend_ref, keyword_init: true) do
384
- def kind
385
- "tree-sitter"
386
- end
387
-
388
- def to_h
389
- {
390
- kind: kind,
391
- language: language,
392
- **(dialect ? { dialect: dialect } : {}),
393
- root_type: root_type,
394
- has_error: has_error,
395
- backend_ref: backend_ref.to_h
396
- }
397
- end
398
- end
399
-
400
- LanguagePackProcessAnalysis = Struct.new(:language, :structure, :imports, :diagnostics, :backend_ref, keyword_init: true) do
401
- def kind
402
- "tree-sitter-process"
403
- end
404
-
405
- def to_h
406
- {
407
- kind: kind,
408
- language: language,
409
- structure: (structure || []).map(&:to_h),
410
- imports: (imports || []).map(&:to_h),
411
- diagnostics: (diagnostics || []).map(&:to_h),
412
- backend_ref: backend_ref.to_h
413
- }
414
- end
415
- end
416
-
417
981
  KaitaiByteSpan = Struct.new(:start_byte, :end_byte, keyword_init: true) do
418
982
  def to_h
419
983
  {
@@ -441,9 +1005,10 @@ module TreeHaver
441
1005
  end
442
1006
  end
443
1007
 
444
- KaitaiTreeAnalysis = Struct.new(:schema, :root, :backend_ref, :source_byte_length, :diagnostics, keyword_init: true) do
1008
+ KaitaiTreeAnalysis = Struct.new(:schema, :root, :backend_ref, :source_byte_length, :diagnostics,
1009
+ keyword_init: true) do
445
1010
  def kind
446
- "kaitai-tree"
1011
+ 'kaitai-tree'
447
1012
  end
448
1013
 
449
1014
  def to_h