rigortype 0.1.16 → 0.1.17
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/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +209 -0
- data/lib/rigor/analysis/check_rules.rb +149 -70
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +18 -0
- data/lib/rigor/analysis/incremental.rb +162 -0
- data/lib/rigor/analysis/incremental_session.rb +337 -0
- data/lib/rigor/analysis/rule_catalog.rb +48 -0
- data/lib/rigor/analysis/runner.rb +434 -37
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +50 -49
- data/lib/rigor/cache/incremental_snapshot.rb +147 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +30 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
- data/lib/rigor/cache/rbs_constant_table.rb +2 -8
- data/lib/rigor/cache/rbs_environment.rb +2 -8
- data/lib/rigor/cache/rbs_instance_definitions.rb +3 -16
- data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +99 -1
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/command.rb +47 -0
- data/lib/rigor/cli/coverage_command.rb +3 -23
- data/lib/rigor/cli/coverage_renderer.rb +3 -8
- data/lib/rigor/cli/diff_command.rb +3 -7
- data/lib/rigor/cli/explain_command.rb +2 -7
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mcp_command.rb +3 -7
- data/lib/rigor/cli/options.rb +57 -0
- data/lib/rigor/cli/plugin_command.rb +3 -7
- data/lib/rigor/cli/plugins_command.rb +2 -7
- data/lib/rigor/cli/renderable.rb +26 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -7
- data/lib/rigor/cli/skill_command.rb +3 -7
- data/lib/rigor/cli/triage_command.rb +2 -7
- data/lib/rigor/cli/type_of_command.rb +5 -38
- data/lib/rigor/cli/type_of_renderer.rb +4 -9
- data/lib/rigor/cli/type_scan_command.rb +3 -23
- data/lib/rigor/cli/type_scan_renderer.rb +4 -9
- data/lib/rigor/cli.rb +125 -43
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +13 -3
- data/lib/rigor/environment/rbs_loader.rb +76 -3
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
- data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
- data/lib/rigor/inference/expression_typer.rb +140 -20
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
- data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher.rb +99 -59
- data/lib/rigor/inference/narrowing.rb +202 -5
- data/lib/rigor/inference/scope_indexer.rb +134 -7
- data/lib/rigor/inference/statement_evaluator.rb +105 -26
- data/lib/rigor/language_server/buffer_resolution.rb +33 -0
- data/lib/rigor/language_server/completion_provider.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
- data/lib/rigor/language_server/folding_range_provider.rb +4 -4
- data/lib/rigor/language_server/hover_provider.rb +4 -4
- data/lib/rigor/language_server/selection_range_provider.rb +4 -4
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/plugin/base.rb +20 -4
- data/lib/rigor/plugin/registry.rb +39 -1
- data/lib/rigor/rbs_extended/conformance_checker.rb +208 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope.rb +123 -9
- data/lib/rigor/type/acceptance_router.rb +19 -0
- data/lib/rigor/type/accepts_result.rb +3 -10
- data/lib/rigor/type/app.rb +3 -7
- data/lib/rigor/type/bot.rb +2 -3
- data/lib/rigor/type/bound_method.rb +5 -12
- data/lib/rigor/type/combinator.rb +17 -0
- data/lib/rigor/type/constant.rb +2 -3
- data/lib/rigor/type/data_class.rb +80 -0
- data/lib/rigor/type/data_instance.rb +100 -0
- data/lib/rigor/type/difference.rb +5 -10
- data/lib/rigor/type/dynamic.rb +5 -10
- data/lib/rigor/type/hash_shape.rb +5 -15
- data/lib/rigor/type/integer_range.rb +5 -10
- data/lib/rigor/type/intersection.rb +5 -10
- data/lib/rigor/type/nominal.rb +5 -10
- data/lib/rigor/type/refined.rb +5 -10
- data/lib/rigor/type/singleton.rb +5 -10
- data/lib/rigor/type/top.rb +2 -3
- data/lib/rigor/type/tuple.rb +5 -10
- data/lib/rigor/type/union.rb +5 -10
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/value_semantics.rb +77 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
- data/sig/rigor/cache.rbs +19 -0
- data/sig/rigor/inference.rbs +22 -0
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +5 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +6 -1
- metadata +22 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "digest"
|
|
3
4
|
require "prism"
|
|
4
5
|
require "tmpdir"
|
|
5
6
|
|
|
6
7
|
require_relative "../environment"
|
|
7
8
|
require_relative "../scope"
|
|
8
9
|
require_relative "../cache/store"
|
|
10
|
+
require_relative "../cache/rbs_descriptor"
|
|
9
11
|
require_relative "../plugin"
|
|
10
12
|
require_relative "../plugin/source_rbs_synthesis_reporter"
|
|
11
13
|
require_relative "../rbs_extended/reporter"
|
|
14
|
+
require_relative "../rbs_extended/conformance_checker"
|
|
12
15
|
require_relative "../reflection"
|
|
13
16
|
require_relative "../type/combinator"
|
|
14
17
|
require_relative "../inference/coverage_scanner"
|
|
@@ -18,6 +21,10 @@ require_relative "../inference/project_patched_scanner"
|
|
|
18
21
|
require_relative "../inference/method_dispatcher/file_folding"
|
|
19
22
|
require_relative "buffer_binding"
|
|
20
23
|
require_relative "check_rules"
|
|
24
|
+
require_relative "dependency_recorder"
|
|
25
|
+
require_relative "self_call_resolution_recorder"
|
|
26
|
+
require_relative "incremental"
|
|
27
|
+
require_relative "incremental_session"
|
|
21
28
|
require_relative "dependency_source_inference"
|
|
22
29
|
require_relative "diagnostic"
|
|
23
30
|
require_relative "erb_template_detector"
|
|
@@ -33,7 +40,8 @@ module Rigor
|
|
|
33
40
|
DEFAULT_CACHE_ROOT = ".rigor/cache"
|
|
34
41
|
|
|
35
42
|
attr_reader :cache_store, :plugin_registry, :dependency_source_index,
|
|
36
|
-
:rbs_extended_reporter, :boundary_cross_reporter
|
|
43
|
+
:rbs_extended_reporter, :boundary_cross_reporter, :file_dependencies,
|
|
44
|
+
:analyzed_files, :unresolved_self_calls
|
|
37
45
|
|
|
38
46
|
# @param configuration [Rigor::Configuration]
|
|
39
47
|
# @param explain [Boolean] surface fail-soft fallback events
|
|
@@ -82,10 +90,11 @@ module Rigor
|
|
|
82
90
|
# (bundler / lockfile / collection discovery, RbsLoader
|
|
83
91
|
# construction). Pool mode ignores the override — each
|
|
84
92
|
# worker continues to build its own Environment.
|
|
85
|
-
def initialize(configuration:, explain: false, # rubocop:disable Metrics/ParameterLists
|
|
93
|
+
def initialize(configuration:, explain: false, # rubocop:disable Metrics/ParameterLists,Metrics/AbcSize,Metrics/MethodLength
|
|
86
94
|
cache_store: Cache::Store.new(root: DEFAULT_CACHE_ROOT),
|
|
87
95
|
plugin_requirer: nil, workers: 0, collect_stats: true,
|
|
88
|
-
buffer: nil, prebuilt: nil, environment: nil
|
|
96
|
+
buffer: nil, prebuilt: nil, environment: nil,
|
|
97
|
+
record_dependencies: false, record_self_calls: false, analyze_only: nil)
|
|
89
98
|
@configuration = configuration
|
|
90
99
|
@explain = explain
|
|
91
100
|
@cache_store = enforce_read_only_cache(cache_store, buffer)
|
|
@@ -95,6 +104,35 @@ module Rigor
|
|
|
95
104
|
@buffer = buffer
|
|
96
105
|
@prebuilt = prebuilt
|
|
97
106
|
@environment_override = environment
|
|
107
|
+
# ADR-46 slice 1 — opt-in cross-file dependency recording. Off by
|
|
108
|
+
# default; when true, `analyze_file` records each file's
|
|
109
|
+
# cross-file reads into `file_dependencies` (the incremental
|
|
110
|
+
# cache, a later slice, consumes them).
|
|
111
|
+
@record_dependencies = record_dependencies
|
|
112
|
+
# ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording.
|
|
113
|
+
# Off by default; when true, `analyze_file` activates the engine
|
|
114
|
+
# choke-point recorder and collects each file's misses into
|
|
115
|
+
# `unresolved_self_calls` (a later closed-class-gated rule consumes
|
|
116
|
+
# them). Purely observational — diagnostics are byte-identical.
|
|
117
|
+
@record_self_calls = record_self_calls
|
|
118
|
+
@unresolved_self_calls = {}
|
|
119
|
+
# Memoised activation decision for the `call.self-undefined-method`
|
|
120
|
+
# rule (nil = not yet computed). See `self_undefined_rule_active?`.
|
|
121
|
+
@self_undefined_rule_active = nil
|
|
122
|
+
@analyzed_files = [].freeze
|
|
123
|
+
# In-memory source map for `#run_source` — `{ logical_path => source
|
|
124
|
+
# String }`. When set, `parse_source` reads bytes from here instead
|
|
125
|
+
# of disk and `expand_paths` accepts the (possibly non-existent)
|
|
126
|
+
# logical path. nil on a normal disk-backed run.
|
|
127
|
+
@in_memory_sources = nil
|
|
128
|
+
# ADR-46 slice 2 — the subset-analysis hook. When set (a collection
|
|
129
|
+
# of paths), the whole-project pre-pass still runs over every file
|
|
130
|
+
# (so the cross-file index is complete), but only files in this set
|
|
131
|
+
# are analyzed for diagnostics — the body tier re-analyses the
|
|
132
|
+
# affected closure and serves the rest from the per-file cache.
|
|
133
|
+
# `nil` (the default) analyzes everything.
|
|
134
|
+
@analyze_only = analyze_only && Set.new(analyze_only)
|
|
135
|
+
@file_dependencies = {}
|
|
98
136
|
@plugin_registry = Plugin::Registry::EMPTY
|
|
99
137
|
@dependency_source_index = DependencySourceInference::Index::EMPTY
|
|
100
138
|
@rbs_extended_reporter = RbsExtended::Reporter.new
|
|
@@ -103,18 +141,27 @@ module Rigor
|
|
|
103
141
|
# `#run` resets these for each invocation; pre-seed them to
|
|
104
142
|
# empty containers so `build_run_stats` / `pre_file_diagnostics`
|
|
105
143
|
# (private, called only from `#run`) can read them without
|
|
106
|
-
# nil-guards.
|
|
144
|
+
# nil-guards. Kept inline (not a helper) so the engine's own
|
|
145
|
+
# flow analysis sees the ivars established in the constructor.
|
|
107
146
|
@class_decl_paths_snapshot = {}.freeze
|
|
108
147
|
@signature_paths_snapshot = [].freeze
|
|
109
148
|
@synthesized_namespaces_snapshot = [].freeze
|
|
149
|
+
# `rigor:v1:conforms-to` results, snapshotted from the
|
|
150
|
+
# per-run RBS env in `analyze_files_sequentially` (gated on
|
|
151
|
+
# the project declaring `signature_paths:`) and drained by
|
|
152
|
+
# `conforms_to_diagnostics`. Inline default per the comment
|
|
153
|
+
# above so the engine's own flow analysis sees it seeded.
|
|
154
|
+
@conformance_results_snapshot = [].freeze
|
|
110
155
|
@cached_plugin_prepare_diagnostics = [].freeze
|
|
111
156
|
@project_discovered_classes = {}.freeze
|
|
112
157
|
@project_discovered_def_nodes = {}.freeze
|
|
113
158
|
@project_discovered_def_sources = {}.freeze
|
|
114
159
|
@project_discovered_superclasses = {}.freeze
|
|
115
160
|
@project_discovered_includes = {}.freeze
|
|
161
|
+
@project_discovered_class_sources = {}.freeze
|
|
116
162
|
@project_discovered_method_visibilities = {}.freeze
|
|
117
163
|
@project_discovered_methods = {}.freeze
|
|
164
|
+
@project_data_member_layouts = {}.freeze
|
|
118
165
|
end
|
|
119
166
|
|
|
120
167
|
# ADR-pending editor mode — present when the runner is wired
|
|
@@ -144,6 +191,7 @@ module Rigor
|
|
|
144
191
|
@class_decl_paths_snapshot = {}.freeze
|
|
145
192
|
@signature_paths_snapshot = []
|
|
146
193
|
@synthesized_namespaces_snapshot = []
|
|
194
|
+
@conformance_results_snapshot = []
|
|
147
195
|
|
|
148
196
|
if @prebuilt
|
|
149
197
|
adopt_prebuilt_project_scan(@prebuilt)
|
|
@@ -151,17 +199,210 @@ module Rigor
|
|
|
151
199
|
run_project_pre_passes(expansion: expansion)
|
|
152
200
|
end
|
|
153
201
|
|
|
202
|
+
diagnostics = compute_run_diagnostics(expansion)
|
|
203
|
+
|
|
204
|
+
Result.new(
|
|
205
|
+
diagnostics: apply_severity_profile(diagnostics),
|
|
206
|
+
stats: stats_for_run(wall_started_at: wall_started_at, expansion: expansion)
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Analyze a single source String in memory, without writing it to
|
|
211
|
+
# disk — a clean entry point for embedders (LSP / editor mode) and a
|
|
212
|
+
# faster spec path than the per-call tmpdir + chdir. The source is
|
|
213
|
+
# bound to `path` (purely a logical identity carried in diagnostic
|
|
214
|
+
# locations; it need not exist on disk). The full run machinery still
|
|
215
|
+
# runs — environment build, plugin `prepare`, severity profile — so
|
|
216
|
+
# the result matches a one-file disk run; only the cross-file project
|
|
217
|
+
# pre-pass is empty (there is one file, and the per-file indexer
|
|
218
|
+
# self-discovers its own classes / defs).
|
|
219
|
+
#
|
|
220
|
+
# @param source [String] Ruby source to analyze.
|
|
221
|
+
# @param path [String] logical path for diagnostic locations.
|
|
222
|
+
# @return [Result]
|
|
223
|
+
def run_source(source:, path: "(source).rb")
|
|
224
|
+
@in_memory_sources = { path => source }
|
|
225
|
+
run([path])
|
|
226
|
+
ensure
|
|
227
|
+
@in_memory_sources = nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# ADR-46 — the project file set that a run over `paths` would
|
|
231
|
+
# analyze, computed by globbing only (no RBS environment build), so
|
|
232
|
+
# the incremental fingerprint can be derived cheaply on the warm path
|
|
233
|
+
# before deciding whether to build the env at all.
|
|
234
|
+
def analysis_file_set(paths = @configuration.paths)
|
|
235
|
+
expand_paths(paths).fetch(:files)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# ADR-46 §2 — inverts {#file_dependencies} into the reverse edge the
|
|
239
|
+
# incremental step walks: `dependents[X] = { A : A read a
|
|
240
|
+
# declaration / body from X }`. On an edit to X, the body tier
|
|
241
|
+
# (slice 2) re-analyses `{X} ∪ dependents[X]` and serves every other
|
|
242
|
+
# file from the per-file cache. Built on demand from the recorded
|
|
243
|
+
# `sources` sets (so it reflects whatever `analyze_file` captured —
|
|
244
|
+
# empty unless the runner was constructed with
|
|
245
|
+
# `record_dependencies: true`). The negative (`missing`) edges are
|
|
246
|
+
# NOT inverted here: they feed the structural tier (slice 3), which
|
|
247
|
+
# re-checks a consumer when a name it looked up and did not resolve
|
|
248
|
+
# later appears.
|
|
249
|
+
def file_dependents
|
|
250
|
+
Incremental.invert(@file_dependencies.transform_values(&:sources))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# ADR-46 slice 4 — per-symbol body fingerprints, computed from the
|
|
254
|
+
# project pre-pass def index. Returns a frozen hash of the form:
|
|
255
|
+
# { "path/to/file.rb" => { "ClassName#method" => sha256_hex, … }, … }
|
|
256
|
+
# Used by {Analysis::IncrementalSession} to detect which symbols in a
|
|
257
|
+
# changed file actually changed bodies, so only callers of those
|
|
258
|
+
# specific symbols are re-checked. Only meaningful after a run that
|
|
259
|
+
# populated `@project_discovered_def_nodes` (i.e. any full or subset
|
|
260
|
+
# analysis); returns an empty frozen hash before the first run.
|
|
261
|
+
def symbol_fingerprints
|
|
262
|
+
result = Hash.new { |h, k| h[k] = {} }
|
|
263
|
+
@project_discovered_def_sources.each do |class_name, methods|
|
|
264
|
+
methods.each do |method_sym, path_line|
|
|
265
|
+
path = path_line.split(":", 2).first
|
|
266
|
+
node = @project_discovered_def_nodes.dig(class_name, method_sym)
|
|
267
|
+
next unless node
|
|
268
|
+
|
|
269
|
+
result[path]["#{class_name}##{method_sym}"] =
|
|
270
|
+
Digest::SHA256.hexdigest(node.location.slice)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
result.transform_values(&:freeze).freeze
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# ADR-46 slice 3 — per-file set of the qualified class/module names
|
|
277
|
+
# declared in that file. Used to detect a class that *appeared* in an
|
|
278
|
+
# edit so a subclass whose ancestor was previously undefined (and so
|
|
279
|
+
# recorded a negative class edge) is re-checked. Inverts the project
|
|
280
|
+
# class-source attribution (class → declaring files).
|
|
281
|
+
def class_declarations
|
|
282
|
+
result = Hash.new { |hash, key| hash[key] = Set.new }
|
|
283
|
+
@project_discovered_class_sources.each do |class_name, files|
|
|
284
|
+
files.each { |file| result[file] << class_name }
|
|
285
|
+
end
|
|
286
|
+
result.transform_values(&:freeze).freeze
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# ADR-45 — unchanged-project fast path. Serves the whole run's
|
|
290
|
+
# (pre-severity-profile) diagnostics from one record-and-validate
|
|
291
|
+
# cache entry when every file the previous run read is unchanged,
|
|
292
|
+
# skipping the dominant per-file inference. The dependency set is
|
|
293
|
+
# collected AFTER the run (so it captures files the plugins read
|
|
294
|
+
# mid-analysis, e.g. a Pundit policy) and re-validated on the next
|
|
295
|
+
# run; the entry is keyed on the inputs known up front (config, gem
|
|
296
|
+
# / engine versions, analyzed-path set).
|
|
297
|
+
def compute_run_diagnostics(expansion)
|
|
298
|
+
@run_served_from_cache = false
|
|
299
|
+
return assemble_run_diagnostics(expansion) unless run_result_cacheable?
|
|
300
|
+
|
|
301
|
+
environment = resolve_sequential_environment(source_files: target_files(expansion))
|
|
302
|
+
rbs_descriptor = environment&.rbs_loader ? Cache::RbsDescriptor.build(environment.rbs_loader) : Cache::Descriptor.new
|
|
303
|
+
key_descriptor = run_key_descriptor(expansion, rbs_descriptor)
|
|
304
|
+
return assemble_run_diagnostics(expansion, environment: environment) if key_descriptor.nil?
|
|
305
|
+
|
|
306
|
+
computed = false
|
|
307
|
+
diagnostics = @cache_store.fetch_or_validate(
|
|
308
|
+
producer_id: "analysis.run-diagnostics", key_descriptor: key_descriptor
|
|
309
|
+
) do
|
|
310
|
+
computed = true
|
|
311
|
+
diags = assemble_run_diagnostics(expansion, environment: environment)
|
|
312
|
+
[diags, run_dependency_descriptor(expansion, rbs_descriptor)]
|
|
313
|
+
end
|
|
314
|
+
@run_served_from_cache = !computed
|
|
315
|
+
diagnostics
|
|
316
|
+
rescue StandardError
|
|
317
|
+
# The result cache must never break a run. If anything in the
|
|
318
|
+
# cache path fails, fall back to a direct, uncached analysis.
|
|
319
|
+
@run_served_from_cache = false
|
|
320
|
+
assemble_run_diagnostics(expansion)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def assemble_run_diagnostics(expansion, environment: nil)
|
|
154
324
|
diagnostics = pre_file_diagnostics(expansion)
|
|
155
|
-
|
|
325
|
+
# ADR-46 — record which project files this run actually analyzed
|
|
326
|
+
# (the `analyze_only` subset, or all of them). The incremental
|
|
327
|
+
# orchestrator serves every analyzed-but-not-affected file from the
|
|
328
|
+
# per-file cache, so it needs the full analyzed set to subtract the
|
|
329
|
+
# affected closure from.
|
|
330
|
+
targets = target_files(expansion)
|
|
331
|
+
@analyzed_files = targets
|
|
332
|
+
diagnostics += analyze_files(targets, environment: environment)
|
|
156
333
|
diagnostics += rbs_synthesized_namespace_diagnostics
|
|
334
|
+
diagnostics += conforms_to_diagnostics
|
|
157
335
|
diagnostics += rbs_extended_reporter_diagnostics
|
|
158
336
|
diagnostics += boundary_cross_diagnostics
|
|
159
|
-
diagnostics
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
337
|
+
diagnostics + source_rbs_synthesis_diagnostics
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# A cache hit skipped the analysis, so the per-run stats (wall
|
|
341
|
+
# split, RBS-class counts, …) were never gathered — report none
|
|
342
|
+
# rather than the stale snapshot defaults.
|
|
343
|
+
def stats_for_run(wall_started_at:, expansion:)
|
|
344
|
+
return nil unless @collect_stats
|
|
345
|
+
return nil if @run_served_from_cache
|
|
346
|
+
|
|
347
|
+
build_run_stats(wall_started_at: wall_started_at, expansion: expansion)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Cacheable only for a full sequential project run with a writable
|
|
351
|
+
# cache and no per-buffer / prebuilt override — every other mode has
|
|
352
|
+
# a different result identity (pool workers read in separate
|
|
353
|
+
# processes; editor mode is per-buffer; prebuilt is the LSP path).
|
|
354
|
+
def run_result_cacheable?
|
|
355
|
+
!@cache_store.nil? && !@cache_store.read_only? &&
|
|
356
|
+
@buffer.nil? && @prebuilt.nil? && !pool_mode?
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Stable cache key inputs — known before the run: a digest of the
|
|
360
|
+
# resolved configuration, the engine + rbs versions + `--explain`,
|
|
361
|
+
# and the analyzed-path SET (adding/removing a file changes the
|
|
362
|
+
# key; editing one is caught by dependency validation). nil disables
|
|
363
|
+
# the cache for this run rather than risking a malformed key.
|
|
364
|
+
def run_key_descriptor(expansion, rbs_descriptor)
|
|
365
|
+
Cache::Descriptor.new(
|
|
366
|
+
gems: rbs_descriptor.gems,
|
|
367
|
+
configs: rbs_descriptor.configs + [
|
|
368
|
+
config_hash_entry("configuration", Marshal.dump(@configuration.to_h)),
|
|
369
|
+
config_hash_entry("engine", "#{Rigor::VERSION}:#{Cache::Descriptor::SCHEMA_VERSION}:#{@explain}"),
|
|
370
|
+
config_hash_entry("paths", expansion.fetch(:files).sort.join("\n"))
|
|
371
|
+
]
|
|
164
372
|
)
|
|
373
|
+
rescue StandardError
|
|
374
|
+
nil
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Files the run actually depended on, collected AFTER it ran:
|
|
378
|
+
# every analyzed file, every RBS `sig` file (`rbs_descriptor.files`),
|
|
379
|
+
# and every file each plugin read (complete post-run, so reads made
|
|
380
|
+
# mid-analysis are included). Re-digested on the next run by
|
|
381
|
+
# {Descriptor#fresh?}.
|
|
382
|
+
def run_dependency_descriptor(expansion, rbs_descriptor)
|
|
383
|
+
entries = analyzed_file_entries(expansion) + rbs_descriptor.files
|
|
384
|
+
@plugin_registry.plugins.each do |plugin|
|
|
385
|
+
# Read the boundary WITHOUT triggering its lazy `@io_boundary ||=`
|
|
386
|
+
# initializer: plugin instances are frozen after the run, and a
|
|
387
|
+
# plugin that never built a boundary read no files through it, so
|
|
388
|
+
# it contributes no dependencies.
|
|
389
|
+
boundary = plugin.instance_variable_get(:@io_boundary)
|
|
390
|
+
entries.concat(boundary.cache_descriptor.files) if boundary
|
|
391
|
+
end
|
|
392
|
+
Cache::Descriptor.new(files: entries)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def analyzed_file_entries(expansion)
|
|
396
|
+
expansion.fetch(:files).map do |path|
|
|
397
|
+
physical = @buffer ? @buffer.resolve(path) : path
|
|
398
|
+
Cache::Descriptor::FileEntry.new(
|
|
399
|
+
path: physical, comparator: :digest, value: Digest::SHA256.file(physical).hexdigest
|
|
400
|
+
)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def config_hash_entry(key, payload)
|
|
405
|
+
Cache::Descriptor::ConfigEntry.new(key: key, value_hash: Digest::SHA256.hexdigest(payload))
|
|
165
406
|
end
|
|
166
407
|
|
|
167
408
|
# Runs every project-wide pre-pass (`load_plugins` +
|
|
@@ -204,7 +445,7 @@ module Rigor
|
|
|
204
445
|
# downstream `#run` body expects. Extracted so
|
|
205
446
|
# `#prepare_project_scan` and the prebuilt-less `#run` path
|
|
206
447
|
# share one implementation.
|
|
207
|
-
def run_project_pre_passes(expansion:)
|
|
448
|
+
def run_project_pre_passes(expansion:) # rubocop:disable Metrics/AbcSize
|
|
208
449
|
@plugin_registry = load_plugins
|
|
209
450
|
@dependency_source_index = DependencySourceInference::Builder.build(@configuration.dependencies)
|
|
210
451
|
# ADR-18 slice 3 — plugin prepare MUST run before the
|
|
@@ -266,8 +507,10 @@ module Rigor
|
|
|
266
507
|
@project_discovered_def_sources = def_index.fetch(:def_sources)
|
|
267
508
|
@project_discovered_superclasses = def_index.fetch(:superclasses)
|
|
268
509
|
@project_discovered_includes = def_index.fetch(:includes)
|
|
510
|
+
@project_discovered_class_sources = def_index.fetch(:class_sources)
|
|
269
511
|
@project_discovered_method_visibilities = def_index.fetch(:method_visibilities)
|
|
270
512
|
@project_discovered_methods = def_index.fetch(:methods)
|
|
513
|
+
@project_data_member_layouts = def_index.fetch(:data_member_layouts)
|
|
271
514
|
end
|
|
272
515
|
|
|
273
516
|
# Internal: adopts a frozen {ProjectScan} snapshot supplied
|
|
@@ -301,31 +544,36 @@ module Rigor
|
|
|
301
544
|
# method returns — holding it as long-lived state added
|
|
302
545
|
# memory pressure that surfaced as a Bus Error during the
|
|
303
546
|
# spec suite under Ruby 4.0 + rbs 4.0.2.
|
|
304
|
-
def analyze_files(files)
|
|
547
|
+
def analyze_files(files, environment: nil)
|
|
305
548
|
return [] if files.empty?
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
549
|
+
return dispatch_pool(files) if pool_mode?
|
|
550
|
+
|
|
551
|
+
analyze_files_sequentially(files, environment || resolve_sequential_environment(source_files: files))
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def analyze_files_sequentially(files, environment)
|
|
555
|
+
# Snapshot the small synthesized-namespace name list (NOT the
|
|
556
|
+
# env — see the method comment) so #run can surface the
|
|
557
|
+
# malformed-RBS `:info` diagnostic without rebuilding the env.
|
|
558
|
+
# Gated on the project actually declaring `signature_paths:`:
|
|
559
|
+
# synthesis only matters for the project's own RBS, and
|
|
560
|
+
# `#synthesized_namespaces` forces the (otherwise-lazy) RBS env
|
|
561
|
+
# to build — doing so when there is no project sig set would
|
|
562
|
+
# warm `.rigor/cache` on a bare `--no-stats` run.
|
|
563
|
+
@synthesized_namespaces_snapshot =
|
|
564
|
+
project_signature_paths? ? (environment.rbs_loader&.synthesized_namespaces || []) : []
|
|
565
|
+
# `rigor:v1:conforms-to` lives only in the project's own
|
|
566
|
+
# `signature_paths:` RBS, so gate the scan the same way and
|
|
567
|
+
# reuse the already-built env (no extra RBS load).
|
|
568
|
+
@conformance_results_snapshot =
|
|
569
|
+
project_signature_paths? ? RbsExtended::ConformanceChecker.scan(environment.rbs_loader) : []
|
|
570
|
+
result = files.flat_map { |path| analyze_file(path, environment) }
|
|
571
|
+
if @collect_stats
|
|
572
|
+
loader = environment.rbs_loader
|
|
573
|
+
@class_decl_paths_snapshot = loader&.class_decl_paths || {}.freeze
|
|
574
|
+
@signature_paths_snapshot = loader&.signature_paths || [].freeze
|
|
328
575
|
end
|
|
576
|
+
result
|
|
329
577
|
end
|
|
330
578
|
|
|
331
579
|
# Sequential-mode environment resolver. Returns the supplied
|
|
@@ -463,6 +711,11 @@ module Rigor
|
|
|
463
711
|
# buffer".
|
|
464
712
|
def target_files(expansion)
|
|
465
713
|
files = expansion.fetch(:files)
|
|
714
|
+
# ADR-46 slice 2 — restrict the analyzed set to the affected
|
|
715
|
+
# closure while the pre-pass (run separately over `expansion`'s
|
|
716
|
+
# full file list) keeps the cross-file index complete. Buffer mode
|
|
717
|
+
# takes precedence — its single logical path is the analyzed set.
|
|
718
|
+
files = files.select { |path| @analyze_only.include?(path) } if @analyze_only
|
|
466
719
|
return files if @buffer.nil?
|
|
467
720
|
|
|
468
721
|
[@buffer.logical_path]
|
|
@@ -1146,6 +1399,72 @@ module Rigor
|
|
|
1146
1399
|
[build_rbs_synthesized_namespace_diagnostic(synthesized)]
|
|
1147
1400
|
end
|
|
1148
1401
|
|
|
1402
|
+
# Maps the per-run `rigor:v1:conforms-to` scan results into
|
|
1403
|
+
# diagnostics (spec: `rbs-extended.md` § "Explicit conformance
|
|
1404
|
+
# directive"). A class that declares `conforms-to _Interface`
|
|
1405
|
+
# but is missing a required interface method surfaces as
|
|
1406
|
+
# `rbs_extended.unsatisfied-conformance`; an unresolvable
|
|
1407
|
+
# interface name surfaces as `dynamic.rbs-extended.unresolved`
|
|
1408
|
+
# `:info` (the same fail-soft channel the other directive
|
|
1409
|
+
# parsers use). Empty for a project with no directive, a
|
|
1410
|
+
# well-formed conformance, or a non-sequential pool run (the
|
|
1411
|
+
# snapshot mirrors `synthesized_namespaces`).
|
|
1412
|
+
def conforms_to_diagnostics
|
|
1413
|
+
results = @conformance_results_snapshot
|
|
1414
|
+
return [] if results.nil? || results.empty?
|
|
1415
|
+
|
|
1416
|
+
results.map { |record| build_conformance_diagnostic(record) }
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
def build_conformance_diagnostic(record)
|
|
1420
|
+
case record
|
|
1421
|
+
when RbsExtended::ConformanceChecker::Unsatisfied
|
|
1422
|
+
build_unsatisfied_conformance_diagnostic(record)
|
|
1423
|
+
when RbsExtended::ConformanceChecker::IncompatibleSignature
|
|
1424
|
+
build_incompatible_signature_diagnostic(record)
|
|
1425
|
+
else # UnresolvedInterface
|
|
1426
|
+
build_reporter_diagnostic(
|
|
1427
|
+
record.location,
|
|
1428
|
+
rule: "dynamic.rbs-extended.unresolved",
|
|
1429
|
+
message: "`#{record.class_name}` declares `conforms-to #{record.interface_name}` but " \
|
|
1430
|
+
"interface `#{record.interface_name}` is not loaded. Check for a typo or add " \
|
|
1431
|
+
"the `sig`/library that declares it to the RBS load path."
|
|
1432
|
+
)
|
|
1433
|
+
end
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
def build_unsatisfied_conformance_diagnostic(record)
|
|
1437
|
+
path, line, column = location_fields(record.location)
|
|
1438
|
+
Diagnostic.new(
|
|
1439
|
+
path: path, line: line, column: column,
|
|
1440
|
+
message: "`#{record.class_name}` declares `conforms-to #{record.interface_name}` " \
|
|
1441
|
+
"but does not provide #{pluralize_methods(record.missing_methods)}: " \
|
|
1442
|
+
"#{record.missing_methods.map { |m| "`##{m}`" }.join(', ')}. Implement the " \
|
|
1443
|
+
"missing method(s) or remove the directive.",
|
|
1444
|
+
severity: :warning,
|
|
1445
|
+
rule: "rbs_extended.unsatisfied-conformance",
|
|
1446
|
+
source_family: :builtin
|
|
1447
|
+
)
|
|
1448
|
+
end
|
|
1449
|
+
|
|
1450
|
+
def build_incompatible_signature_diagnostic(record)
|
|
1451
|
+
path, line, column = location_fields(record.location)
|
|
1452
|
+
Diagnostic.new(
|
|
1453
|
+
path: path, line: line, column: column,
|
|
1454
|
+
message: "`#{record.class_name}##{record.method_name}` does not satisfy " \
|
|
1455
|
+
"`conforms-to #{record.interface_name}`: #{record.detail}. Adjust the " \
|
|
1456
|
+
"signature to a subtype of the interface contract.",
|
|
1457
|
+
severity: :warning,
|
|
1458
|
+
rule: "rbs_extended.unsatisfied-conformance",
|
|
1459
|
+
source_family: :builtin,
|
|
1460
|
+
method_name: record.method_name
|
|
1461
|
+
)
|
|
1462
|
+
end
|
|
1463
|
+
|
|
1464
|
+
def pluralize_methods(methods)
|
|
1465
|
+
methods.size == 1 ? "required method" : "#{methods.size} required methods"
|
|
1466
|
+
end
|
|
1467
|
+
|
|
1149
1468
|
# True when the project declares its own `signature_paths:` (the
|
|
1150
1469
|
# only place the qualified-name-without-namespace mistake lives).
|
|
1151
1470
|
def project_signature_paths?
|
|
@@ -1455,7 +1774,8 @@ module Rigor
|
|
|
1455
1774
|
|
|
1456
1775
|
def accept_as_ruby_file?(path)
|
|
1457
1776
|
(File.file?(path) && path.end_with?(".rb")) ||
|
|
1458
|
-
(@buffer && path == @buffer.logical_path)
|
|
1777
|
+
(@buffer && path == @buffer.logical_path) ||
|
|
1778
|
+
@in_memory_sources&.key?(path)
|
|
1459
1779
|
end
|
|
1460
1780
|
|
|
1461
1781
|
# `Configuration#exclude_patterns` is a list of glob patterns
|
|
@@ -1494,6 +1814,10 @@ module Rigor
|
|
|
1494
1814
|
# LOGICAL path. Non-binding paths go through the cheaper
|
|
1495
1815
|
# `Prism.parse_file` codepath unchanged.
|
|
1496
1816
|
def parse_source(path)
|
|
1817
|
+
if @in_memory_sources&.key?(path)
|
|
1818
|
+
return Prism.parse(@in_memory_sources[path], filepath: path, version: @configuration.target_ruby)
|
|
1819
|
+
end
|
|
1820
|
+
|
|
1497
1821
|
physical = @buffer ? @buffer.resolve(path) : path
|
|
1498
1822
|
return Prism.parse_file(physical, version: @configuration.target_ruby) if physical == path
|
|
1499
1823
|
|
|
@@ -1522,10 +1846,34 @@ module Rigor
|
|
|
1522
1846
|
scope = scope.with_discovered_method_visibilities(@project_discovered_method_visibilities)
|
|
1523
1847
|
end
|
|
1524
1848
|
scope = scope.with_discovered_methods(@project_discovered_methods) unless @project_discovered_methods.empty?
|
|
1849
|
+
scope = scope.with_data_member_layouts(@project_data_member_layouts) unless @project_data_member_layouts.empty?
|
|
1850
|
+
# ADR-46 slice 1 — the class-declaration source map is read only by
|
|
1851
|
+
# the ancestry accessors during dependency recording, so seed it
|
|
1852
|
+
# only when recording is on; a normal run never carries it.
|
|
1853
|
+
if @record_dependencies && !@project_discovered_class_sources.empty?
|
|
1854
|
+
scope = scope.with_discovered_class_sources(@project_discovered_class_sources)
|
|
1855
|
+
end
|
|
1525
1856
|
scope
|
|
1526
1857
|
end
|
|
1527
1858
|
|
|
1528
|
-
|
|
1859
|
+
# ADR-46 slice 1 — when dependency recording is enabled, wrap the
|
|
1860
|
+
# per-file analysis so the cross-file reads its inference makes are
|
|
1861
|
+
# captured into `file_dependencies[path]`. Off by default: a normal
|
|
1862
|
+
# run calls the body directly and the instrumented `Scope` accessors
|
|
1863
|
+
# short-circuit on `DependencyRecorder.active? == false`. Recording
|
|
1864
|
+
# is observational, so diagnostics are byte-identical either way.
|
|
1865
|
+
def analyze_file(path, environment)
|
|
1866
|
+
return analyze_file_body(path, environment) unless @record_dependencies
|
|
1867
|
+
|
|
1868
|
+
diagnostics = nil
|
|
1869
|
+
record = DependencyRecorder.record_for(path) do
|
|
1870
|
+
diagnostics = analyze_file_body(path, environment)
|
|
1871
|
+
end
|
|
1872
|
+
@file_dependencies[path] = record
|
|
1873
|
+
diagnostics
|
|
1874
|
+
end
|
|
1875
|
+
|
|
1876
|
+
def analyze_file_body(path, environment) # rubocop:disable Metrics/MethodLength
|
|
1529
1877
|
parse_result = parse_source(path)
|
|
1530
1878
|
unless parse_result.errors.empty?
|
|
1531
1879
|
return [] if ErbTemplateDetector.template?(parse_result)
|
|
@@ -1534,11 +1882,20 @@ module Rigor
|
|
|
1534
1882
|
end
|
|
1535
1883
|
|
|
1536
1884
|
scope = seed_project_scope(Scope.empty(environment: environment, source_path: path))
|
|
1537
|
-
|
|
1885
|
+
# ADR-24 slice 4a/4 — record unresolved implicit-self calls during the
|
|
1886
|
+
# typing pass ONLY (not CheckRules, whose own `type_of` queries would
|
|
1887
|
+
# otherwise re-trigger the choke-point). `self_call_misses` feeds the
|
|
1888
|
+
# `call.self-undefined-method` collector; the recorder is inert unless
|
|
1889
|
+
# the rule is active or `record_self_calls:` opted in.
|
|
1890
|
+
index = nil
|
|
1891
|
+
self_call_record = with_self_call_recording(path) do
|
|
1892
|
+
index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
|
|
1893
|
+
end
|
|
1538
1894
|
diagnostics = CheckRules.diagnose(
|
|
1539
1895
|
path: path,
|
|
1540
1896
|
root: parse_result.value,
|
|
1541
1897
|
scope_index: index,
|
|
1898
|
+
self_call_misses: self_call_record ? self_call_record.calls : [],
|
|
1542
1899
|
comments: parse_result.comments,
|
|
1543
1900
|
disabled_rules: @configuration.disabled_rules
|
|
1544
1901
|
)
|
|
@@ -1566,6 +1923,46 @@ module Rigor
|
|
|
1566
1923
|
]
|
|
1567
1924
|
end
|
|
1568
1925
|
|
|
1926
|
+
# ADR-24 slice 4a — runs `block` (the typing pass) with the self-call
|
|
1927
|
+
# recorder active when either the test-only `record_self_calls:` flag is
|
|
1928
|
+
# set or the `call.self-undefined-method` rule resolves to a firing
|
|
1929
|
+
# severity. Returns the frozen {SelfCallResolutionRecorder::Record}, or
|
|
1930
|
+
# nil when recording is inactive (the common path — one integer read).
|
|
1931
|
+
def with_self_call_recording(path, &)
|
|
1932
|
+
unless self_call_recording_active?
|
|
1933
|
+
yield
|
|
1934
|
+
return nil
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1937
|
+
record = SelfCallResolutionRecorder.record_for(path, &)
|
|
1938
|
+
@unresolved_self_calls[path] = record
|
|
1939
|
+
record
|
|
1940
|
+
end
|
|
1941
|
+
|
|
1942
|
+
def self_call_recording_active?
|
|
1943
|
+
@record_self_calls || self_undefined_rule_active?
|
|
1944
|
+
end
|
|
1945
|
+
|
|
1946
|
+
# Memoised: the rule fires only when its resolved severity is not `:off`
|
|
1947
|
+
# and it is not in `disable:`. Default profiles map it to `:off`, so a
|
|
1948
|
+
# normal run never activates the recorder (pending the external WD4
|
|
1949
|
+
# corpus FP gate — see ADR-24 § "Slice 4"); a project opts in via
|
|
1950
|
+
# `severity_overrides:`.
|
|
1951
|
+
def self_undefined_rule_active?
|
|
1952
|
+
return @self_undefined_rule_active unless @self_undefined_rule_active.nil?
|
|
1953
|
+
|
|
1954
|
+
rule = CheckRules::RULE_SELF_UNDEFINED_METHOD
|
|
1955
|
+
@self_undefined_rule_active =
|
|
1956
|
+
if @configuration.disabled_rules.include?(rule) || @configuration.disabled_rules.include?("call")
|
|
1957
|
+
false
|
|
1958
|
+
else
|
|
1959
|
+
Configuration::SeverityProfile.resolve(
|
|
1960
|
+
rule: rule, authored_severity: :warning,
|
|
1961
|
+
profile: @configuration.severity_profile, overrides: @configuration.severity_overrides
|
|
1962
|
+
) != :off
|
|
1963
|
+
end
|
|
1964
|
+
end
|
|
1965
|
+
|
|
1569
1966
|
# v0.0.2 #10 — fail-soft fallback explanation. When
|
|
1570
1967
|
# `--explain` is set the runner additionally walks the
|
|
1571
1968
|
# file with `Rigor::Inference::CoverageScanner` and emits
|