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,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class ActivesupportCoreExt < Rigor::Plugin::Base
|
|
8
|
+
# rigor-activesupport-core-ext's effect contract (ADR-103 WD10; design note § 11.2; issue #387).
|
|
9
|
+
#
|
|
10
|
+
# **Scope note.** This is the *impure* half of ActiveSupport only: the clock, the notification bus and
|
|
11
|
+
# `CurrentAttributes`. The `%a{pure}` sweep over `blank?` / `present?` / `deep_dup` and the rest of
|
|
12
|
+
# the core_ext predicate surface — the single cheapest purity win in a Rails app, per WD10 — is issue
|
|
13
|
+
# #388 and lands in this plugin's shipped RBS, not here (`try` was audited and skipped: it dispatches
|
|
14
|
+
# to whatever method the caller names, so its purity is a fact about the call site, not about `try`).
|
|
15
|
+
# One row below (`in_time_zone_row`) is #388's own addition: `DateTime#in_time_zone` reads `Time.zone`
|
|
16
|
+
# through a default argument, a gap the RBS sweep found but can only fix here.
|
|
17
|
+
#
|
|
18
|
+
# ## The clock
|
|
19
|
+
#
|
|
20
|
+
# `Time.current` and friends are `nondet.time`, plus `global.read` for the zone: `Time.zone` is
|
|
21
|
+
# process state that `Time.use_zone` and the per-request `around_action` both change. The distinction
|
|
22
|
+
# matters for the purity policy of ADR-103 WD9 — a method that calls `Time.current` may never have its
|
|
23
|
+
# result remembered, and that is the whole reason the label exists.
|
|
24
|
+
#
|
|
25
|
+
# ## `CurrentAttributes`
|
|
26
|
+
#
|
|
27
|
+
# `Current.user` is fiber-local mutable state: `global.read` on the way in, `global.write` on the way
|
|
28
|
+
# out, with `rails.current.read` / `.write` as the meaning. Rigor cannot name the attributes per app
|
|
29
|
+
# — they come from an `attribute :user` macro in the project's own `Current` class — so the rows are
|
|
30
|
+
# keyed on the framework base class and reach every project subclass through inheritance. `reset` and
|
|
31
|
+
# `set` are named explicitly because they are the ones a reviewer looks for.
|
|
32
|
+
module Effects
|
|
33
|
+
TIME = "Time"
|
|
34
|
+
DATE = "Date"
|
|
35
|
+
DATETIME = "DateTime"
|
|
36
|
+
ZONE = "Time.zone"
|
|
37
|
+
CURRENT = "ActiveSupport::CurrentAttributes"
|
|
38
|
+
NOTIFICATIONS = "ActiveSupport::Notifications"
|
|
39
|
+
|
|
40
|
+
CLOCK = ["nondet.time", "global.read"].freeze
|
|
41
|
+
CLOCK_NO_ZONE = ["nondet.time"].freeze
|
|
42
|
+
ZONE_READ = ["global.read"].freeze
|
|
43
|
+
CURRENT_READ = ["global.read", "rails.current.read"].freeze
|
|
44
|
+
CURRENT_WRITE = ["global.write", "rails.current.write"].freeze
|
|
45
|
+
TELEMETRY = %w[io telemetry].freeze
|
|
46
|
+
|
|
47
|
+
# Zone-aware clock readers: they consult `Time.zone`, which is mutable process state.
|
|
48
|
+
ZONED_NOW = %w[current].freeze
|
|
49
|
+
# Zone-blind ones. `Time.now` / `Date.today` are already catalogue rows for core Ruby; these are the
|
|
50
|
+
# ActiveSupport additions.
|
|
51
|
+
ZONE_BLIND = %w[].freeze
|
|
52
|
+
|
|
53
|
+
module_function
|
|
54
|
+
|
|
55
|
+
def attributions
|
|
56
|
+
clock_rows + zone_rows + current_rows + notification_rows + [in_time_zone_row]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clock_rows
|
|
60
|
+
[
|
|
61
|
+
row(TIME, :current, CLOCK, singleton: true,
|
|
62
|
+
why: "reads the clock AND `Time.zone`, which `Time.use_zone` and a per-request " \
|
|
63
|
+
"`around_action` both rewrite"),
|
|
64
|
+
row(DATE, :current, CLOCK, singleton: true, why: "the same, to day precision"),
|
|
65
|
+
row(DATE, :yesterday, CLOCK, singleton: true, why: "`Date.current - 1`"),
|
|
66
|
+
row(DATE, :tomorrow, CLOCK, singleton: true, why: "`Date.current + 1`"),
|
|
67
|
+
row(DATETIME, :current, CLOCK, singleton: true, why: "the DateTime spelling of `Time.current`"),
|
|
68
|
+
row("ActiveSupport::TimeZone", :now, CLOCK_NO_ZONE, singleton: true,
|
|
69
|
+
why: "the zone is the receiver here, so only the clock read is left"),
|
|
70
|
+
row(ZONE, :now, CLOCK_NO_ZONE, why: "`Time.zone.now` — the zone is named, the clock is read"),
|
|
71
|
+
row(ZONE, :today, CLOCK_NO_ZONE, why: "`Time.zone.today`")
|
|
72
|
+
] + duration_rows
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# `2.days.ago` / `3.hours.from_now`. The receiver is an `ActiveSupport::Duration`; the call reads
|
|
76
|
+
# the clock, which is what makes `created_at > 7.days.ago` a `nondet.time` expression and not a
|
|
77
|
+
# constant.
|
|
78
|
+
def duration_rows
|
|
79
|
+
%w[ago until since from_now after before].map do |selector|
|
|
80
|
+
row("ActiveSupport::Duration", selector, CLOCK,
|
|
81
|
+
why: "`n.days.ago` and its family read the clock (and the zone) at the call — this is what " \
|
|
82
|
+
"stops a duration comparison folding to a constant")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def zone_rows
|
|
87
|
+
[
|
|
88
|
+
row(TIME, :zone, ZONE_READ, singleton: true,
|
|
89
|
+
why: "reads the process/fiber's current time zone, which is mutable"),
|
|
90
|
+
row(TIME, :zone=, ["global.write"], singleton: true,
|
|
91
|
+
why: "rewrites the process/fiber's time zone for everything downstream"),
|
|
92
|
+
row(TIME, :use_zone, ["global.write"], singleton: true,
|
|
93
|
+
why: "swaps the zone around a block; the block's own origins join by containment")
|
|
94
|
+
]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# `DateTime#in_time_zone` — a gap the #388 `%a{pure}` sweep over `sig/active_support/core_ext.rbs`
|
|
98
|
+
# surfaced: `in_time_zone(zone = ::Time.zone)` reads `Time.zone` through its own default argument
|
|
99
|
+
# whenever the caller doesn't pass one explicitly, so it cannot be annotated `%a{pure}` there. It
|
|
100
|
+
# belongs here rather than in the RBS because the label is `global.read` alone (not the
|
|
101
|
+
# `nondet.time` + `global.read` pair `CLOCK` carries) — `in_time_zone` converts an already-fixed
|
|
102
|
+
# instant into a zone, it does not read the clock itself.
|
|
103
|
+
def in_time_zone_row
|
|
104
|
+
row("DateTime", :in_time_zone, ZONE_READ,
|
|
105
|
+
why: "the default argument reads `Time.zone` when the caller doesn't name one explicitly")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def current_rows
|
|
109
|
+
[
|
|
110
|
+
row(CURRENT, :set, CURRENT_WRITE, singleton: true,
|
|
111
|
+
why: "writes fiber-local request state that every later read in the request sees"),
|
|
112
|
+
row(CURRENT, :reset, CURRENT_WRITE, singleton: true, why: "clears the same fiber-local state"),
|
|
113
|
+
row(CURRENT, :attributes, CURRENT_READ, singleton: true, why: "reads the whole bag"),
|
|
114
|
+
row(CURRENT, :attributes=, CURRENT_WRITE, singleton: true, why: "replaces the whole bag"),
|
|
115
|
+
row(CURRENT, :instance, CURRENT_READ, singleton: true,
|
|
116
|
+
why: "materialises the per-fiber instance the accessors read through")
|
|
117
|
+
]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def notification_rows
|
|
121
|
+
[
|
|
122
|
+
EffectAttribution.new(
|
|
123
|
+
receiver: NOTIFICATIONS, method: :instrument, labels: TELEMETRY, singleton: true,
|
|
124
|
+
discharge: true, taint: "opaque-callable",
|
|
125
|
+
why: "publishes to the notification bus — `io` because a subscriber may do anything and " \
|
|
126
|
+
"`telemetry` because that is the meaning. The taint is the honest half: the SUBSCRIBERS " \
|
|
127
|
+
"are registered at runtime and the analyzer cannot see them, so the summary reads " \
|
|
128
|
+
"'this much, and possibly more'"
|
|
129
|
+
),
|
|
130
|
+
row(NOTIFICATIONS, :publish, TELEMETRY, singleton: true, why: "the bare publish"),
|
|
131
|
+
row(NOTIFICATIONS, :subscribe, ["mutate.static"], singleton: true,
|
|
132
|
+
why: "registers a subscriber on process-global state")
|
|
133
|
+
]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def row(receiver, selector, labels, why:, singleton: false)
|
|
137
|
+
EffectAttribution.new(receiver: receiver, method: selector, labels: labels,
|
|
138
|
+
singleton: singleton, discharge: true, why: why)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "rigor/plugin"
|
|
4
4
|
|
|
5
|
+
require_relative "activesupport_core_ext/effects"
|
|
6
|
+
|
|
5
7
|
module Rigor
|
|
6
8
|
module Plugin
|
|
7
9
|
# ADR-25 — a pure RBS-bundle plugin. It ships NO analyzer code: no `diagnostics_for_file`, no
|
|
@@ -24,7 +26,13 @@ module Rigor
|
|
|
24
26
|
# aliases that return Time, not Date).
|
|
25
27
|
version: "0.2.0",
|
|
26
28
|
description: "RBS bundle for the most-frequently-flagged ActiveSupport core_ext extensions.",
|
|
27
|
-
signature_paths: ["sig"]
|
|
29
|
+
signature_paths: ["sig"],
|
|
30
|
+
# ADR-103 WD10 (#387) — the IMPURE half of ActiveSupport: the clock, the notification bus and
|
|
31
|
+
# `CurrentAttributes`. The `%a{pure}` sweep over the predicate surface is issue #388 and lands in
|
|
32
|
+
# `sig/`, not here. See {Effects}.
|
|
33
|
+
effect_root: "rails",
|
|
34
|
+
effect_labels: %w[rails.current.read rails.current.write],
|
|
35
|
+
effect_attributions: Effects.attributions
|
|
28
36
|
)
|
|
29
37
|
end
|
|
30
38
|
|