tree_haver 7.1.8 → 7.1.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd8a2bf5d9e6895f75c2fb3e686b1c9ff857327f9c2204415e321376a6f9b080
4
- data.tar.gz: bf8489bd8e6205fbb950e67699dc6aec5d90de8a986646e1faeec6477c1e75a5
3
+ metadata.gz: c442758cd9e4d17f3088f4b501db45cae5a6b041db3fa3a31797bd2021b1a486
4
+ data.tar.gz: 33741a50778080021bdd02e11e87fe03f54e5631a2032d6dd51ab2a8c1434a1b
5
5
  SHA512:
6
- metadata.gz: 598312346e2540cada502c8b50d660ce22bc59fc1a77991e56e025ad3bd68c7e629db880cccb9bf2f8b48b61693c44f0392251ddb31c0b411325b5189f8df71b
7
- data.tar.gz: a0dd401a8237aa13bcae96111bc17ef3cc245b94cbb0124258413eb0d0f2d78cc7cf1cd32db50ec842d6137677f8d5067b7549743c5e1a6e457dbdafd8a290a7
6
+ metadata.gz: fcf407fba26bdba32f80ecebf92b6d946a6c176f187da0a6ee928e877723cd97a5bcca732811bf5695c8bb112a5b75614768f9e3d57cd9bcf4b50807e14fa390
7
+ data.tar.gz: f210d2e7d3673870097c56a22a2380a51a98620c2c39a6bf00ad2b66f54e4e96837ee26bc57caf8151eb0ae134d6e519090d9d1c15429c1a160e2e2d646e6468
checksums.yaml.gz.sig CHANGED
Binary file
@@ -613,7 +613,7 @@ module TreeHaver
613
613
  ok = Native.ts_parser_set_language(@parser, ptr)
614
614
  raise TreeHaver::NotAvailable, 'Failed to set language on parser' unless ok
615
615
 
616
- lang # rubocop:disable Lint/Void (intentional return value)
616
+ lang # (intentional return value)
617
617
  end
618
618
 
619
619
  # Parse source code into a syntax tree
@@ -207,7 +207,7 @@ module TreeHaver
207
207
  lang_name = lang.respond_to?(:name) ? lang.name : lang.to_s
208
208
  # tree_stump uses set_language with a string name
209
209
  @parser.set_language(lang_name)
210
- lang # rubocop:disable Lint/Void (intentional return value)
210
+ lang # (intentional return value)
211
211
  end
212
212
 
