rigortype 0.1.3 → 0.1.5

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +154 -33
  3. data/lib/rigor/analysis/check_rules.rb +10 -18
  4. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
  5. data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
  6. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  7. data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
  8. data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
  9. data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
  10. data/lib/rigor/analysis/diagnostic.rb +0 -2
  11. data/lib/rigor/analysis/fact_store.rb +26 -6
  12. data/lib/rigor/analysis/result.rb +11 -3
  13. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  14. data/lib/rigor/analysis/run_stats.rb +193 -0
  15. data/lib/rigor/analysis/runner.rb +498 -12
  16. data/lib/rigor/analysis/worker_session.rb +327 -0
  17. data/lib/rigor/builtins/imported_refinements.rb +364 -55
  18. data/lib/rigor/builtins/regex_refinement.rb +17 -12
  19. data/lib/rigor/cache/descriptor.rb +1 -1
  20. data/lib/rigor/cache/rbs_descriptor.rb +3 -1
  21. data/lib/rigor/cache/store.rb +39 -6
  22. data/lib/rigor/cli/diff_command.rb +1 -1
  23. data/lib/rigor/cli/sig_gen_command.rb +173 -0
  24. data/lib/rigor/cli/type_of_command.rb +1 -1
  25. data/lib/rigor/cli/type_scan_renderer.rb +1 -1
  26. data/lib/rigor/cli/type_scan_report.rb +2 -2
  27. data/lib/rigor/cli.rb +61 -3
  28. data/lib/rigor/configuration/dependencies.rb +2 -2
  29. data/lib/rigor/configuration.rb +131 -6
  30. data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
  31. data/lib/rigor/environment/class_registry.rb +12 -3
  32. data/lib/rigor/environment/lockfile_resolver.rb +125 -0
  33. data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
  34. data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
  35. data/lib/rigor/environment/rbs_loader.rb +194 -6
  36. data/lib/rigor/environment/reflection.rb +152 -0
  37. data/lib/rigor/environment.rb +109 -6
  38. data/lib/rigor/flow_contribution/conflict.rb +2 -2
  39. data/lib/rigor/flow_contribution/element.rb +1 -1
  40. data/lib/rigor/flow_contribution/fact.rb +1 -1
  41. data/lib/rigor/flow_contribution/merge_result.rb +1 -1
  42. data/lib/rigor/flow_contribution/merger.rb +3 -3
  43. data/lib/rigor/flow_contribution.rb +2 -2
  44. data/lib/rigor/inference/acceptance.rb +35 -1
  45. data/lib/rigor/inference/block_parameter_binder.rb +0 -2
  46. data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
  47. data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
  48. data/lib/rigor/inference/coverage_scanner.rb +1 -1
  49. data/lib/rigor/inference/expression_typer.rb +77 -11
  50. data/lib/rigor/inference/fallback.rb +1 -1
  51. data/lib/rigor/inference/macro_block_self_type.rb +96 -0
  52. data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
  53. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -41
  54. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
  55. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
  56. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
  57. data/lib/rigor/inference/method_dispatcher/method_folding.rb +135 -0
  58. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +7 -12
  59. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
  60. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -44
  61. data/lib/rigor/inference/method_dispatcher.rb +274 -5
  62. data/lib/rigor/inference/method_parameter_binder.rb +22 -14
  63. data/lib/rigor/inference/narrowing.rb +129 -12
  64. data/lib/rigor/inference/rbs_type_translator.rb +0 -2
  65. data/lib/rigor/inference/scope_indexer.rb +14 -9
  66. data/lib/rigor/inference/statement_evaluator.rb +7 -7
  67. data/lib/rigor/inference/synthetic_method.rb +86 -0
  68. data/lib/rigor/inference/synthetic_method_index.rb +82 -0
  69. data/lib/rigor/inference/synthetic_method_scanner.rb +521 -0
  70. data/lib/rigor/plugin/blueprint.rb +60 -0
  71. data/lib/rigor/plugin/io_boundary.rb +0 -2
  72. data/lib/rigor/plugin/loader.rb +5 -3
  73. data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
  74. data/lib/rigor/plugin/macro/external_file.rb +143 -0
  75. data/lib/rigor/plugin/macro/heredoc_template.rb +201 -0
  76. data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
  77. data/lib/rigor/plugin/macro.rb +31 -0
  78. data/lib/rigor/plugin/manifest.rb +102 -10
  79. data/lib/rigor/plugin/registry.rb +43 -2
  80. data/lib/rigor/plugin/services.rb +1 -1
  81. data/lib/rigor/plugin/type_node_resolver.rb +52 -0
  82. data/lib/rigor/plugin.rb +2 -0
  83. data/lib/rigor/rbs_extended/reporter.rb +91 -0
  84. data/lib/rigor/rbs_extended.rb +131 -32
  85. data/lib/rigor/scope.rb +25 -8
  86. data/lib/rigor/sig_gen/classification.rb +36 -0
  87. data/lib/rigor/sig_gen/generator.rb +1048 -0
  88. data/lib/rigor/sig_gen/layout_index.rb +108 -0
  89. data/lib/rigor/sig_gen/method_candidate.rb +62 -0
  90. data/lib/rigor/sig_gen/observation_collector.rb +391 -0
  91. data/lib/rigor/sig_gen/observed_call.rb +62 -0
  92. data/lib/rigor/sig_gen/path_mapper.rb +116 -0
  93. data/lib/rigor/sig_gen/renderer.rb +157 -0
  94. data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
  95. data/lib/rigor/sig_gen/write_result.rb +48 -0
  96. data/lib/rigor/sig_gen/writer.rb +530 -0
  97. data/lib/rigor/sig_gen.rb +25 -0
  98. data/lib/rigor/trinary.rb +15 -11
  99. data/lib/rigor/type/bot.rb +6 -3
  100. data/lib/rigor/type/bound_method.rb +79 -0
  101. data/lib/rigor/type/combinator.rb +207 -3
  102. data/lib/rigor/type/constant.rb +13 -0
  103. data/lib/rigor/type/hash_shape.rb +0 -2
  104. data/lib/rigor/type/integer_range.rb +7 -7
  105. data/lib/rigor/type/refined.rb +18 -12
  106. data/lib/rigor/type/top.rb +4 -3
  107. data/lib/rigor/type/union.rb +20 -1
  108. data/lib/rigor/type.rb +1 -0
  109. data/lib/rigor/type_node/generic.rb +68 -0
  110. data/lib/rigor/type_node/identifier.rb +38 -0
  111. data/lib/rigor/type_node/indexed_access.rb +41 -0
  112. data/lib/rigor/type_node/integer_literal.rb +29 -0
  113. data/lib/rigor/type_node/name_scope.rb +52 -0
  114. data/lib/rigor/type_node/resolver_chain.rb +56 -0
  115. data/lib/rigor/type_node/string_literal.rb +32 -0
  116. data/lib/rigor/type_node/symbol_literal.rb +28 -0
  117. data/lib/rigor/type_node/union.rb +42 -0
  118. data/lib/rigor/type_node.rb +29 -0
  119. data/lib/rigor/version.rb +1 -1
  120. data/lib/rigor.rb +2 -0
  121. data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
  122. data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
  123. data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
  124. data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
  125. data/sig/rigor/cli/diff_command.rbs +4 -0
  126. data/sig/rigor/cli/explain_command.rbs +4 -0
  127. data/sig/rigor/cli/sig_gen_command.rbs +4 -0
  128. data/sig/rigor/cli/type_scan_command.rbs +3 -0
  129. data/sig/rigor/environment.rbs +8 -2
  130. data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
  131. data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
  132. data/sig/rigor/inference/builtins.rbs +2 -0
  133. data/sig/rigor/plugin/access_denied_error.rbs +3 -0
  134. data/sig/rigor/plugin/base.rbs +6 -0
  135. data/sig/rigor/plugin/blueprint.rbs +7 -0
  136. data/sig/rigor/plugin/fact_store.rbs +11 -0
  137. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  138. data/sig/rigor/plugin/load_error.rbs +6 -0
  139. data/sig/rigor/plugin/loader.rbs +20 -0
  140. data/sig/rigor/plugin/manifest.rbs +9 -0
  141. data/sig/rigor/plugin/registry.rbs +16 -0
  142. data/sig/rigor/plugin/services.rbs +3 -0
  143. data/sig/rigor/plugin/trust_policy.rbs +4 -0
  144. data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
  145. data/sig/rigor/plugin.rbs +8 -0
  146. data/sig/rigor/scope.rbs +4 -2
  147. data/sig/rigor/type.rbs +28 -6
  148. data/sig/rigor.rbs +35 -2
  149. metadata +90 -1
