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.
- checksums.yaml +4 -4
- data/README.md +154 -33
- data/lib/rigor/analysis/check_rules.rb +10 -18
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
- data/lib/rigor/analysis/diagnostic.rb +0 -2
- data/lib/rigor/analysis/fact_store.rb +26 -6
- data/lib/rigor/analysis/result.rb +11 -3
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +193 -0
- data/lib/rigor/analysis/runner.rb +498 -12
- data/lib/rigor/analysis/worker_session.rb +327 -0
- data/lib/rigor/builtins/imported_refinements.rb +364 -55
- data/lib/rigor/builtins/regex_refinement.rb +17 -12
- data/lib/rigor/cache/descriptor.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +3 -1
- data/lib/rigor/cache/store.rb +39 -6
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +173 -0
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_renderer.rb +1 -1
- data/lib/rigor/cli/type_scan_report.rb +2 -2
- data/lib/rigor/cli.rb +61 -3
- data/lib/rigor/configuration/dependencies.rb +2 -2
- data/lib/rigor/configuration.rb +131 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
- data/lib/rigor/environment/class_registry.rb +12 -3
- data/lib/rigor/environment/lockfile_resolver.rb +125 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
- data/lib/rigor/environment/rbs_loader.rb +194 -6
- data/lib/rigor/environment/reflection.rb +152 -0
- data/lib/rigor/environment.rb +109 -6
- data/lib/rigor/flow_contribution/conflict.rb +2 -2
- data/lib/rigor/flow_contribution/element.rb +1 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution/merge_result.rb +1 -1
- data/lib/rigor/flow_contribution/merger.rb +3 -3
- data/lib/rigor/flow_contribution.rb +2 -2
- data/lib/rigor/inference/acceptance.rb +35 -1
- data/lib/rigor/inference/block_parameter_binder.rb +0 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
- data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
- data/lib/rigor/inference/coverage_scanner.rb +1 -1
- data/lib/rigor/inference/expression_typer.rb +77 -11
- data/lib/rigor/inference/fallback.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +96 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -41
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +135 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +7 -12
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -44
- data/lib/rigor/inference/method_dispatcher.rb +274 -5
- data/lib/rigor/inference/method_parameter_binder.rb +22 -14
- data/lib/rigor/inference/narrowing.rb +129 -12
- data/lib/rigor/inference/rbs_type_translator.rb +0 -2
- data/lib/rigor/inference/scope_indexer.rb +14 -9
- data/lib/rigor/inference/statement_evaluator.rb +7 -7
- data/lib/rigor/inference/synthetic_method.rb +86 -0
- data/lib/rigor/inference/synthetic_method_index.rb +82 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +521 -0
- data/lib/rigor/plugin/blueprint.rb +60 -0
- data/lib/rigor/plugin/io_boundary.rb +0 -2
- data/lib/rigor/plugin/loader.rb +5 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
- data/lib/rigor/plugin/macro/external_file.rb +143 -0
- data/lib/rigor/plugin/macro/heredoc_template.rb +201 -0
- data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
- data/lib/rigor/plugin/macro.rb +31 -0
- data/lib/rigor/plugin/manifest.rb +102 -10
- data/lib/rigor/plugin/registry.rb +43 -2
- data/lib/rigor/plugin/services.rb +1 -1
- data/lib/rigor/plugin/type_node_resolver.rb +52 -0
- data/lib/rigor/plugin.rb +2 -0
- data/lib/rigor/rbs_extended/reporter.rb +91 -0
- data/lib/rigor/rbs_extended.rb +131 -32
- data/lib/rigor/scope.rb +25 -8
- data/lib/rigor/sig_gen/classification.rb +36 -0
- data/lib/rigor/sig_gen/generator.rb +1048 -0
- data/lib/rigor/sig_gen/layout_index.rb +108 -0
- data/lib/rigor/sig_gen/method_candidate.rb +62 -0
- data/lib/rigor/sig_gen/observation_collector.rb +391 -0
- data/lib/rigor/sig_gen/observed_call.rb +62 -0
- data/lib/rigor/sig_gen/path_mapper.rb +116 -0
- data/lib/rigor/sig_gen/renderer.rb +157 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
- data/lib/rigor/sig_gen/write_result.rb +48 -0
- data/lib/rigor/sig_gen/writer.rb +530 -0
- data/lib/rigor/sig_gen.rb +25 -0
- data/lib/rigor/trinary.rb +15 -11
- data/lib/rigor/type/bot.rb +6 -3
- data/lib/rigor/type/bound_method.rb +79 -0
- data/lib/rigor/type/combinator.rb +207 -3
- data/lib/rigor/type/constant.rb +13 -0
- data/lib/rigor/type/hash_shape.rb +0 -2
- data/lib/rigor/type/integer_range.rb +7 -7
- data/lib/rigor/type/refined.rb +18 -12
- data/lib/rigor/type/top.rb +4 -3
- data/lib/rigor/type/union.rb +20 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +68 -0
- data/lib/rigor/type_node/identifier.rb +38 -0
- data/lib/rigor/type_node/indexed_access.rb +41 -0
- data/lib/rigor/type_node/integer_literal.rb +29 -0
- data/lib/rigor/type_node/name_scope.rb +52 -0
- data/lib/rigor/type_node/resolver_chain.rb +56 -0
- data/lib/rigor/type_node/string_literal.rb +32 -0
- data/lib/rigor/type_node/symbol_literal.rb +28 -0
- data/lib/rigor/type_node/union.rb +42 -0
- data/lib/rigor/type_node.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -0
- data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
- data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
- data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
- data/sig/rigor/cli/diff_command.rbs +4 -0
- data/sig/rigor/cli/explain_command.rbs +4 -0
- data/sig/rigor/cli/sig_gen_command.rbs +4 -0
- data/sig/rigor/cli/type_scan_command.rbs +3 -0
- data/sig/rigor/environment.rbs +8 -2
- data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
- data/sig/rigor/inference/builtins.rbs +2 -0
- data/sig/rigor/plugin/access_denied_error.rbs +3 -0
- data/sig/rigor/plugin/base.rbs +6 -0
- data/sig/rigor/plugin/blueprint.rbs +7 -0
- data/sig/rigor/plugin/fact_store.rbs +11 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/load_error.rbs +6 -0
- data/sig/rigor/plugin/loader.rbs +20 -0
- data/sig/rigor/plugin/manifest.rbs +9 -0
- data/sig/rigor/plugin/registry.rbs +16 -0
- data/sig/rigor/plugin/services.rbs +3 -0
- data/sig/rigor/plugin/trust_policy.rbs +4 -0
- data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
- data/sig/rigor/plugin.rbs +8 -0
- data/sig/rigor/scope.rbs +4 -2
- data/sig/rigor/type.rbs +28 -6
- data/sig/rigor.rbs +35 -2
- 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
|