213
213
  # Parse source code
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'digest'
5
+
6
+ module TreeHaver
7
+ module Backends
8
+ # Explicit Magnus bridge to the canonical Rust TreeHaver/TSLP substrate.
9
+ #
10
+ # This backend deliberately exposes only the normalized tree contract. It
11
+ # does not replace the direct Ruby language-pack or tree_stump backends.
12
+ module RustTslp
13
+ class << self
14
+ attr_reader :unavailable_reason
15
+
16
+ def available?
17
+ return @loaded if @load_attempted
18
+
19
+ @load_attempted = true
20
+ @loaded = load_host
21
+ end
22
+
23
+ def reset!
24
+ @load_attempted = false
25
+ @loaded = false
26
+ @unavailable_reason = nil
27
+ end
28
+
29
+ def capabilities
30
+ return {} unless available?
31
+
32
+ {
33
+ backend: :rust_tslp,
34
+ query: false,
35
+ bytes_field: true,
36
+ incremental: false,
37
+ comment_support: :nodes_only,
38
+ language_pack: true,
39
+ provenance: :rust_tree_haver
40
+ }
41
+ end
42
+
43
+ def register_language_parser(language)
44
+ return unless available?
45
+
46
+ # Cache only registrations made by this adapter, never adopt a foreign
47
+ # duplicate ID. An external removal therefore fails closed on parse.
48
+ REGISTRATION_MUTEX.synchronize do
49
+ @registrations ||= {}
50
+ key = [::StructuredmergeCore, language.to_s]
51
+ @registrations[key] ||= ::StructuredmergeCore.register_language_pack_parser(
52
+ "tree_haver.rust_tslp.#{language}", language.to_s
53
+ ).id
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # rubocop:disable Metrics/MethodLength
60
+ def load_host
61
+ unless RUBY_ENGINE == 'ruby'
62
+ @unavailable_reason = 'the Rust TreeHaver Magnus bridge requires MRI Ruby'
63
+ return false
64
+ end
65
+
66
+ require 'structuredmerge_core' unless defined?(::StructuredmergeCore)
67
+ unless ::StructuredmergeCore.respond_to?(:parse_sources) && ::StructuredmergeCore.respond_to?(:register_language_pack_parser)
68
+ @unavailable_reason = 'structuredmerge-core does not expose typed Rust parser registration'
69
+ return false
70
+ end
71
+
72
+ true
73
+ rescue LoadError => e
74
+ @unavailable_reason = e.message
75
+ false
76
+ rescue StandardError => e
77
+ @unavailable_reason = e.message
78
+ false
79
+ end
80
+ # rubocop:enable Metrics/MethodLength
81
+ end
82
+
83
+ REGISTRATION_MUTEX = Mutex.new
84
+
85
+ # Identifies a language parsed by the canonical Rust substrate.
86
+ class Language < TreeHaver::Base::Language
87
+ def initialize(name)
88
+ super(name, backend: :rust_tslp, options: {})
89
+ end
90
+
91
+ class << self
92
+ def from_library(_path = nil, symbol: nil, name: nil) # rubocop:disable Lint/UnusedMethodArgument
93
+ new(name || :unknown)
94
+ end
95
+ end
96
+ end
97
+
98
+ # Parses source into the Rust normalized tree representation.
99
+ class Parser < TreeHaver::Base::Parser
100
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
101
+ def parse(source)
102
+ raise TreeHaver::NotAvailable, unavailable_message unless RustTslp.available?
103
+ raise TreeHaver::NotAvailable, 'Rust TSLP language is not set' unless language
104
+
105
+ normalized_source = normalize_source_encoding(source)
106
+ core = ::StructuredmergeCore
107
+ provider_id = RustTslp.register_language_parser(language.name)
108
+ crlf = normalized_source.scan("\r\n").length
109
+ descriptor = core::SourceDescriptor.new(
110
+ source_id: 'tree_haver.source', role: 'source', byte_length: normalized_source.bytesize,
111
+ sha256: Digest::SHA256.hexdigest(normalized_source), encoding: 'utf8',
112
+ bom: normalized_source.start_with?("\uFEFF"), final_newline: normalized_source.end_with?("\n", "\r"),
113
+ line_endings: core::LineEndings.new(lf: normalized_source.count("\n") - crlf,
114
+ crlf: crlf, bare_cr: normalized_source.count("\r") - crlf)
115
+ )
116
+ request = core::ParseRequest.new(
117
+ schema: 'structuredmerge.parse-request/v1', request_id: 'tree_haver.parse',
118
+ source: core::SourceInput.new(descriptor: descriptor, bytes: normalized_source.bytes),
119
+ language: language.name.to_s, dialect: nil,
120
+ selection: core::ParserSelection.new(backend_id: provider_id, preference: [], required_capabilities: []),
121
+ options: core::ParseOptions.new(comments: true, diagnostics: true, tokens: false, native_extensions: true),
122
+ metadata: {}, extra: {}
123
+ )
124
+ limits = core::ParseLimits.new(max_batch_items: 1, max_input_bytes: 64 * 1024 * 1024,
125
+ max_nodes: 1_000_000, max_diagnostics: 1000)
126
+ result = core.parse_sources([request], limits).fetch(0)
127
+ Tree.new(result, source: normalized_source, language: language.name)
128
+ end
129
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
130
+
131
+ private
132
+
133
+ def normalize_source_encoding(source)
134
+ if [Encoding::UTF_8, Encoding::BINARY, Encoding::US_ASCII].include?(source.encoding)
135
+ utf8 = source.dup.force_encoding(Encoding::UTF_8)
136
+ return utf8.freeze if utf8.valid_encoding?
137
+ end
138
+
139
+ raise TreeHaver::NotAvailable, 'Rust TreeHaver normalized parsing requires valid UTF-8 source bytes'
140
+ end
141
+
142
+ def unavailable_message
143
+ detail = RustTslp.unavailable_reason.to_s
144
+ detail = 'unknown reason' if detail.empty?
145
+ "Rust TreeHaver normalized parser is unavailable: #{detail}"
146
+ end
147
+ end
148
+
149
+ # Wraps one validated Rust normalized parse result.
150
+ class Tree < TreeHaver::Base::Tree
151
+ attr_reader :diagnostics, :language, :provenance
152
+
153
+ def initialize(result, source:, language:)
154
+ validate_result!(result)
155
+ super(result, source: source)
156
+ @language = language.to_s
157
+ @nodes_by_id = result.parsed.nodes.to_h { |node| [node.id, node] }
158
+ @root_id = result.parsed.root_id
159
+ @diagnostics = result.parsed.diagnostics.map(&:message)
160
+ @provenance = {
161
+ 'backend_ref' => { 'id' => result.backend.id },
162
+ 'language' => @language, 'runtime' => result.backend.runtime,
163
+ 'parser' => result.backend.parser, 'parser_version' => result.backend.parser_version
164
+ }
165
+ end
166
+
167
+ def root_node
168
+ Node.new(
169
+ @nodes_by_id.fetch(@root_id),
170
+ nodes_by_id: @nodes_by_id,
171
+ source: source,
172
+ lines: lines,
173
+ language: language
174
+ )
175
+ end
176
+
177
+ def errors
178
+ diagnostics
179
+ end
180
+
181
+ private
182
+
183
+ def validate_result!(result)
184
+ nodes = result.parsed.nodes
185
+ root_id = result.parsed.root_id
186
+ valid_root = root_id.is_a?(String) && nodes.any? { |node| node.id == root_id }
187
+ return if valid_root
188
+
189
+ raise TreeHaver::Error, 'Rust TreeHaver returned an invalid normalized tree'
190
+ end
191
+ end
192
+
193
+ # Adapts normalized node records to TreeHaver's node contract.
194
+ class Node < TreeHaver::Base::Node
195
+ # Preserve the established TreeHaver surface when the two language-pack
196
+ # implementations expose equivalent JSON5 grammar nodes under different
197
+ # native names.
198
+ NODE_TYPE_ALIASES = {
199
+ 'json5' => {
200
+ 'file' => 'document',
201
+ 'member' => 'pair'
202
+ }
203
+ }.freeze
204
+
205
+ attr_reader :language
206
+
207
+ def initialize(node, nodes_by_id:, source: nil, lines: nil, language: nil)
208
+ super(node, source: source, lines: lines)
209
+ @nodes_by_id = nodes_by_id
210
+ @language = language.to_s
211
+ end
212
+
213
+ def type
214
+ NODE_TYPE_ALIASES.fetch(language, {}).fetch(native_type, native_type)
215
+ end
216
+
217
+ def native_type = inner_node.native_type
218
+
219
+ def start_byte = span.range.start_byte
220
+ def end_byte = span.range.end_byte
221
+ def start_point = symbolize_point(span.start_point)
222
+ def end_point = symbolize_point(span.end_point)
223
+ def child_count = inner_node.children.length
224
+ def named? = inner_node.named
225
+
226
+ def has_error? # rubocop:disable Naming/PredicatePrefix
227
+ inner_node.has_error
228
+ end
229
+
230
+ def error? = has_error?
231
+ def missing? = inner_node.missing
232
+
233
+ def extra?
234
+ extension = inner_node.extensions.find do |item|
235
+ item.schema == 'tree-haver.tree-sitter.node/v1' && item.namespace == 'tree-sitter'
236
+ end
237
+ raise TreeHaver::Error, 'Rust TreeHaver omitted native node flags' unless extension
238
+
239
+ # Alef transports only the open extension payload as JSON, not the tree.
240
+ JSON.parse(extension.payload).fetch('extra')
241
+ end
242
+
243
+ def text
244
+ source.byteslice(start_byte...end_byte)
245
+ end
246
+
247
+ def child(index)
248
+ edge = inner_node.children[index]
249
+ edge && wrap(@nodes_by_id.fetch(edge.node_id))
250
+ end
251
+
252
+ def children
253
+ inner_node.children.map { |edge| wrap(@nodes_by_id.fetch(edge.node_id)) }
254
+ end
255
+
256
+ def child_by_field_name(name)
257
+ edge = inner_node.children.find { |item| item.field_name == name.to_s }
258
+ edge && wrap(@nodes_by_id.fetch(edge.node_id))
259
+ end
260
+
261
+ def parent
262
+ parent_id = inner_node.parent_id
263
+ parent_id && wrap(@nodes_by_id.fetch(parent_id))
264
+ end
265
+
266
+ def next_sibling
267
+ sibling_at(1)
268
+ end
269
+
270
+ def prev_sibling
271
+ sibling_at(-1)
272
+ end
273
+
274
+ private
275
+
276
+ def span = inner_node.span
277
+
278
+ def symbolize_point(point)
279
+ { row: point.row, column: point.column }
280
+ end
281
+
282
+ def wrap(node)
283
+ self.class.new(node, nodes_by_id: @nodes_by_id, source: source, lines: lines, language: language)
284
+ end
285
+
286
+ def sibling_at(offset)
287
+ parent_node = parent
288
+ return unless parent_node
289
+
290
+ siblings = parent_node.inner_node.children.map(&:node_id)
291
+ index = siblings.index(inner_node.id)
292
+ sibling_index = index&.+(offset)
293
+ return unless sibling_index&.between?(0, siblings.length - 1)
294
+
295
+ sibling_id = siblings[sibling_index]
296
+ sibling_id && wrap(@nodes_by_id.fetch(sibling_id))
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
@@ -8,11 +8,20 @@ module TreeHaver
8
8
  # process API is not a TreeHaver parser backend and must not be used as a