@@ -0,0 +1,327 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../environment"
6
+ require_relative "../scope"
7
+ require_relative "../cache/store"
8
+ require_relative "../plugin"
9
+ require_relative "../rbs_extended/reporter"
10
+ require_relative "../reflection"
11
+ require_relative "../type/combinator"
12
+ require_relative "../inference/coverage_scanner"
13
+ require_relative "../inference/scope_indexer"
14
+ require_relative "../inference/method_dispatcher/file_folding"
15
+ require_relative "check_rules"
16
+ require_relative "dependency_source_inference"
17
+ require_relative "diagnostic"
18
+
19
+ module Rigor
20
+ module Analysis
21
+ # ADR-15 Phase 4a — per-worker analysis substrate.
22
+ # [ADR-15](../../../docs/adr/15-ractor-concurrency.md)
23
+ # § Phase 4 carves the eventual Ractor-isolated worker pool
24
+ # into three sub-phases; this is the substrate that 4b will
25
+ # wrap in `Ractor.new` and 4c will gate behind
26
+ # `RIGOR_RACTOR_WORKERS`. NO Ractor in the loop yet — 4a
27
+ # exists so the per-worker ownership boundary is testable in
28
+ # the absence of any Ractor coordination.
29
+ #
30
+ # The constructor takes only `Ractor.shareable?` inputs:
31
+ #
32
+ # - `configuration` — Phase 2a ({Rigor::Configuration} is
33
+ # `Ractor.shareable?`).
34
+ # - `cache_store` — frozen-shareable handle is NOT a precondition;
35
+ # future 4b workers build their OWN Store at the shared
36
+ # `cache_root` directory. 4a accepts an already-built Store
37
+ # for the no-Ractor coordinator path.
38
+ # - `plugin_blueprints` — Phase 3a
39
+ # (`Array<Plugin::Blueprint>` is `Ractor.shareable?`).
40
+ # - `explain` — Boolean.
41
+ #
42
+ # Internally the session OWNS (and never shares):
43
+ #
44
+ # - {Rigor::Plugin::Services} bound to the per-worker Store.
45
+ # - {Rigor::Plugin::Registry} materialised from the blueprints
46
+ # via {Rigor::Plugin::Registry.materialize}; each plugin
47
+ # instance, with its mutable per-run accumulators
48
+ # (`@reachable_absurd_nodes`, `*_index`, …) lives entirely
49
+ # inside this session.
50
+ # - {Rigor::RbsExtended::Reporter} +
51
+ # {Rigor::Analysis::DependencySourceInference::BoundaryCrossReporter}
52
+ # (Mutex-bearing; intentionally per-worker — the runner
53
+ # merges entries post-pool via {#drain_reporters}).
54
+ # - {Rigor::Environment} threaded with the per-worker reporters
55
+ # so reporter writes from inference / dispatcher accumulate
56
+ # into the worker's own state.
57
+ #
58
+ # Plugin `prepare` runs ONCE at construction time so each
59
+ # worker is "warm" by the time `#analyze` is first called. Any
60
+ # raise from `prepare` is captured into {#prepare_diagnostics}
61
+ # so the runner can surface them alongside the per-file
62
+ # diagnostic stream.
63
+ #
64
+ # Equivalence contract (proven by spec): given identical
65
+ # `(configuration, cache_store, plugin_blueprints)`, the
66
+ # multiset of diagnostics from
67
+ # `paths.flat_map { |p| session.analyze(p) }` plus
68
+ # {#prepare_diagnostics} plus reporter drains MUST equal the
69
+ # corresponding subset of {Rigor::Analysis::Runner#run}'s
70
+ # output (modulo severity-profile re-stamping, which the
71
+ # session leaves to the caller because it is a per-run
72
+ # aggregate concern).
73
+ class WorkerSession
74
+ attr_reader :configuration, :cache_store, :services, :plugin_registry,
75
+ :dependency_source_index, :environment,
76
+ :rbs_extended_reporter, :boundary_cross_reporter,
77
+ :prepare_diagnostics
78
+
79
+ # @param configuration [Rigor::Configuration]
80
+ # @param cache_store [Rigor::Cache::Store, nil] persistent
81
+ # cache the session exposes to plugin-side producers and
82
+ # the RBS loader. Pass `nil` to disable caching.
83
+ # @param plugin_blueprints [Array<Rigor::Plugin::Blueprint>]
84
+ # replay descriptors. Empty array yields a session with
85
+ # no plugin contributions.
86
+ # @param explain [Boolean] when true, `#analyze` additionally
87
+ # emits one `:info` `fallback` diagnostic per
88
+ # directly-unrecognised node, mirroring
89
+ # {Rigor::Analysis::Runner#explain_diagnostics}.
90
+ def initialize(configuration:, cache_store: nil, # rubocop:disable Metrics/MethodLength
91
+ plugin_blueprints: [], explain: false)
92
+ @configuration = configuration
93
+ @cache_store = cache_store
94
+ @explain = explain
95
+
96
+ # NOTE: `Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths`
97
+ # is process-global state. Writing it from a non-main
98
+ # Ractor would raise `Ractor::IsolationError`, so the
99
+ # session does NOT touch it — the CALLER (typically
100
+ # {Rigor::Analysis::Runner#run}) is responsible for
101
+ # setting it on the main Ractor before spawning the
102
+ # pool. The substrate stays Ractor-safe by construction.
103
+ @rbs_extended_reporter = RbsExtended::Reporter.new
104
+ @boundary_cross_reporter = DependencySourceInference::BoundaryCrossReporter.new
105
+ @dependency_source_index = DependencySourceInference::Builder.build(configuration.dependencies)
106
+
107
+ @services = Plugin::Services.new(
108
+ reflection: Reflection,
109
+ type: Type::Combinator,
110
+ configuration: configuration,
111
+ cache_store: cache_store,
112
+ trust_policy: build_trust_policy
113
+ )
114
+ @plugin_registry = Plugin::Registry.materialize(
115
+ blueprints: plugin_blueprints, services: @services
116
+ )
117
+ @environment = Environment.for_project(
118
+ libraries: configuration.libraries,
119
+ signature_paths: configuration.signature_paths,
120
+ cache_store: cache_store,
121
+ plugin_registry: @plugin_registry,
122
+ dependency_source_index: @dependency_source_index,
123
+ rbs_extended_reporter: @rbs_extended_reporter,
124
+ boundary_cross_reporter: @boundary_cross_reporter,
125
+ bundler_bundle_path: configuration.bundler_bundle_path,
126
+ bundler_auto_detect: configuration.bundler_auto_detect,
127
+ bundler_lockfile: configuration.bundler_lockfile,
128
+ rbs_collection_lockfile: configuration.rbs_collection_lockfile,
129
+ rbs_collection_auto_detect: configuration.rbs_collection_auto_detect
130
+ )
131
+ @prepare_diagnostics = run_plugin_prepare.freeze
132
+ end
133
+
134
+ # Equivalent of {Rigor::Analysis::Runner#analyze_file} +
135
+ # `plugin_emitted_diagnostics` + `explain_diagnostics`.
136
+ # Returns a flat `Array<Diagnostic>` for the file. Severity
137
+ # profile re-stamping is intentionally NOT applied — that
138
+ # is a per-run aggregate concern handled by the caller.
139
+ def analyze(path)
140
+ parse_result = Prism.parse_file(path, version: @configuration.target_ruby)
141
+ return parse_diagnostics(path, parse_result) unless parse_result.errors.empty?
142
+
143
+ scope = Scope.empty(environment: @environment, source_path: path)
144
+ index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
145
+ diagnostics = CheckRules.diagnose(
146
+ path: path,
147
+ root: parse_result.value,
148
+ scope_index: index,
149
+ comments: parse_result.comments,
150
+ disabled_rules: @configuration.disabled_rules
151
+ )
152
+ diagnostics += plugin_emitted_diagnostics(path, parse_result.value, scope)
153
+ diagnostics + explain_diagnostics(path, parse_result.value, scope)
154
+ rescue Errno::ENOENT => e
155
+ [analyzer_error(path, e.message)]
156
+ rescue StandardError => e
157
+ [analyzer_error(path, "internal analyzer error: #{e.class}: #{e.message}")]
158
+ end
159
+
160
+ # Read-once snapshot of the per-worker reporters so the
161
+ # caller (or the eventual Phase 4b pool aggregator) can
162
+ # merge into a single coordinator-side reporter. Both
163
+ # reporters dedupe at write time, so a post-hoc concat +
164
+ # de-dup at the entry-key level is sound.
165
+ def drain_reporters
166
+ {
167
+ rbs_extended: {
168
+ unresolved_payloads: @rbs_extended_reporter.unresolved_payloads,
169
+ lossy_projections: @rbs_extended_reporter.lossy_projections
170
+ },
171
+ boundary_cross: @boundary_cross_reporter.entries
172
+ }
173
+ end
174
+
175
+ private
176
+
177
+ # Mirrors {Runner#build_trust_policy}. Workers under Phase
178
+ # 4b will need the same trust derivation, and the
179
+ # configuration is already shareable, so deriving it inside
180
+ # the session keeps the substrate decoupled from the
181
+ # coordinator's helper.
182
+ def build_trust_policy
183
+ trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
184
+ roots = [Dir.pwd]
185
+ Array(@configuration.signature_paths).each { |sp| roots << File.expand_path(sp) }
186
+ trusted_gems.each do |gem_name|
187
+ path = trusted_gem_root(gem_name)
188
+ roots << path if path
189
+ end
190
+ @configuration.plugins_io_allowed_paths.each { |p| roots << File.expand_path(p) }
191
+
192
+ Plugin::TrustPolicy.new(
193
+ trusted_gems: trusted_gems,
194
+ allowed_read_roots: roots,
195
+ network_policy: @configuration.plugins_io_network,
196
+ allowed_url_hosts: @configuration.plugins_io_allowed_url_hosts
197
+ )
198
+ end
199
+
200
+ def trusted_gem_name(entry)
201
+ case entry
202
+ when String then entry
203
+ when Hash then entry["gem"] || entry["id"]
204
+ end
205
+ end
206
+
207
+ def trusted_gem_root(gem_name)
208
+ return nil if gem_name.nil? || gem_name.empty?
209
+
210
+ spec = Gem.loaded_specs[gem_name]
211
+ spec&.full_gem_path # rigor:disable undefined-method
212
+ rescue StandardError
213
+ nil
214
+ end
215
+
216
+ def run_plugin_prepare
217
+ return [] if @plugin_registry.empty?
218
+
219
+ @plugin_registry.plugins.flat_map do |plugin|
220
+ plugin.prepare(plugin.services)
221
+ []
222
+ rescue StandardError => e
223
+ [plugin_prepare_error_diagnostic(plugin, e)]
224
+ end
225
+ end
226
+
227
+ def plugin_prepare_error_diagnostic(plugin, error)
228
+ plugin_id = safe_plugin_id(plugin)
229
+ Diagnostic.new(
230
+ path: ".rigor.yml",
231
+ line: 1,
232
+ column: 1,
233
+ message: "plugin #{plugin_id.inspect} raised during prepare: " \
234
+ "#{error.class}: #{error.message}",
235
+ severity: :error,
236
+ rule: "runtime-error",
237
+ source_family: :plugin_loader
238
+ )
239
+ end
240
+
241
+ def plugin_emitted_diagnostics(path, root, scope)
242
+ return [] if @plugin_registry.empty?
243
+
244
+ @plugin_registry.plugins.flat_map do |plugin|
245
+ collect_plugin_diagnostics(plugin, path, root, scope)
246
+ end
247
+ end
248
+
249
+ def collect_plugin_diagnostics(plugin, path, root, scope)
250
+ raw = plugin.diagnostics_for_file(path: path, scope: scope, root: root)
251
+ Array(raw).map { |diagnostic| stamp_plugin_diagnostic(diagnostic, plugin.manifest.id) }
252
+ rescue StandardError => e
253
+ [plugin_runtime_error_diagnostic(path, plugin, e)]
254
+ end
255
+
256
+ def stamp_plugin_diagnostic(diagnostic, plugin_id)
257
+ Diagnostic.new(
258
+ path: diagnostic.path,
259
+ line: diagnostic.line,
260
+ column: diagnostic.column,
261
+ message: diagnostic.message,
262
+ severity: diagnostic.severity,
263
+ rule: diagnostic.rule,
264
+ source_family: "plugin.#{plugin_id}"
265
+ )
266
+ end
267
+
268
+ def plugin_runtime_error_diagnostic(path, plugin, error)
269
+ plugin_id = safe_plugin_id(plugin)
270
+ Diagnostic.new(
271
+ path: path,
272
+ line: 1,
273
+ column: 1,
274
+ message: "plugin #{plugin_id.inspect} raised during diagnostics_for_file: " \
275
+ "#{error.class}: #{error.message}",
276
+ severity: :error,
277
+ rule: "runtime-error",
278
+ source_family: :plugin_loader
279
+ )
280
+ end
281
+
282
+ def safe_plugin_id(plugin)
283
+ plugin.manifest.id
284
+ rescue StandardError
285
+ plugin.class.to_s
286
+ end
287
+
288
+ def explain_diagnostics(path, root, scope)
289
+ return [] unless @explain
290
+
291
+ result = Inference::CoverageScanner.new(scope: scope).scan(root)
292
+ result.events.map { |event| explain_diagnostic(path, event) }
293
+ end
294
+
295
+ def explain_diagnostic(path, event)
296
+ location = event.location
297
+ line = location ? location.start_line : 1
298
+ column = location ? location.start_column + 1 : 1
299
+ Diagnostic.new(
300
+ path: path,
301
+ line: line,
302
+ column: column,
303
+ message: "fail-soft fallback at #{event.node_class}: #{event.inner_type.describe(:short)}",
304
+ severity: :info,
305
+ rule: "fallback"
306
+ )
307
+ end
308
+
309
+ def parse_diagnostics(path, parse_result)
310
+ parse_result.errors.map do |error|
311
+ location = error.location
312
+ Diagnostic.new(
313
+ path: path,
314
+ line: location.start_line,
315
+ column: location.start_column + 1,
316
+ message: error.message,
317
+ severity: :error
318
+ )
319
+ end
320
+ end
321
+
322
+ def analyzer_error(path, message)
323
+ Diagnostic.new(path: path, line: 1, column: 1, message: message, severity: :error)
324
+ end
325
+ end
326
+ end
327
+ end