rigortype 0.1.19 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +3 -21
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules.rb +492 -71
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +17 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +9 -8
- data/lib/rigor/analysis/runner.rb +17 -6
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +10 -14
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +28 -7
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +115 -16
- data/lib/rigor/cli/coverage_command.rb +148 -16
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration.rb +45 -7
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +49 -5
- data/lib/rigor/environment.rb +17 -7
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +20 -28
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +156 -21
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +90 -15
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +40 -48
- data/lib/rigor/inference/mutation_widening.rb +5 -11
- data/lib/rigor/inference/narrowing.rb +14 -16
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +129 -55
- data/lib/rigor/inference/statement_evaluator.rb +244 -114
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +10 -8
- data/lib/rigor/plugin/macro/block_as_method.rb +3 -4
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +4 -5
- data/lib/rigor/plugin/manifest.rb +45 -66
- data/lib/rigor/plugin/registry.rb +6 -7
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/mutation_scanner.rb +120 -0
- data/lib/rigor/protection/mutator.rb +246 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +14 -2
- data/lib/rigor/scope.rb +54 -11
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +16 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +3 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +7 -10
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +6 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +7 -9
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +3 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +19 -14
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +28 -41
- data/sig/rigor/scope.rbs +9 -1
- data/sig/rigor/type.rbs +36 -1
- metadata +19 -1
|
@@ -6,14 +6,11 @@ module Rigor
|
|
|
6
6
|
module Analysis
|
|
7
7
|
module DependencySourceInference
|
|
8
8
|
# Per-run collection of gem-source-inference state. Holds
|
|
9
|
-
# the resolved gems the walker
|
|
10
|
-
#
|
|
9
|
+
# the resolved gems the walker visits plus the unresolvable
|
|
10
|
+
# entries the runner surfaces as
|
|
11
11
|
# `dynamic.dependency-source.gem-not-found` diagnostics.
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# tier consults {#contribution_for} but the lookup always
|
|
15
|
-
# answers `nil` until slice 2b populates the method table
|
|
16
|
-
# by walking the resolved gems' `roots:`.
|
|
12
|
+
# The method table is fully populated by {Walker} at build
|
|
13
|
+
# time; {#contribution_for} returns live entries.
|
|
17
14
|
class Index
|
|
18
15
|
attr_reader :resolved_gems, :unresolvable, :method_catalog, :budget_exceeded,
|
|
19
16
|
:class_to_gem, :budget_overrun_strategy, :gem_modes
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "return_type_heuristic"
|
|
6
|
+
require_relative "../../source/constant_path"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Analysis
|
|
@@ -166,7 +167,7 @@ module Rigor
|
|
|
166
167
|
# children under the same prefix so any inner class
|
|
167
168
|
# definitions are still recorded under their own name.
|
|
168
169
|
def descend_class_or_module(node, qualified_prefix, in_singleton_class, accumulator, budget)
|
|
169
|
-
name =
|
|
170
|
+
name = Source::ConstantPath.qualified_name_or_nil(node.constant_path)
|
|
170
171
|
if name && node.body
|
|
171
172
|
walk_node(node.body, qualified_prefix + [name], in_singleton_class, accumulator, budget)
|
|
172
173
|
else
|
|
@@ -197,23 +198,6 @@ module Rigor
|
|
|
197
198
|
return_type = ReturnTypeHeuristic.extract(node)
|
|
198
199
|
accumulator[key] = CatalogEntry.new(kind: kind, return_type: return_type)
|
|
199
200
|
end
|
|
200
|
-
|
|
201
|
-
# Resolves a `Prism::ConstantPathNode` /
|
|
202
|
-
# `Prism::ConstantReadNode` chain to its dot-separated
|
|
203
|
-
# name (e.g. `"Foo::Bar"`). Returns nil for the rare
|
|
204
|
-
# dynamic-prefix shape (`module ::Foo`-rooted variants
|
|
205
|
-
# whose left side is a runtime expression) so the
|
|
206
|
-
# walker treats those as opaque rather than guessing.
|
|
207
|
-
def qualified_name_for(node)
|
|
208
|
-
case node
|
|
209
|
-
when Prism::ConstantReadNode then node.name.to_s
|
|
210
|
-
when Prism::ConstantPathNode
|
|
211
|
-
parent = node.parent.nil? ? nil : qualified_name_for(node.parent)
|
|
212
|
-
return nil if !node.parent.nil? && parent.nil?
|
|
213
|
-
|
|
214
|
-
parent.nil? ? node.name.to_s : "#{parent}::#{node.name}"
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
201
|
end
|
|
218
202
|
end
|
|
219
203
|
end
|
|
@@ -20,18 +20,9 @@ module Rigor
|
|
|
20
20
|
# - {Builder.build} folds a `Configuration::Dependencies`
|
|
21
21
|
# into a frozen {Index} carrying the partitioned outcomes.
|
|
22
22
|
# - {Index} holds the per-run state the dispatcher tier
|
|
23
|
-
# consults via `#contribution_for
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# Per the ADR's "Implementation slicing" section, slice 2 is
|
|
28
|
-
# split internally:
|
|
29
|
-
#
|
|
30
|
-
# - Slice 2a (this commit): gem resolution, index plumbing,
|
|
31
|
-
# `Analysis::Runner` wiring, `dynamic.dependency-source.gem-not-found`
|
|
32
|
-
# diagnostic for unresolvable entries.
|
|
33
|
-
# - Slice 2b (next commit): walker, dispatcher tier
|
|
34
|
-
# integration, `Type::Dynamic`-wrapped returns.
|
|
23
|
+
# consults via `#contribution_for`; the method table is
|
|
24
|
+
# fully populated by {Walker} walking each resolved gem's
|
|
25
|
+
# `roots:`.
|
|
35
26
|
module DependencySourceInference
|
|
36
27
|
end
|
|
37
28
|
end
|
|
@@ -4,10 +4,11 @@ module Rigor
|
|
|
4
4
|
module Analysis
|
|
5
5
|
# Immutable storage for flow-sensitive facts attached to a Scope snapshot.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# facts that mention a target,
|
|
10
|
-
# retaining only facts that both
|
|
7
|
+
# Six buckets (see BUCKETS): local_binding, captured_local,
|
|
8
|
+
# object_content, global_storage, dynamic_origin, relational.
|
|
9
|
+
# Callers can record facts, invalidate all facts that mention a target,
|
|
10
|
+
# and conservatively join two stores by retaining only facts that both
|
|
11
|
+
# incoming edges share.
|
|
11
12
|
class FactStore
|
|
12
13
|
BUCKETS = %i[
|
|
13
14
|
local_binding
|
|
@@ -29,19 +29,48 @@ module Rigor
|
|
|
29
29
|
# - `severity_by_profile` — Hash of `:lenient` / `:balanced`
|
|
30
30
|
# / `:strict` to the configured severity per profile, taken
|
|
31
31
|
# from `Configuration::SeverityProfile::PROFILES`.
|
|
32
|
+
# - `evidence_tier` — `:high` / `:medium` / `:low` (or `nil`
|
|
33
|
+
# for informational helpers), Rigor's own confidence that a
|
|
34
|
+
# firing is a true positive, derived from the rule's firing
|
|
35
|
+
# gates. `:high` rules fire only on a concrete, statically-
|
|
36
|
+
# known type with no metaprogramming escape (Rigor's false-
|
|
37
|
+
# positive discipline has already filtered the uncertain
|
|
38
|
+
# cases); `:medium` rules rest on flow / inference proofs
|
|
39
|
+
# that inherit a documented FP envelope (loop / mutation /
|
|
40
|
+
# RBS-strictness modelling gaps); `:low` rules are
|
|
41
|
+
# resolution- or coverage-gap signals where a firing often
|
|
42
|
+
# reflects missing context rather than a definite bug. The
|
|
43
|
+
# tier routes a consumer's attention (and lets a downstream
|
|
44
|
+
# classifier promote a `:high` firing without cross-tool
|
|
45
|
+
# corroboration); it never feeds severity — that stays the
|
|
46
|
+
# `severity_profile:` decision.
|
|
32
47
|
# - `since` — first version the rule shipped in.
|
|
33
48
|
module RuleCatalog # rubocop:disable Metrics/ModuleLength
|
|
49
|
+
# Stable documentation home for a built-in rule. `documentation_url`
|
|
50
|
+
# appends a per-rule fragment that resolves to the rule's anchor in
|
|
51
|
+
# the published diagnostics catalogue; the page itself points at
|
|
52
|
+
# `rigor explain <rule>` as the authoritative per-rule reference.
|
|
53
|
+
# Mirrors the gemspec `documentation_uri` URL scheme (`…/tree/main`).
|
|
54
|
+
DOCUMENTATION_BASE = "https://github.com/rigortype/rigor/blob/main/docs/manual/04-diagnostics.md"
|
|
55
|
+
|
|
34
56
|
class Entry < Data.define(:id, :summary, :fires_when, :does_not_fire_when,
|
|
35
|
-
:suppression, :severity_authored, :severity_by_profile,
|
|
57
|
+
:suppression, :severity_authored, :severity_by_profile,
|
|
58
|
+
:evidence_tier, :since)
|
|
36
59
|
def aliases
|
|
37
60
|
CheckRules::LEGACY_RULE_ALIASES.select { |_legacy, canonical| canonical == id }.keys
|
|
38
61
|
end
|
|
39
62
|
|
|
63
|
+
# Stable per-rule documentation URL (see {RuleCatalog.documentation_url}).
|
|
64
|
+
def documentation_url
|
|
65
|
+
RuleCatalog.documentation_url(id)
|
|
66
|
+
end
|
|
67
|
+
|
|
40
68
|
# Hash-shaped form for `--format=json` consumers. Keys are
|
|
41
69
|
# Strings so the payload is JSON-stable without a transform
|
|
42
|
-
# pass.
|
|
70
|
+
# pass. `evidence_tier` is omitted when nil (informational
|
|
71
|
+
# helpers carry no confidence tier).
|
|
43
72
|
def to_h
|
|
44
|
-
{
|
|
73
|
+
base = {
|
|
45
74
|
"id" => id,
|
|
46
75
|
"aliases" => aliases,
|
|
47
76
|
"summary" => summary,
|
|
@@ -50,8 +79,11 @@ module Rigor
|
|
|
50
79
|
"suppression" => suppression,
|
|
51
80
|
"severity_authored" => severity_authored.to_s,
|
|
52
81
|
"severity_by_profile" => severity_by_profile.transform_keys(&:to_s).transform_values(&:to_s),
|
|
82
|
+
"documentation_url" => documentation_url,
|
|
53
83
|
"since" => since
|
|
54
84
|
}
|
|
85
|
+
base["evidence_tier"] = evidence_tier.to_s if evidence_tier
|
|
86
|
+
base
|
|
55
87
|
end
|
|
56
88
|
end
|
|
57
89
|
|
|
@@ -75,6 +107,7 @@ module Rigor
|
|
|
75
107
|
"or `disable: [\"call.undefined-method\"]` in `.rigor.yml`.",
|
|
76
108
|
severity_authored: :error,
|
|
77
109
|
severity_by_profile: { lenient: :error, balanced: :error, strict: :error },
|
|
110
|
+
evidence_tier: :high,
|
|
78
111
|
since: "0.0.1"
|
|
79
112
|
),
|
|
80
113
|
|
|
@@ -98,9 +131,43 @@ module Rigor
|
|
|
98
131
|
"`severity_overrides: { call.self-undefined-method: warning }` in `.rigor.yml`.",
|
|
99
132
|
severity_authored: :warning,
|
|
100
133
|
severity_by_profile: { lenient: :off, balanced: :off, strict: :off },
|
|
134
|
+
# Off by default and metaprogramming-prone — a firing on a
|
|
135
|
+
# class whose real surface the per-class scan cannot enumerate
|
|
136
|
+
# (C-extension, `class << self`, dynamic accessors) is the
|
|
137
|
+
# known FP mode, so a firing is a candidate to review, not a
|
|
138
|
+
# high-confidence bug.
|
|
139
|
+
evidence_tier: :low,
|
|
101
140
|
since: "0.1.17"
|
|
102
141
|
),
|
|
103
142
|
|
|
143
|
+
CheckRules::RULE_UNRESOLVED_TOPLEVEL => Entry.new(
|
|
144
|
+
id: CheckRules::RULE_UNRESOLVED_TOPLEVEL,
|
|
145
|
+
summary: "Top-level implicit-self call resolves against no def, pre_eval: patch, or Kernel method.",
|
|
146
|
+
fires_when: [
|
|
147
|
+
"The call is an implicit-self call (no receiver) at top level (outside any class / module body).",
|
|
148
|
+
"Its name resolves against no same-file top-level `def`.",
|
|
149
|
+
"No ADR-17 `pre_eval:` monkey-patch on `Object` / `Kernel` declares it.",
|
|
150
|
+
"It is not a standard `Kernel` / `Object` private method (`puts`, `require`, `loop`, …)."
|
|
151
|
+
],
|
|
152
|
+
does_not_fire_when: [
|
|
153
|
+
"The call has an explicit receiver, or sits inside a `def` / `class` / `module` body (ADR-24 WD3 " \
|
|
154
|
+
"stays lenient there).",
|
|
155
|
+
"A project file defines the name via a top-level `def` or an Object/Kernel monkey-patch listed in " \
|
|
156
|
+
"`.rigor.yml`'s `pre_eval:` (ADR-17).",
|
|
157
|
+
"The name is a Kernel/Object method visible in the loaded RBS environment."
|
|
158
|
+
],
|
|
159
|
+
suppression: "`# rigor:disable call.unresolved-toplevel` on the call line, or list the defining " \
|
|
160
|
+
"file in `.rigor.yml`'s `pre_eval:` so the analyzer sees the top-level `def` / patch.",
|
|
161
|
+
severity_authored: :warning,
|
|
162
|
+
severity_by_profile: { lenient: :off, balanced: :warning, strict: :error },
|
|
163
|
+
# A firing is frequently a resolution gap — the defining file
|
|
164
|
+
# is not in the analyzed set or injects the method via a
|
|
165
|
+
# metaprogramming patch the analyzer does not see — rather than
|
|
166
|
+
# a definite typo, so it routes to the `pre_eval:` review path.
|
|
167
|
+
evidence_tier: :low,
|
|
168
|
+
since: "0.1.14"
|
|
169
|
+
),
|
|
170
|
+
|
|
104
171
|
CheckRules::RULE_WRONG_ARITY => Entry.new(
|
|
105
172
|
id: CheckRules::RULE_WRONG_ARITY,
|
|
106
173
|
summary: "Call's positional argument count is outside the declared overloads' envelope.",
|
|
@@ -117,6 +184,7 @@ module Rigor
|
|
|
117
184
|
suppression: "`# rigor:disable call.wrong-arity`.",
|
|
118
185
|
severity_authored: :error,
|
|
119
186
|
severity_by_profile: { lenient: :error, balanced: :error, strict: :error },
|
|
187
|
+
evidence_tier: :high,
|
|
120
188
|
since: "0.0.1"
|
|
121
189
|
),
|
|
122
190
|
|
|
@@ -125,17 +193,22 @@ module Rigor
|
|
|
125
193
|
summary: "Call passes an argument whose type the parameter cannot accept.",
|
|
126
194
|
fires_when: [
|
|
127
195
|
"The parameter type rejects the argument under `accepts(arg, mode: :gradual)`.",
|
|
128
|
-
"
|
|
196
|
+
"Single-overload: no overload accepts the arg class (ADR-64 non-nil channel).",
|
|
197
|
+
"Multi-overload: every overload rejects a pure-`nil` arg (ADR-64 nil channel) " \
|
|
198
|
+
"or every overload rejects a single concrete non-nil arg class (non-nil channel).",
|
|
129
199
|
"Both sides have a non-Dynamic concrete type."
|
|
130
200
|
],
|
|
131
201
|
does_not_fire_when: [
|
|
132
202
|
"Either the parameter or the argument is `Dynamic[T]`.",
|
|
133
|
-
"
|
|
134
|
-
"
|
|
203
|
+
"The call is a coerce-dispatch operator (`+`, `-`, `*`, `/`, `<`, `>`, …) — " \
|
|
204
|
+
"excluded because the `coerce` protocol makes acceptance undecidable.",
|
|
205
|
+
"Method has `*rest_positionals`, required keywords, or trailing positionals.",
|
|
206
|
+
"The argument type is a union (not a single concrete class)."
|
|
135
207
|
],
|
|
136
208
|
suppression: "`# rigor:disable call.argument-type-mismatch`.",
|
|
137
209
|
severity_authored: :error,
|
|
138
210
|
severity_by_profile: { lenient: :warning, balanced: :error, strict: :error },
|
|
211
|
+
evidence_tier: :high,
|
|
139
212
|
since: "0.0.2"
|
|
140
213
|
),
|
|
141
214
|
|
|
@@ -155,6 +228,7 @@ module Rigor
|
|
|
155
228
|
suppression: "`# rigor:disable call.possible-nil-receiver`.",
|
|
156
229
|
severity_authored: :error,
|
|
157
230
|
severity_by_profile: { lenient: :warning, balanced: :error, strict: :error },
|
|
231
|
+
evidence_tier: :high,
|
|
158
232
|
since: "0.0.2"
|
|
159
233
|
),
|
|
160
234
|
|
|
@@ -171,6 +245,9 @@ module Rigor
|
|
|
171
245
|
suppression: "Remove the `dump_type` call (it's a debug helper, not a real diagnostic).",
|
|
172
246
|
severity_authored: :info,
|
|
173
247
|
severity_by_profile: { lenient: :info, balanced: :info, strict: :error },
|
|
248
|
+
# Informational helper, not a correctness finding — no
|
|
249
|
+
# confidence tier applies.
|
|
250
|
+
evidence_tier: nil,
|
|
174
251
|
since: "0.0.1"
|
|
175
252
|
),
|
|
176
253
|
|
|
@@ -187,6 +264,7 @@ module Rigor
|
|
|
187
264
|
suppression: "Update the assertion to the actual inferred type, or correct the source.",
|
|
188
265
|
severity_authored: :error,
|
|
189
266
|
severity_by_profile: { lenient: :error, balanced: :error, strict: :error },
|
|
267
|
+
evidence_tier: :high,
|
|
190
268
|
since: "0.0.1"
|
|
191
269
|
),
|
|
192
270
|
|
|
@@ -205,6 +283,7 @@ module Rigor
|
|
|
205
283
|
suppression: "`# rigor:disable flow.always-raises`.",
|
|
206
284
|
severity_authored: :error,
|
|
207
285
|
severity_by_profile: { lenient: :warning, balanced: :error, strict: :error },
|
|
286
|
+
evidence_tier: :high,
|
|
208
287
|
since: "0.0.3"
|
|
209
288
|
),
|
|
210
289
|
|
|
@@ -224,6 +303,9 @@ module Rigor
|
|
|
224
303
|
"points at the dead branch, not the predicate, so the suppression goes there).",
|
|
225
304
|
severity_authored: :warning,
|
|
226
305
|
severity_by_profile: { lenient: :info, balanced: :warning, strict: :error },
|
|
306
|
+
# The literal-only firing envelope makes the deadness provable
|
|
307
|
+
# from syntax alone — no inference uncertainty.
|
|
308
|
+
evidence_tier: :high,
|
|
227
309
|
since: "0.1.2"
|
|
228
310
|
),
|
|
229
311
|
|
|
@@ -246,6 +328,11 @@ module Rigor
|
|
|
246
328
|
suppression: "`# rigor:disable always-truthy-condition` on the predicate line.",
|
|
247
329
|
severity_authored: :warning,
|
|
248
330
|
severity_by_profile: { lenient: :info, balanced: :warning, strict: :error },
|
|
331
|
+
# Rests on inferred-constant folding, which inherits the
|
|
332
|
+
# loop / mutation FP envelope the `does_not_fire_when` guards
|
|
333
|
+
# narrow — true positive in the common case, but not literal-
|
|
334
|
+
# provable like `unreachable-branch`.
|
|
335
|
+
evidence_tier: :medium,
|
|
249
336
|
since: "0.1.2"
|
|
250
337
|
),
|
|
251
338
|
|
|
@@ -271,6 +358,9 @@ module Rigor
|
|
|
271
358
|
# ADR-47 WD4: balanced stays :info (one notch below its `flow.*`
|
|
272
359
|
# siblings' :warning) until the regression-corpus FP gate is green.
|
|
273
360
|
severity_by_profile: { lenient: :info, balanced: :info, strict: :warning },
|
|
361
|
+
# Narrowing-driven proof that inherits the `always-truthy`
|
|
362
|
+
# FP envelope; balanced keeps it `:info` pending the corpus gate.
|
|
363
|
+
evidence_tier: :medium,
|
|
274
364
|
since: "0.1.17"
|
|
275
365
|
),
|
|
276
366
|
|
|
@@ -292,6 +382,11 @@ module Rigor
|
|
|
292
382
|
suppression: "`# rigor:disable dead-assignment` on the offending line, or rename the local to `_<name>`.",
|
|
293
383
|
severity_authored: :warning,
|
|
294
384
|
severity_by_profile: { lenient: :info, balanced: :warning, strict: :error },
|
|
385
|
+
# The unread-write proof is reliable, but it flags a code
|
|
386
|
+
# smell rather than a runtime fault, and the syntactic write
|
|
387
|
+
# classification has narrow corners (the `does_not_fire_when`
|
|
388
|
+
# exclusions).
|
|
389
|
+
evidence_tier: :medium,
|
|
295
390
|
since: "0.1.2"
|
|
296
391
|
),
|
|
297
392
|
|
|
@@ -313,6 +408,10 @@ module Rigor
|
|
|
313
408
|
suppression: "`# rigor:disable def.return-type-mismatch`.",
|
|
314
409
|
severity_authored: :warning,
|
|
315
410
|
severity_by_profile: { lenient: :warning, balanced: :warning, strict: :error },
|
|
411
|
+
# Depends on re-typing the body against an authored RBS return;
|
|
412
|
+
# RBS strict-on-returns plus incomplete body inference makes a
|
|
413
|
+
# firing usually-right but not concrete-call certain.
|
|
414
|
+
evidence_tier: :medium,
|
|
316
415
|
since: "0.1.0"
|
|
317
416
|
),
|
|
318
417
|
|
|
@@ -333,6 +432,7 @@ module Rigor
|
|
|
333
432
|
suppression: "`# rigor:disable method-visibility-mismatch`.",
|
|
334
433
|
severity_authored: :error,
|
|
335
434
|
severity_by_profile: { lenient: :warning, balanced: :error, strict: :error },
|
|
435
|
+
evidence_tier: :high,
|
|
336
436
|
since: "0.1.2"
|
|
337
437
|
),
|
|
338
438
|
|
|
@@ -356,6 +456,10 @@ module Rigor
|
|
|
356
456
|
suppression: "`# rigor:disable def.override-visibility-reduced` on the override.",
|
|
357
457
|
severity_authored: :warning,
|
|
358
458
|
severity_by_profile: { lenient: :off, balanced: :warning, strict: :error },
|
|
459
|
+
# Both the override and the shadowed ancestor visibility are
|
|
460
|
+
# statically observed from project source — the substitutability
|
|
461
|
+
# violation is concrete.
|
|
462
|
+
evidence_tier: :high,
|
|
359
463
|
since: "0.1.15"
|
|
360
464
|
),
|
|
361
465
|
|
|
@@ -383,6 +487,9 @@ module Rigor
|
|
|
383
487
|
suppression: "`# rigor:disable def.override-return-widened` on the override.",
|
|
384
488
|
severity_authored: :warning,
|
|
385
489
|
severity_by_profile: { lenient: :off, balanced: :warning, strict: :error },
|
|
490
|
+
# Gated on both-sides-authored RBS and a resolvable subtype
|
|
491
|
+
# relationship, so a firing is a concrete covariance violation.
|
|
492
|
+
evidence_tier: :high,
|
|
386
493
|
since: "0.1.15"
|
|
387
494
|
),
|
|
388
495
|
|
|
@@ -413,6 +520,9 @@ module Rigor
|
|
|
413
520
|
suppression: "`# rigor:disable def.override-param-narrowed` on the override.",
|
|
414
521
|
severity_authored: :warning,
|
|
415
522
|
severity_by_profile: { lenient: :off, balanced: :warning, strict: :error },
|
|
523
|
+
# Gated on both-sides-authored RBS and a resolvable subtype
|
|
524
|
+
# relationship, so a firing is a concrete contravariance violation.
|
|
525
|
+
evidence_tier: :high,
|
|
416
526
|
since: "0.1.15"
|
|
417
527
|
),
|
|
418
528
|
|
|
@@ -434,6 +544,9 @@ module Rigor
|
|
|
434
544
|
suppression: "`# rigor:disable ivar-write-mismatch` on the offending write.",
|
|
435
545
|
severity_authored: :error,
|
|
436
546
|
severity_by_profile: { lenient: :warning, balanced: :warning, strict: :error },
|
|
547
|
+
# Both writes resolve to concrete classes before firing; the
|
|
548
|
+
# union / Dynamic / clear-idiom escapes are excluded.
|
|
549
|
+
evidence_tier: :high,
|
|
437
550
|
since: "0.1.2"
|
|
438
551
|
)
|
|
439
552
|
}.freeze
|
|
@@ -466,6 +579,40 @@ module Rigor
|
|
|
466
579
|
def all
|
|
467
580
|
ENTRIES.values.sort_by(&:id)
|
|
468
581
|
end
|
|
582
|
+
|
|
583
|
+
# Rigor's confidence tier (`:high` / `:medium` / `:low`) that a
|
|
584
|
+
# firing of `token` is a true positive, or nil for an
|
|
585
|
+
# informational rule (`dump.type`) or an unknown / non-built-in
|
|
586
|
+
# token (plugin and `rbs_extended.*` rules carry no built-in
|
|
587
|
+
# tier). Resolves legacy aliases. See {Entry}'s `evidence_tier`
|
|
588
|
+
# documentation for the tier semantics.
|
|
589
|
+
def evidence_tier(token)
|
|
590
|
+
entries = resolve(token)
|
|
591
|
+
return nil unless entries.size == 1
|
|
592
|
+
|
|
593
|
+
entries.first.evidence_tier
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Stable documentation URL for `token`, or nil for an unknown /
|
|
597
|
+
# non-built-in token. The URL is the published diagnostics
|
|
598
|
+
# catalogue page anchored at the rule's per-rule anchor
|
|
599
|
+
# (`#rule-<id-with-dots-as-dashes>`); the page itself names
|
|
600
|
+
# `rigor explain <rule>` as the authoritative per-rule reference.
|
|
601
|
+
# Resolves legacy aliases to the canonical id.
|
|
602
|
+
def documentation_url(token)
|
|
603
|
+
entries = resolve(token)
|
|
604
|
+
return nil unless entries.size == 1
|
|
605
|
+
|
|
606
|
+
"#{DOCUMENTATION_BASE}##{doc_anchor(entries.first.id)}"
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# The per-rule fragment a `documentation_url` points at:
|
|
610
|
+
# `call.undefined-method` → `rule-call-undefined-method`. The
|
|
611
|
+
# `04-diagnostics.md` catalogue carries the matching `<a id>`
|
|
612
|
+
# anchors.
|
|
613
|
+
def doc_anchor(rule_id)
|
|
614
|
+
"rule-#{rule_id.tr('.', '-')}"
|
|
615
|
+
end
|
|
469
616
|
end
|
|
470
617
|
end
|
|
471
618
|
end
|
|
@@ -423,23 +423,6 @@ module Rigor
|
|
|
423
423
|
unresolved + lossy
|
|
424
424
|
end
|
|
425
425
|
|
|
426
|
-
# ADR-10 slice 5c — drains the per-run
|
|
427
|
-
# {DependencySourceInference::BoundaryCrossReporter} into
|
|
428
|
-
# `dynamic.dependency-source.boundary-cross` `:info`
|
|
429
|
-
# diagnostics. Each event flags a call site where RBS
|
|
430
|
-
# dispatch produced a concrete answer AND a `mode: :full`
|
|
431
|
-
# opt-in gem's source catalog ALSO contains an entry for
|
|
432
|
-
# the same `(class_name, method_name)` — i.e., both
|
|
433
|
-
# contracts have an opinion. RBS still wins on the
|
|
434
|
-
# dispatch result; the diagnostic is purely advisory so
|
|
435
|
-
# the user can verify the two contracts haven't drifted.
|
|
436
|
-
#
|
|
437
|
-
# Severity profile re-stamps the rule per project taste.
|
|
438
|
-
# The diagnostic carries no `path` / `line` / `column`
|
|
439
|
-
# because the crossing is per-method-per-gem, not
|
|
440
|
-
# per-call-site — the diagnostic anchors at `.rigor.yml`
|
|
441
|
-
# like the other `dependency-source.*` diagnostics that
|
|
442
|
-
# report on opt-in configuration.
|
|
443
426
|
# ADR-32 WD6 — drains the per-run
|
|
444
427
|
# {Plugin::SourceRbsSynthesisReporter} into
|
|
445
428
|
# `source-rbs-synthesis-failed` `:info` diagnostics. Each
|
|
@@ -469,6 +452,23 @@ module Rigor
|
|
|
469
452
|
end
|
|
470
453
|
end
|
|
471
454
|
|
|
455
|
+
# ADR-10 slice 5c — drains the per-run
|
|
456
|
+
# {DependencySourceInference::BoundaryCrossReporter} into
|
|
457
|
+
# `dynamic.dependency-source.boundary-cross` `:info`
|
|
458
|
+
# diagnostics. Each event flags a call site where RBS
|
|
459
|
+
# dispatch produced a concrete answer AND a `mode: :full`
|
|
460
|
+
# opt-in gem's source catalog ALSO contains an entry for
|
|
461
|
+
# the same `(class_name, method_name)` — i.e., both
|
|
462
|
+
# contracts have an opinion. RBS still wins on the
|
|
463
|
+
# dispatch result; the diagnostic is purely advisory so
|
|
464
|
+
# the user can verify the two contracts haven't drifted.
|
|
465
|
+
#
|
|
466
|
+
# Severity profile re-stamps the rule per project taste.
|
|
467
|
+
# The diagnostic carries no `path` / `line` / `column`
|
|
468
|
+
# because the crossing is per-method-per-gem, not
|
|
469
|
+
# per-call-site — the diagnostic anchors at `.rigor.yml`
|
|
470
|
+
# like the other `dependency-source.*` diagnostics that
|
|
471
|
+
# report on opt-in configuration.
|
|
472
472
|
def boundary_cross_diagnostics
|
|
473
473
|
return [] if @boundary_cross_reporter.empty?
|
|
474
474
|
|
|
@@ -45,7 +45,8 @@ module Rigor
|
|
|
45
45
|
:discovered_class_sources,
|
|
46
46
|
:discovered_method_visibilities,
|
|
47
47
|
:discovered_methods,
|
|
48
|
-
:data_member_layouts
|
|
48
|
+
:data_member_layouts,
|
|
49
|
+
:struct_member_layouts
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
# @param configuration [Rigor::Configuration]
|
|
@@ -139,7 +140,8 @@ module Rigor
|
|
|
139
140
|
discovered_class_sources: def_index.fetch(:class_sources),
|
|
140
141
|
discovered_method_visibilities: def_index.fetch(:method_visibilities),
|
|
141
142
|
discovered_methods: def_index.fetch(:methods),
|
|
142
|
-
data_member_layouts: def_index.fetch(:data_member_layouts)
|
|
143
|
+
data_member_layouts: def_index.fetch(:data_member_layouts),
|
|
144
|
+
struct_member_layouts: def_index.fetch(:struct_member_layouts)
|
|
143
145
|
)
|
|
144
146
|
end
|
|
145
147
|
|
|
@@ -182,7 +184,8 @@ module Rigor
|
|
|
182
184
|
discovered_class_sources: nil,
|
|
183
185
|
discovered_method_visibilities: nil,
|
|
184
186
|
discovered_methods: nil,
|
|
185
|
-
data_member_layouts: nil
|
|
187
|
+
data_member_layouts: nil,
|
|
188
|
+
struct_member_layouts: nil
|
|
186
189
|
)
|
|
187
190
|
end
|
|
188
191
|
|
|
@@ -278,11 +281,9 @@ module Rigor
|
|
|
278
281
|
# `#diagnostics_for_file` raise envelope in
|
|
279
282
|
# `plugin_runtime_error_diagnostic`.
|
|
280
283
|
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
# `Configuration#plugins` order MUST be producer-first if
|
|
285
|
-
# cross-plugin dependencies exist.
|
|
284
|
+
# `Plugin::Loader` returns plugins in topological order by
|
|
285
|
+
# `manifest(consumes:)` (ADR-9 slice 5), so producers
|
|
286
|
+
# always run before consumers.
|
|
286
287
|
def plugin_prepare_diagnostics(plugin_registry)
|
|
287
288
|
return [] if plugin_registry.empty?
|
|
288
289
|
|
|
@@ -58,11 +58,10 @@ module Rigor
|
|
|
58
58
|
# slices route real producers through it.
|
|
59
59
|
# @param workers [Integer] ADR-15 Phase 4b — when greater
|
|
60
60
|
# than zero, per-file analysis dispatches across a pool of
|
|
61
|
-
# N
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# leaves the parameter as a programmatic opt-in only.
|
|
61
|
+
# N workers. Default `0` keeps the sequential code path
|
|
62
|
+
# bit-for-bit unchanged. Controlled via the
|
|
63
|
+
# `RIGOR_RACTOR_WORKERS` env var or `.rigor.yml`
|
|
64
|
+
# `parallel.workers:` (Phase 4c, fully wired).
|
|
66
65
|
# @param collect_stats [Boolean] when true (default), `#run`
|
|
67
66
|
# builds a {RunStats} summary exposed via `result.stats`
|
|
68
67
|
# — this forces the RBS env build at end-of-run so the
|
|
@@ -163,6 +162,7 @@ module Rigor
|
|
|
163
162
|
@project_discovered_method_visibilities = {}.freeze
|
|
164
163
|
@project_discovered_methods = {}.freeze
|
|
165
164
|
@project_data_member_layouts = {}.freeze
|
|
165
|
+
@project_struct_member_layouts = {}.freeze
|
|
166
166
|
build_collaborators
|
|
167
167
|
end
|
|
168
168
|
|
|
@@ -479,6 +479,7 @@ module Rigor
|
|
|
479
479
|
end
|
|
480
480
|
@project_discovered_methods = result.discovered_methods if result.discovered_methods
|
|
481
481
|
@project_data_member_layouts = result.data_member_layouts if result.data_member_layouts
|
|
482
|
+
@project_struct_member_layouts = result.struct_member_layouts if result.struct_member_layouts
|
|
482
483
|
end
|
|
483
484
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result
|
|
484
485
|
|
|
@@ -837,7 +838,7 @@ module Rigor
|
|
|
837
838
|
tables[:discovered_method_visibilities] = @project_discovered_method_visibilities
|
|
838
839
|
end
|
|
839
840
|
tables[:discovered_methods] = @project_discovered_methods unless @project_discovered_methods.empty?
|
|
840
|
-
tables
|
|
841
|
+
seed_member_layout_tables(tables)
|
|
841
842
|
# ADR-46 slice 1 — the class-declaration source map is read only by
|
|
842
843
|
# the ancestry accessors during dependency recording, so seed it
|
|
843
844
|
# only when recording is on; a normal run never carries it.
|
|
@@ -847,6 +848,16 @@ module Rigor
|
|
|
847
848
|
tables
|
|
848
849
|
end
|
|
849
850
|
|
|
851
|
+
# ADR-48 — seed the Data + Struct member-layout tables (each only when
|
|
852
|
+
# non-empty). Extracted to keep {#project_scope_seed_tables} under the
|
|
853
|
+
# complexity budget.
|
|
854
|
+
def seed_member_layout_tables(tables)
|
|
855
|
+
tables[:data_member_layouts] = @project_data_member_layouts unless @project_data_member_layouts.empty?
|
|
856
|
+
return if @project_struct_member_layouts.empty?
|
|
857
|
+
|
|
858
|
+
tables[:struct_member_layouts] = @project_struct_member_layouts
|
|
859
|
+
end
|
|
860
|
+
|
|
850
861
|
# ADR-46 slice 1 — when dependency recording is enabled, wrap the
|
|
851
862
|
# per-file analysis so the cross-file reads its inference makes are
|
|
852
863
|
# captured into `file_dependencies[path]`. Off by default: a normal
|
|
@@ -21,10 +21,9 @@ module Rigor
|
|
|
21
21
|
# reach the recorder. This module is the ADR-46 / ADR-47 "collect at
|
|
22
22
|
# evaluation time, never recompute" lesson applied to self-calls.
|
|
23
23
|
#
|
|
24
|
-
#
|
|
25
|
-
# closed-class gate
|
|
26
|
-
#
|
|
27
|
-
# plumbing OFF by default — {active?} is false on a normal run, so the
|
|
24
|
+
# ADR-24 slice 4 (`call.self-undefined-method`) consumes the recorded
|
|
25
|
+
# misses behind a confidently-closed-class gate (see `CheckRules` L775).
|
|
26
|
+
# The rule ships `:off` by default — {active?} is false on a normal run, so the
|
|
28
27
|
# instrumented choke-point pays a single integer read and records
|
|
29
28
|
# nothing. Recording is purely observational; it never changes a
|
|
30
29
|
# diagnostic.
|
|
@@ -21,21 +21,19 @@ module Rigor
|
|
|
21
21
|
module Analysis
|
|
22
22
|
# ADR-15 Phase 4a — per-worker analysis substrate.
|
|
23
23
|
# [ADR-15](../../../docs/adr/15-ractor-concurrency.md)
|
|
24
|
-
# § Phase 4 carves the
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# the absence of any Ractor coordination.
|
|
24
|
+
# § Phase 4 carves the Ractor-isolated worker pool into sub-phases;
|
|
25
|
+
# 4a/4b/4c all landed, but the Ractor pool (4b) is blocked by Ruby
|
|
26
|
+
# Bug #22075 (UAF) — the active pool backend is fork (ADR-15 Amendment).
|
|
27
|
+
# This class exists so the per-worker ownership boundary is testable
|
|
28
|
+
# independently of any pool coordinator.
|
|
30
29
|
#
|
|
31
30
|
# The constructor takes only `Ractor.shareable?` inputs:
|
|
32
31
|
#
|
|
33
32
|
# - `configuration` — Phase 2a ({Rigor::Configuration} is
|
|
34
33
|
# `Ractor.shareable?`).
|
|
35
|
-
# - `cache_store` —
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# for the no-Ractor coordinator path.
|
|
34
|
+
# - `cache_store` — the fork backend passes the parent runner's
|
|
35
|
+
# pre-built Store (`cache_store: @cache_store` in PoolCoordinator);
|
|
36
|
+
# workers share it rather than building their own at `cache_root`.
|
|
39
37
|
# - `plugin_blueprints` — Phase 3a
|
|
40
38
|
# (`Array<Plugin::Blueprint>` is `Ractor.shareable?`).
|
|
41
39
|
# - `explain` — Boolean.
|
|
@@ -234,11 +232,9 @@ module Rigor
|
|
|
234
232
|
Prism.parse(File.read(physical), filepath: path, version: @configuration.target_ruby)
|
|
235
233
|
end
|
|
236
234
|
|
|
237
|
-
# Mirrors {Runner#build_trust_policy}.
|
|
238
|
-
# 4b will need the same trust derivation, and the
|
|
239
|
-
# configuration is already shareable, so deriving it inside
|
|
235
|
+
# Mirrors {Runner#build_trust_policy}. Deriving trust inside
|
|
240
236
|
# the session keeps the substrate decoupled from the
|
|
241
|
-
# coordinator
|
|
237
|
+
# coordinator; configuration is already Ractor-shareable.
|
|
242
238
|
def build_trust_policy
|
|
243
239
|
trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
|
|
244
240
|
roots = [Dir.pwd]
|