9
9
  # merge-gem integration surface.
10
10
  module Tslp
11
- @load_attempted = false
11
+ # Transient grammar hot-load failures (for example a failed on-demand
12
+ # download of the tree-sitter-language-pack bundle) are retried on later
13
+ # availability probes instead of latching for the whole process. After
14
+ # this many consecutive transient failures the backend latches as
15
+ # unavailable so a dead network cannot slow every later parse attempt.
16
+ TRANSIENT_FAILURE_RETRY_LIMIT = 3
17
+
12
18
  @loaded = false
19
+ @permanently_unavailable = false
20
+ @transient_failures = 0
13
21
  @unavailable_reason = nil
14
22
  @language_availability = {}
15
23
  @language_unavailable_reasons = {}
24
+ @language_attempts = {}
16
25
  PARSER_SMOKE_SOURCES = {
17
26
  'json' => '{}',
18
27
  'json5' => '{}',
@@ -28,35 +37,91 @@ module TreeHaver
28
37
  }.freeze
29
38
  DEFAULT_PARSER_SMOKE_SOURCE = ''
30
39
 
40
+ # Grammar hot-load failures caused by the on-demand bundle download
41
+ # (network flake, transient GitHub release fetch failure, cache lock
42
+ # contention) must not latch the backend for the whole process.
43
+ TRANSIENT_FAILURE_PATTERN = /
44
+ Download\ error
45
+ | Download\ cache\ lock\ error
46
+ | Failed\ to\ fetch\ manifest
47
+ | Failed\ to\ download
48
+ | Failed\ to\ read\ (cached|download)
49
+ | connection\ (refused|reset)
50
+ | timed?\ out
51
+ | timeout
52
+ | temporarily
53
+ /xi
54
+
31
55
  class << self
