rigortype 0.3.3 → 0.3.4
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 +1 -1
- data/data/builtins/ruby_core/array.yml +11 -24
- data/data/builtins/ruby_core/enumerable.yml +4 -8
- data/data/builtins/ruby_core/exception.yml +6 -9
- data/data/builtins/ruby_core/hash.yml +13 -20
- data/data/builtins/ruby_core/io.yml +6 -9
- data/data/builtins/ruby_core/numeric.yml +2 -3
- data/data/builtins/ruby_core/proc.yml +4 -8
- data/data/builtins/ruby_core/re.yml +6 -9
- data/data/builtins/ruby_core/set.yml +2 -3
- data/data/builtins/ruby_core/string.yml +4 -7
- data/data/builtins/ruby_core/struct.yml +0 -6
- data/data/builtins/ruby_core/time.yml +3 -6
- data/data/effects/core.yml +843 -0
- data/data/effects/registry.yml +67 -0
- data/docs/handbook/07-rbs-and-extended.md +106 -5
- data/docs/manual/02-cli-reference.md +311 -1
- data/docs/manual/03-configuration.md +84 -1
- data/docs/manual/04-diagnostics.md +6 -2
- data/docs/manual/16-rbs-extended-annotations.md +77 -0
- data/docs/manual/18-removing-dead-code.md +326 -0
- data/docs/manual/README.md +5 -0
- data/docs/manual/plugins/rigor-actionmailer.md +5 -0
- data/docs/manual/plugins/rigor-activejob.md +47 -0
- data/docs/manual/plugins/rigor-factorybot.md +16 -0
- data/docs/manual/plugins/rigor-pundit.md +26 -0
- data/docs/manual/plugins/rigor-rails-routes.md +41 -2
- data/docs/manual/plugins/rigor-rspec-rails.md +5 -0
- data/docs/manual/plugins/rigor-rspec.md +8 -0
- data/docs/manual/plugins/rigor-sidekiq.md +54 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +28 -2
- data/lib/rigor/analysis/incremental_session.rb +140 -6
- data/lib/rigor/analysis/reachability/graph.rb +244 -0
- data/lib/rigor/analysis/reachability/plugin_roots.rb +164 -0
- data/lib/rigor/analysis/reachability/project_files.rb +58 -0
- data/lib/rigor/analysis/reachability/scan.rb +241 -0
- data/lib/rigor/analysis/reachability/signature_scan.rb +134 -0
- data/lib/rigor/analysis/rule_catalog.rb +158 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +54 -0
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +108 -0
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +358 -0
- data/lib/rigor/analysis/runner/envelope_messages.rb +61 -0
- data/lib/rigor/analysis/runner/pool_coordinator.rb +68 -10
- data/lib/rigor/analysis/runner.rb +420 -10
- data/lib/rigor/analysis/worker_session.rb +58 -2
- data/lib/rigor/bleeding_edge.rb +22 -0
- data/lib/rigor/cache/incremental_snapshot.rb +24 -5
- data/lib/rigor/cli/check_command.rb +13 -1
- data/lib/rigor/cli/check_runner_factory.rb +4 -1
- data/lib/rigor/cli/effects_command.rb +131 -0
- data/lib/rigor/cli/effects_diff_renderer.rb +123 -0
- data/lib/rigor/cli/effects_explain_renderer.rb +68 -0
- data/lib/rigor/cli/effects_renderer.rb +54 -0
- data/lib/rigor/cli/effects_report.rb +53 -0
- data/lib/rigor/cli/effects_snapshot_command.rb +256 -0
- data/lib/rigor/cli/unused_command.rb +288 -0
- data/lib/rigor/cli.rb +16 -0
- data/lib/rigor/configuration/severity_profile.rb +23 -0
- data/lib/rigor/configuration.rb +296 -14
- data/lib/rigor/effects/attribution.rb +76 -0
- data/lib/rigor/effects/catalog.rb +275 -0
- data/lib/rigor/effects/collector.rb +182 -0
- data/lib/rigor/effects/config_envelopes.rb +185 -0
- data/lib/rigor/effects/discharge.rb +69 -0
- data/lib/rigor/effects/effect_table.rb +92 -0
- data/lib/rigor/effects/entry_points.rb +93 -0
- data/lib/rigor/effects/envelope.rb +86 -0
- data/lib/rigor/effects/envelope_check.rb +172 -0
- data/lib/rigor/effects/envelope_index.rb +157 -0
- data/lib/rigor/effects/file_collection.rb +160 -0
- data/lib/rigor/effects/framework_units.rb +167 -0
- data/lib/rigor/effects/identity.rb +104 -0
- data/lib/rigor/effects/label.rb +77 -0
- data/lib/rigor/effects/label_intent.rb +73 -0
- data/lib/rigor/effects/label_set.rb +136 -0
- data/lib/rigor/effects/liskov_check.rb +167 -0
- data/lib/rigor/effects/local_ownership.rb +132 -0
- data/lib/rigor/effects/method_key.rb +40 -0
- data/lib/rigor/effects/mutation_classifier.rb +92 -0
- data/lib/rigor/effects/narrowing.rb +202 -0
- data/lib/rigor/effects/origin.rb +66 -0
- data/lib/rigor/effects/path_finder.rb +89 -0
- data/lib/rigor/effects/plugin_facts.rb +335 -0
- data/lib/rigor/effects/propagator.rb +275 -0
- data/lib/rigor/effects/registry.rb +193 -0
- data/lib/rigor/effects/scanner.rb +290 -0
- data/lib/rigor/effects/signature_sources.rb +74 -0
- data/lib/rigor/effects/snapshot.rb +380 -0
- data/lib/rigor/effects/snapshot_diff.rb +265 -0
- data/lib/rigor/effects/summary.rb +154 -0
- data/lib/rigor/effects/taint_cause.rb +38 -0
- data/lib/rigor/effects/unit_scan.rb +572 -0
- data/lib/rigor/effects/unknown_label_check.rb +86 -0
- data/lib/rigor/effects/unknown_label_report.rb +59 -0
- data/lib/rigor/effects.rb +47 -0
- data/lib/rigor/environment/rbs_loader.rb +34 -0
- data/lib/rigor/environment.rb +8 -1
- data/lib/rigor/flow_contribution/element.rb +1 -0
- data/lib/rigor/flow_contribution/merge_result.rb +5 -3
- data/lib/rigor/flow_contribution/merger.rb +16 -1
- data/lib/rigor/flow_contribution.rb +20 -4
- data/lib/rigor/inference/expression_typer.rb +18 -4
- data/lib/rigor/inference/pre_eval_constants.rb +189 -0
- data/lib/rigor/inference/scope_indexer.rb +15 -3
- data/lib/rigor/plugin/base.rb +24 -0
- data/lib/rigor/plugin/effect_attribution.rb +208 -0
- data/lib/rigor/plugin/effect_edge.rb +101 -0
- data/lib/rigor/plugin/effect_entry_points.rb +51 -0
- data/lib/rigor/plugin/first_party.rb +57 -0
- data/lib/rigor/plugin/isolation.rb +4 -1
- data/lib/rigor/plugin/manifest.rb +103 -3
- data/lib/rigor/plugin/registry.rb +48 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/envelope_scanner.rb +160 -0
- data/lib/rigor/rbs_extended.rb +168 -0
- data/lib/rigor/reflection.rb +131 -30
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +90 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +7 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/effects.rb +85 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +8 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +152 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +9 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +156 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +113 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +68 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +186 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +12 -1
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +145 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/effects.rb +77 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +143 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -1
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +385 -28
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +151 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +46 -3
- data/plugins/rigor-rails/lib/rigor-rails.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/effects.rb +47 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +7 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +81 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +13 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +202 -15
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +43 -3
- data/plugins/rigor-railties/lib/rigor/plugin/railties/effects.rb +171 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +62 -0
- data/plugins/rigor-railties/lib/rigor-railties.rb +3 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +122 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +56 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/inference.rbs +2 -0
- data/sig/rigor/rbs_extended.rbs +6 -0
- data/sig/rigor.rbs +17 -1
- data/skills/rigor-unused-adjudicate/SKILL.md +90 -0
- metadata +79 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "label_set"
|
|
4
|
+
require_relative "origin"
|
|
5
|
+
require_relative "taint_cause"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Effects
|
|
9
|
+
# One method's effect summary — the value the collector produces and the propagator closes over
|
|
10
|
+
# (ADR-103 WD1 / WD12; the lanes and the exhaustiveness bit are normative in
|
|
11
|
+
# `docs/type-specification/effect-labels.md`).
|
|
12
|
+
#
|
|
13
|
+
# Three carriers and one projection:
|
|
14
|
+
#
|
|
15
|
+
# - {#bundles} — `{Origin => LabelSet}`, the labels kept **per origin** so the judgment can discharge
|
|
16
|
+
# policy per origin (tolerating what an origin was *for* frees its transport, WD14; {Discharge}).
|
|
17
|
+
# - {#declared_bundles} — the same shape for the `≤` lane, and {#declared} its flat projection. Its one
|
|
18
|
+
# producer today is the project's `effects.attribution:` table (#385), whose origins carry
|
|
19
|
+
# {Origin::ATTRIBUTION}; a *nominal supertype's* envelope joins it with #386. The lane is kept apart
|
|
20
|
+
# from {#bundles} because nothing declared is ever proven: diagnostics read {#proven} only, so an
|
|
21
|
+
# attributed label can never manufacture an `effect.envelope-exceeded`.
|
|
22
|
+
# - {#exhaustive} — false when some call this method makes could not be resolved.
|
|
23
|
+
# - {#causes} — why, from {TaintCause}'s closed enum, as `[cause, detail]` pairs. Empty when exhaustive.
|
|
24
|
+
#
|
|
25
|
+
# {#proven} is the flat projection of {#bundles}: the join of every bundle. It is computed once at
|
|
26
|
+
# construction rather than memoised, because the instance is frozen (and so crosses a fork-pool marshal
|
|
27
|
+
# and a Ractor boundary as it is).
|
|
28
|
+
#
|
|
29
|
+
# Summaries compose by {#join}, which is associative and commutative on every field, so merging a
|
|
30
|
+
# project's per-file collections in any order — pooled or sequential — yields the same table.
|
|
31
|
+
class Summary
|
|
32
|
+
NO_BUNDLES = {}.freeze
|
|
33
|
+
private_constant :NO_BUNDLES
|
|
34
|
+
|
|
35
|
+
NO_CAUSES = [].freeze
|
|
36
|
+
private_constant :NO_CAUSES
|
|
37
|
+
|
|
38
|
+
# The bound {#trivial?} measures against. Not a user-facing knob: it is `%a{pure}`'s carve-out,
|
|
39
|
+
# spelled once.
|
|
40
|
+
TRIVIAL_BOUND = LabelSet.new(["mutate.local"])
|
|
41
|
+
|
|
42
|
+
# The pure-modulo-frame-ownership summary: no origins, nothing declared, exhaustive, untainted.
|
|
43
|
+
def self.empty
|
|
44
|
+
@empty ||= new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Builds a summary carrying a single taint and no labels — the fail-soft answer for a unit the
|
|
48
|
+
# collector could not finish (`collector-error`) and the shape a caller uses to taint a method it
|
|
49
|
+
# knows nothing else about.
|
|
50
|
+
def self.tainted(cause, detail = nil)
|
|
51
|
+
new(exhaustive: false, causes: [[cause, detail]])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
attr_reader :bundles, :declared_bundles, :declared, :causes, :proven
|
|
55
|
+
|
|
56
|
+
def initialize(bundles: NO_BUNDLES, declared_bundles: NO_BUNDLES, exhaustive: true, causes: NO_CAUSES)
|
|
57
|
+
@bundles = normalize_bundles(bundles)
|
|
58
|
+
@declared_bundles = normalize_bundles(declared_bundles)
|
|
59
|
+
@exhaustive = exhaustive ? true : false
|
|
60
|
+
@causes = normalize_causes(causes)
|
|
61
|
+
@proven = flatten(@bundles)
|
|
62
|
+
@declared = flatten(@declared_bundles)
|
|
63
|
+
freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Whether every call this method makes was resolved. A false bit reads "these effects, and possibly
|
|
67
|
+
# more" and MUST NOT on its own produce a finding.
|
|
68
|
+
def exhaustive?
|
|
69
|
+
@exhaustive
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Whether this summary is worth showing at all: an exhaustive method whose whole proven footprint is
|
|
73
|
+
# frame-local mutation and which claims nothing. `mutate.local` is tolerated by every envelope
|
|
74
|
+
# (`%a{pure}` included), so a method that only mutates what its own frame allocated reads as pure
|
|
75
|
+
# for reporting purposes. The report omits these unless `--full` is given; the snapshot of #381
|
|
76
|
+
# omits them from `methods:`.
|
|
77
|
+
#
|
|
78
|
+
# A surviving DECLARED label makes the row non-trivial (#386): `≤ io.db` is exactly the kind of
|
|
79
|
+
# thing a reviewer reads a report or a snapshot diff for, and dropping the row would say "clean"
|
|
80
|
+
# about a method that claims otherwise. "Surviving" is the rendering rule — a declared label the
|
|
81
|
+
# proven lane already admits says nothing new and cannot rescue a row on its own.
|
|
82
|
+
def trivial?
|
|
83
|
+
@exhaustive && @proven.subsumed_by?(TRIVIAL_BOUND) && @declared.excluding_subsumed_by(@proven).empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Union in every lane: bundles join per origin, the declared lane joins, the exhaustiveness bit ANDs,
|
|
87
|
+
# causes union. Reopenings of one method and the several files that contribute to it fold through
|
|
88
|
+
# this.
|
|
89
|
+
def join(other)
|
|
90
|
+
return self if other.equal?(self)
|
|
91
|
+
|
|
92
|
+
self.class.new(
|
|
93
|
+
bundles: merge_bundles(@bundles, other.bundles),
|
|
94
|
+
declared_bundles: merge_bundles(@declared_bundles, other.declared_bundles),
|
|
95
|
+
exhaustive: @exhaustive && other.exhaustive?,
|
|
96
|
+
causes: @causes + other.causes
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def ==(other)
|
|
101
|
+
other.is_a?(Summary) && other.bundles == @bundles && other.declared_bundles == @declared_bundles &&
|
|
102
|
+
other.exhaustive? == @exhaustive && other.causes == @causes
|
|
103
|
+
end
|
|
104
|
+
alias eql? ==
|
|
105
|
+
|
|
106
|
+
def hash
|
|
107
|
+
[self.class, @bundles, @declared_bundles, @exhaustive, @causes].hash
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def inspect
|
|
111
|
+
"#<Rigor::Effects::Summary #{@proven.to_a.join(', ')}#{' ?' unless @exhaustive}>"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def merge_bundles(mine, theirs)
|
|
117
|
+
return mine if theirs.empty?
|
|
118
|
+
return theirs if mine.empty?
|
|
119
|
+
|
|
120
|
+
merged = mine.dup
|
|
121
|
+
theirs.each { |origin, set| merged[origin] = merged.key?(origin) ? merged[origin].join(set) : set }
|
|
122
|
+
merged
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def flatten(bundles)
|
|
126
|
+
bundles.each_value.reduce(LabelSet::EMPTY) { |acc, set| acc.join(set) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Origins are sorted by their rendering so two summaries built from the same facts in different
|
|
130
|
+
# orders are `==` and render identically. A repeated origin joins rather than clobbers.
|
|
131
|
+
def normalize_bundles(bundles)
|
|
132
|
+
return NO_BUNDLES if bundles.empty?
|
|
133
|
+
|
|
134
|
+
merged = {}
|
|
135
|
+
bundles.each do |origin, set|
|
|
136
|
+
merged[origin] = merged.key?(origin) ? merged[origin].join(set) : set
|
|
137
|
+
end
|
|
138
|
+
merged.reject { |_, set| set.empty? }.sort_by { |origin, _| origin.to_s }.to_h.freeze
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# `[cause, detail]` pairs, de-duplicated and sorted. A cause outside {TaintCause}'s enum is dropped
|
|
142
|
+
# rather than raised on: the collector is fail-soft, and a producer bug must not take a run down.
|
|
143
|
+
def normalize_causes(causes)
|
|
144
|
+
return NO_CAUSES if causes.empty?
|
|
145
|
+
|
|
146
|
+
causes
|
|
147
|
+
.filter_map { |cause, detail| [cause.to_s, detail&.to_s].freeze if TaintCause.valid?(cause) }
|
|
148
|
+
.uniq
|
|
149
|
+
.sort_by { |cause, detail| [cause, detail.to_s] }
|
|
150
|
+
.freeze
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Effects
|
|
5
|
+
# The closed enum of reasons a method's effect summary is not exhaustive (ADR-103 WD14;
|
|
6
|
+
# normative in `docs/type-specification/effect-labels.md`).
|
|
7
|
+
#
|
|
8
|
+
# A tainted summary reads "these effects, and possibly more": the cause says which knowledge the
|
|
9
|
+
# analyzer could not have. Taint never produces a finding — diagnostics read the proven lane
|
|
10
|
+
# only — so this enum is a reporting vocabulary, not a diagnostic one.
|
|
11
|
+
#
|
|
12
|
+
# Nothing produces causes yet; the collector of #379 is the first writer.
|
|
13
|
+
module TaintCause
|
|
14
|
+
# Closed and ordered by how it arises: dispatch the analyzer could not resolve first, then
|
|
15
|
+
# attribution it had to take on trust, then the collector's own limits.
|
|
16
|
+
ALL = %w[
|
|
17
|
+
dynamic-receiver
|
|
18
|
+
dynamic-send
|
|
19
|
+
method-missing
|
|
20
|
+
unresolved-self-call
|
|
21
|
+
opaque-callable
|
|
22
|
+
unknown-ownership
|
|
23
|
+
plugin-attribution
|
|
24
|
+
template-not-analysed
|
|
25
|
+
collector-error
|
|
26
|
+
budget
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# Whether `cause` is one of the enum's members. The enum is closed: a spelling outside it is
|
|
32
|
+
# a bug in the producer, not a vocabulary extension point.
|
|
33
|
+
def valid?(cause)
|
|
34
|
+
ALL.include?(cause.to_s)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|