32
56
  attr_reader :unavailable_reason
33
57
 
34
58
  def available?
35
- return @loaded if @load_attempted
59
+ return true if @loaded
60
+ return false if @permanently_unavailable
61
+
62
+ @loaded = probe_availability
63
+ return true if @loaded
64
+
65
+ if transient_reason?(@unavailable_reason) && @transient_failures < TRANSIENT_FAILURE_RETRY_LIMIT
66
+ # Leave the backend probeable so a later availability check retries
67
+ # the on-demand grammar download instead of failing every parse for
68
+ # the rest of the process.
69
+ @transient_failures += 1
70
+ return false
71
+ end
36
72
 
37
- @load_attempted = true
73
+ @permanently_unavailable = true
74
+ false
75
+ end
76
+
77
+ # Reason the last per-language probe failed, when known.
78
+ def language_unavailable_reason(language_name)
79
+ @language_unavailable_reasons[language_name.to_s]
80
+ end
81
+
82
+ # Pre-download and pre-load grammars through the language pack's
83
+ # hot-load API before any parse/smoke probing happens. This turns the
84
+ # on-demand download (which otherwise fires inside the first
85
+ # availability probe and can transiently fail) into an explicit,
86
+ # attributable warm-up step.
87
+ #
88
+ # @param languages [Array<String, Symbol>] grammar names to prefetch
89
+ # @return [Hash] {attempted:, prefetched:, failures: {name => reason}}
90
+ def prefetch(languages)
91
+ names = languages.map(&:to_s).uniq
38
92
  begin
39
93
  require 'tree_sitter_language_pack' unless defined?(::TreeSitterLanguagePack)
40
- @loaded = parser_api_available?
41
- if !@loaded && @unavailable_reason.to_s.empty?
42
- @unavailable_reason = 'tree_sitter_language_pack parser API is not exposed'
43
- end
44
94
  rescue LoadError => e
45
- @loaded = false
46
- @unavailable_reason = e.message
95
+ return prefetch_skip_report('tree_sitter_language_pack is not installed', e.message, names)
96
+ end
97
+
98
+ unless ::TreeSitterLanguagePack.respond_to?(:prefetch)
99
+ return prefetch_skip_report('tree_sitter_language_pack does not expose prefetch', nil, names)
100
+ end
101
+
102
+ failures = {}
103
+ prefetched = names.select do |name|
104
+ ::TreeSitterLanguagePack.prefetch([name])
105
+ true
47
106
  rescue StandardError => e
48
- @loaded = false
49
- @unavailable_reason = e.message
107
+ failures[name] = e.message
108
+ false
50
109
  end
51
- @loaded
110
+ { attempted: true, prefetched: prefetched, failures: failures }
111
+ end
112
+
113
+ def transient_reason?(reason)
114
+ !reason.to_s.empty? && TRANSIENT_FAILURE_PATTERN.match?(reason.to_s)
52
115
  end
53
116
 
54
117
  def reset!
55
- @load_attempted = false
56
118
  @loaded = false
119
+ @permanently_unavailable = false
120
+ @transient_failures = 0
57
121
  @unavailable_reason = nil
58
122
  @language_availability = {}
59
123
  @language_unavailable_reasons = {}
124
+ @language_attempts = {}
60
125
  end
61
126
 
62
127
  def capabilities
@@ -78,17 +143,62 @@ module TreeHaver
78
143
  name = language_name.to_s
79
144
  return @language_availability.fetch(name) if @language_availability.key?(name)
80
145
 
81
- @language_availability[name] = smoke_parse_language(name)
82
- @language_unavailable_reasons[name] = @unavailable_reason unless @language_availability.fetch(name)
83
- @language_availability.fetch(name)
146
+ available = smoke_parse_language(name)
147
+ if available
148
+ @language_availability[name] = true
149
+ @language_unavailable_reasons.delete(name)
150
+ return true
151
+ end
152
+
153
+ reason = @language_unavailable_reasons[name] = @unavailable_reason
154
+ attempts = (@language_attempts[name] = @language_attempts.fetch(name, 0) + 1)
155
+ if transient_reason?(reason) && attempts < TRANSIENT_FAILURE_RETRY_LIMIT
156
+ # Do not memoize: a later probe retries the on-demand hot load.
157
+ return false
158
+ end
159
+
160
+ @language_availability[name] = false
161
+ false
84
162
  rescue StandardError => e
163
+ name = language_name.to_s
85
164
  @unavailable_reason = e.message
86
- @language_unavailable_reasons[language_name.to_s] = e.message
165
+ @language_unavailable_reasons[name] = e.message
166
+ attempts = (@language_attempts[name] = @language_attempts.fetch(name, 0) + 1)
167
+ # Memoize only after exhausting the transient retry budget; raising
168
+ # here is how the on-demand grammar download reports failures.
169
+ @language_availability[name] = false if attempts >= TRANSIENT_FAILURE_RETRY_LIMIT
87
170
  false
88
171
  end
89
172
 
90
173
  private
91
174
 
175
+ def prefetch_skip_report(reason, detail, names)
176
+ @unavailable_reason = detail if detail
177
+ {
178
+ attempted: false,
179
+ reason: reason,
180
+ prefetched: [],
181
+ failures: names.to_h { |name| [name, reason] }
182
+ }
183
+ end
184
+
185
+ def probe_availability
186
+ require 'tree_sitter_language_pack' unless defined?(::TreeSitterLanguagePack)
187
+ loaded = parser_api_available?
188
+ if !loaded && @unavailable_reason.to_s.empty?
189
+ @unavailable_reason = 'tree_sitter_language_pack parser API is not exposed'
190
+ end
191
+ loaded
192
+ rescue LoadError => e
193
+ # A missing gem is permanent for the process lifetime.
194
+ @unavailable_reason = e.message
195
+ @permanently_unavailable = true
196
+ false
197
+ rescue StandardError => e
198
+ @unavailable_reason = e.message
199
+ false
200
+ end
201
+
92
202
  def parser_api_available?
93
203
  return false unless ::TreeSitterLanguagePack.respond_to?(:get_parser)
94
204
  return false unless defined?(::TreeSitterLanguagePack::Parser)
@@ -314,12 +314,14 @@ module TreeHaver
314
314
  # @raise [NotAvailable] if library not found and raise_on_missing is true
315
315
  def register!(raise_on_missing: false)
316
316
  if tree_sitter_language_pack_parser_available?
317
+ backend_module, backend_type, gem_name = language_pack_registration
317
318
  TreeHaver.register_language(
318
319
  @language_name,
319
- backend_module: TreeHaver::Backends::Tslp,
320
- backend_type: :tslp,
321
- gem_name: 'tree_sitter_language_pack'
320
+ backend_module: backend_module,
321
+ backend_type: backend_type,
322
+ gem_name: gem_name
322
323
  )
324
+ backend_module.register_language_parser(@language_name) if backend_type == :rust_tslp
323
325
  return true
324
326
  end
325
327
 
@@ -377,6 +379,14 @@ module TreeHaver
377
379
 
378
380
  private
379
381
 
382
+ def language_pack_registration
383
+ if TreeHaver.effective_backend == :rust_tslp
384
+ [TreeHaver::Backends::RustTslp, :rust_tslp, 'structuredmerge-core']
385
+ else
386
+ [TreeHaver::Backends::Tslp, :tslp, 'tree_sitter_language_pack']
387
+ end
388
+ end
389
+
380
390
  def registered_tree_sitter_registration
381
391
  TreeHaver::LanguageRegistry.registered(@language_name, :tree_sitter)
382
392
  end
@@ -399,11 +409,15 @@ module TreeHaver
399
409
  return @tree_sitter_language_pack_parser_available if defined?(@tree_sitter_language_pack_parser_available)
400
410
 
401
411
  @tree_sitter_language_pack_parser_available = begin
402
- require 'tree_sitter_language_pack' unless defined?(::TreeSitterLanguagePack)
403
- TreeHaver::Backends::Tslp.available? &&
404
- ::TreeSitterLanguagePack.respond_to?(:has_language) &&
405
- ::TreeSitterLanguagePack.has_language(@language_name.to_s) &&
406
- TreeHaver::Backends::Tslp.parser_available_for?(@language_name)
412
+ if TreeHaver.effective_backend == :rust_tslp
413
+ TreeHaver::Backends::RustTslp.available?
414
+ else
415
+ require 'tree_sitter_language_pack' unless defined?(::TreeSitterLanguagePack)
416
+ TreeHaver::Backends::Tslp.available? &&
417
+ ::TreeSitterLanguagePack.respond_to?(:has_language) &&
418
+ ::TreeSitterLanguagePack.has_language(@language_name.to_s) &&
419
+ TreeHaver::Backends::Tslp.parser_available_for?(@language_name)
420
+ end
407
421
  rescue LoadError
408
422
  false
409
423
  rescue StandardError => e
@@ -9,9 +9,14 @@ module TreeHaver
9
9
  id: 'kreuzberg-language-pack',
10
10
  family: 'tree-sitter'
11
11
  ).freeze
12
+ RUST_TSLP_BACKEND = BackendReference.new(
13
+ id: 'rust_tslp',
14
+ family: 'tree-sitter'
15
+ ).freeze
12
16
 
13
17
  BackendRegistry.register(TSLP_BACKEND)
14
18
  BackendRegistry.register(KREUZBERG_LANGUAGE_PACK_BACKEND)
19
+ BackendRegistry.register(RUST_TSLP_BACKEND)
15
20
  BackendRegistry.register_availability_checker(:tslp) do
16
21
  Backends::Tslp.available?
17
22
  end
@@ -4,7 +4,7 @@ module TreeHaver
4
4
  # Version namespace for this gem.
5
5
  module Version
6
6
  # Current gem version.
7
- VERSION = '7.1.8'
7
+ VERSION = '7.1.10'
8
8
  end
9
9
  # Current gem version exposed at the traditional constant location.
10
10
  VERSION = Version::VERSION # Traditional Constant Location
data/lib/tree_haver.rb CHANGED
@@ -32,6 +32,7 @@ require_relative 'tree_haver/backends/ffi'
32
32
  require_relative 'tree_haver/backends/rust'
33
33
  require_relative 'tree_haver/backends/java'
34
34
  require_relative 'tree_haver/backends/tslp'
35
+ require_relative 'tree_haver/backends/rust_tslp'
35
36
  require_relative 'tree_haver/grammar_finder'
36
37
  require_relative 'tree_haver/citrus_grammar_finder'
37
38
  require_relative 'tree_haver/parslet_grammar_finder'
@@ -50,7 +51,8 @@ module TreeHaver
50
51
  RUBY_BACKENDS = %i[citrus parslet prism psych commonmarker markly rbs].freeze
51
52
  VALID_NATIVE_BACKENDS = NATIVE_BACKENDS.map(&:to_s).freeze
52
53
  VALID_RUBY_BACKENDS = RUBY_BACKENDS.map(&:to_s).freeze
53
- VALID_BACKENDS = (VALID_NATIVE_BACKENDS + VALID_RUBY_BACKENDS + %w[auto none tslp kreuzberg-language-pack]).freeze
54
+ VALID_BACKENDS = (VALID_NATIVE_BACKENDS + VALID_RUBY_BACKENDS + %w[auto none tslp rust_tslp
55
+ kreuzberg-language-pack]).freeze
54
56
  DEFAULT_BACKEND_ID = 'tslp'
55
57
  NATIVE_BACKEND_REFERENCES = NATIVE_BACKENDS.to_h do |backend_name|
56
58
  [
@@ -76,6 +78,9 @@ module TreeHaver
76
78
  BackendRegistry.register_tag(:java_backend, category: :backend, backend_name: :java) do
77
79
  Backends::Java.available?
78
80
  end
81
+ BackendRegistry.register_tag(:rust_tslp_backend, category: :backend, backend_name: :rust_tslp) do
82
+ Backends::RustTslp.available?
83
+ end
79
84
  end
80
85
 
81
86
  def default_backend_id
@@ -253,6 +258,19 @@ module TreeHaver
253
258
  LanguageRegistry.registered(name)
254
259
  end
255
260
 
261
+ # Warm up language-pack grammars through their hot-load/prefetch API so the
262
+ # on-demand grammar download happens explicitly, up front, instead of firing
263
+ # inside the first availability smoke probe during a merge.
264
+ #
265
+ # Safe to call with the TSLP backend unavailable or absent: reports
266
+ # attempted: false with a reason instead of raising.
267
+ #
268
+ # @param languages [Array<String, Symbol>] grammar names to prefetch
269
+ # @return [Hash] prefetch report (see Backends::Tslp.prefetch)
270
+ def prefetch_languages(languages)
271
+ Backends::Tslp.prefetch(languages)
272
+ end
273
+
256
274
  def register_language(name, path: nil, symbol: nil, grammar_module: nil, grammar_class: nil, backend_module: nil,
257
275
  backend_type: nil, gem_name: nil, contract: nil)
258
276
  LanguageRegistry.register(name, :tree_sitter, path: path, symbol: symbol, contract: contract) if path
@@ -340,7 +358,21 @@ module TreeHaver
340
358
  return parser_for_registered_backend(name, backend_type, registrations)
341
359
  end
342
360
 
343
- raise NotAvailable, "No parser registered for #{name}"
361
+ raise NotAvailable, no_parser_registered_message(name)
362
+ end
363
+
364
+ # Build the "No parser registered" error message, surfacing why the
365
+ # tree-sitter-language-pack backend declined the language (for example a
366
+ # failed on-demand grammar download) so CI logs are attributable instead of
367
+ # misleading.
368
+ def no_parser_registered_message(name)
369
+ message = "No parser registered for #{name}"
370
+ language_reason = Backends::Tslp.language_unavailable_reason(name)
371
+ backend_reason = Backends::Tslp.unavailable_reason
372
+ reasons = [language_reason, backend_reason].map(&:to_s).reject(&:empty?).uniq
373
+ return message if reasons.empty?
374
+
375
+ "#{message} (tree_sitter_language_pack: #{reasons.join('; ')})"
344
376
  end
345
377
 
346
378
  def ruby_reference_parser_backend_contract_report
@@ -407,7 +439,9 @@ module TreeHaver
407
439
  elsif (registered_type = registered_backend_type_for_requested_backend(backend_id, registrations))
408
440
  registered_type
409
441
  elsif backend_ref&.family == 'tree-sitter'
410
- :tree_sitter
442
+ registrations.find do |registered_type, config|
443
+ registration_contract(registered_type, config) == :tree_sitter
444
+ end&.first || :tree_sitter
411
445
  else
412
446
  backend_id.to_s.to_sym
413
447
  end
@@ -452,7 +486,7 @@ module TreeHaver
452
486
  def parser_for_registered_backend(name, backend_type, registrations)
453
487
  config = registrations.fetch(backend_type)
454
488
  case backend_type
455
- when :psych, :prism, :commonmarker, :markly, :rbs
489
+ when :psych, :prism, :commonmarker, :markly, :rbs, :rust_tslp
456
490
  parser_for_backend_module(config.fetch(:backend_module), name)
457
491
  when :citrus
458
492
  parser_for_citrus(config.fetch(:grammar_module))
@@ -562,6 +596,8 @@ module TreeHaver
562
596
  Backends::Java
563
597
  when :tslp, :"kreuzberg-language-pack"
564
598
  Backends::Tslp
599
+ when :rust_tslp
600
+ Backends::RustTslp
565
601
  when :citrus
566
602
  Backends::Citrus
567
603
  when :parslet
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tree_haver
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.8
4
+ version: 7.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -71,7 +71,7 @@ dependencies:
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 1.16.2
74
+ version: 1.17.0
75
75
  - - "<"
76
76
  - !ruby/object:Gem::Version
77
77
  version: '2.0'
@@ -81,7 +81,7 @@ dependencies:
81
81
  requirements:
82
82
  - - ">="
83
83
  - !ruby/object:Gem::Version
84
- version: 1.16.2
84
+ version: 1.17.0
85
85
  - - "<"
86
86
  - !ruby/object:Gem::Version
87
87
  version: '2.0'
@@ -111,20 +111,20 @@ dependencies:
111
111
  requirements:
112
112
  - - "~>"
113
113
  - !ruby/object:Gem::Version
114
- version: '3.0'
114
+ version: '3.1'
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: 3.0.31
117
+ version: 3.1.8
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '3.0'
124
+ version: '3.1'
125
125
  - - ">="
126
126
  - !ruby/object:Gem::Version
127
- version: 3.0.31
127
+ version: 3.1.8
128
128
  - !ruby/object:Gem::Dependency
129
129
  name: bundler-audit
130
130
  requirement: !ruby/object:Gem::Requirement
@@ -202,7 +202,7 @@ dependencies:
202
202
  version: '3.2'
203
203
  - - ">="
204
204
  - !ruby/object:Gem::Version
205
- version: 3.2.3
205
+ version: 3.2.5
206
206
  type: :development
207
207
  prerelease: false
208
208
  version_requirements: !ruby/object:Gem::Requirement
@@ -212,7 +212,7 @@ dependencies:
212
212
  version: '3.2'
213
213
  - - ">="
214
214
  - !ruby/object:Gem::Version
215
- version: 3.2.3
215
+ version: 3.2.5
216
216
  - !ruby/object:Gem::Dependency
217
217
  name: kettle-test
218
218
  requirement: !ruby/object:Gem::Requirement
@@ -222,7 +222,7 @@ dependencies:
222
222
  version: '2.0'
223
223
  - - ">="
224
224
  - !ruby/object:Gem::Version
225
- version: 2.0.21
225
+ version: 2.0.23
226
226
  type: :development
227
227
  prerelease: false
228
228
  version_requirements: !ruby/object:Gem::Requirement
@@ -232,7 +232,7 @@ dependencies:
232
232
  version: '2.0'
233
233
  - - ">="
234
234
  - !ruby/object:Gem::Version
235
- version: 2.0.21
235
+ version: 2.0.23
236
236
  - !ruby/object:Gem::Dependency
237
237
  name: turbo_tests2
238
238
  requirement: !ruby/object:Gem::Requirement
@@ -242,7 +242,7 @@ dependencies:
242
242
  version: '3.2'
243
243
  - - ">="
244
244
  - !ruby/object:Gem::Version
245
- version: 3.2.7
245
+ version: 3.2.14
246
246
  type: :development
247
247
  prerelease: false
248
248
  version_requirements: !ruby/object:Gem::Requirement
@@ -252,7 +252,7 @@ dependencies:
252
252
  version: '3.2'
253
253
  - - ">="
254
254
  - !ruby/object:Gem::Version
255
- version: 3.2.7
255
+ version: 3.2.14
256
256
  - !ruby/object:Gem::Dependency
257
257
  name: ruby-progressbar
258
258
  requirement: !ruby/object:Gem::Requirement
@@ -276,7 +276,7 @@ dependencies:
276
276
  version: '1.0'
277
277
  - - ">="
278
278
  - !ruby/object:Gem::Version
279
- version: 1.0.8
279
+ version: 1.0.9
280
280
  type: :development
281
281
  prerelease: false
282
282
  version_requirements: !ruby/object:Gem::Requirement
@@ -286,7 +286,7 @@ dependencies:
286
286
  version: '1.0'
287
287
  - - ">="
288
288
  - !ruby/object:Gem::Version
289
- version: 1.0.8
289
+ version: 1.0.9
290
290
  - !ruby/object:Gem::Dependency
291
291
  name: gitmoji-regex
292
292
  requirement: !ruby/object:Gem::Requirement
@@ -296,7 +296,7 @@ dependencies:
296
296
  version: '2.0'
297
297
  - - ">="
298
298
  - !ruby/object:Gem::Version
299
- version: 2.0.12
299
+ version: 2.0.15
300
300
  type: :development
301
301
  prerelease: false
302
302
  version_requirements: !ruby/object:Gem::Requirement
@@ -306,7 +306,7 @@ dependencies:
306
306
  version: '2.0'
307
307
  - - ">="
308
308
  - !ruby/object:Gem::Version
309
- version: 2.0.12
309
+ version: 2.0.15
310
310
  - !ruby/object:Gem::Dependency
311
311
  name: toml
312
312
  requirement: !ruby/object:Gem::Requirement
@@ -357,6 +357,7 @@ files:
357
357
  - lib/tree_haver/backends/prism.rb
358
358
  - lib/tree_haver/backends/psych.rb
359
359
  - lib/tree_haver/backends/rust.rb
360
+ - lib/tree_haver/backends/rust_tslp.rb
360
361
  - lib/tree_haver/backends/tslp.rb
361
362
  - lib/tree_haver/base/comment.rb
362
363
  - lib/tree_haver/base/language.rb
@@ -389,10 +390,10 @@ licenses:
389
390
  - PolyForm-Small-Business-1.0.0
390
391
  metadata:
391
392
  homepage_uri: https://structuredmerge.org
392
- source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.8
393
- changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.8/CHANGELOG.md
393
+ source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.10
394
+ changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.10/CHANGELOG.md
394
395
  bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
395
- documentation_uri: https://www.rubydoc.info/gems/tree_haver/7.1.8
396
+ documentation_uri: https://www.rubydoc.info/gems/tree_haver/7.1.10
396
397
  funding_uri: https://github.com/sponsors/pboling
397
398
  wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
398
399
  news_uri: https://www.railsbling.com/tags/tree_haver
@@ -423,7 +424,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
423
424
  - !ruby/object:Gem::Version
424
425
  version: '0'
425
426
  requirements: []
426
- rubygems_version: 4.0.17
427
+ rubygems_version: 4.0.20
427
428
  specification_version: 4
428
429
  summary: "\U0001F334 Structured Merge parser abstraction and process analysis for
429
430
  Ruby"
metadata.gz.sig CHANGED
Binary file