rigortype 0.3.4 → 0.3.6
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 +5 -5
- data/data/effects/core.yml +18 -1
- data/data/effects/registry.yml +31 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +33 -0
- data/docs/handbook/02-everyday-types.md +1 -1
- data/docs/handbook/04-tuples-and-shapes.md +1 -1
- data/docs/handbook/08-understanding-errors.md +1 -1
- data/docs/handbook/09-plugins.md +2 -2
- data/docs/handbook/10-sorbet.md +1 -1
- data/docs/handbook/README.md +2 -2
- data/docs/handbook/appendix-go.md +1 -1
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +1 -1
- data/docs/handbook/appendix-protocols-and-structural-typing.md +2 -2
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +2 -2
- data/docs/handbook/appendix-typescript.md +4 -4
- data/docs/manual/02-cli-reference.md +73 -22
- data/docs/manual/03-configuration.md +19 -2
- data/docs/manual/04-diagnostics.md +15 -2
- data/docs/manual/08-skills.md +1 -1
- data/docs/manual/11-ci.md +38 -1
- data/docs/manual/12-caching.md +40 -1
- data/docs/manual/15-type-protection-coverage.md +1 -1
- data/docs/manual/16-rbs-extended-annotations.md +15 -2
- data/docs/manual/18-removing-dead-code.md +13 -4
- data/docs/manual/19-effect-labels.md +678 -0
- data/docs/manual/README.md +7 -2
- data/docs/manual/ci-templates/README.md +9 -0
- data/docs/manual/plugins/rigor-actionmailer.md +4 -4
- data/docs/manual/plugins/rigor-activejob.md +3 -3
- data/docs/manual/plugins/rigor-activerecord.md +4 -4
- data/docs/manual/plugins/rigor-rails-i18n.md +5 -5
- data/docs/manual/plugins/rigor-rspec.md +6 -2
- data/lib/rigor/analysis/diagnostic.rb +17 -6
- data/lib/rigor/analysis/effects_cache_probe.rb +132 -0
- data/lib/rigor/analysis/reachability/graph.rb +30 -11
- data/lib/rigor/analysis/reachability/plugin_roots.rb +11 -8
- data/lib/rigor/analysis/reachability/scan.rb +20 -4
- data/lib/rigor/analysis/reachability/scan_cache.rb +130 -0
- data/lib/rigor/analysis/rule_catalog.rb +10 -3
- data/lib/rigor/analysis/run_cache_key.rb +12 -0
- data/lib/rigor/analysis/run_cache_probe.rb +69 -1
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -24
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +28 -28
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +15 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +25 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -4
- data/lib/rigor/analysis/runner.rb +163 -31
- data/lib/rigor/analysis/worker_session.rb +3 -1
- data/lib/rigor/cache/file_digest.rb +20 -2
- data/lib/rigor/cli/check_command.rb +67 -48
- data/lib/rigor/cli/coverage_command.rb +5 -6
- data/lib/rigor/cli/doc_links.rb +100 -0
- data/lib/rigor/cli/docs_command.rb +32 -2
- data/lib/rigor/cli/effects_command.rb +149 -10
- data/lib/rigor/cli/effects_diff_renderer.rb +88 -14
- data/lib/rigor/cli/effects_explain_renderer.rb +6 -3
- data/lib/rigor/cli/effects_renderer.rb +41 -3
- data/lib/rigor/cli/effects_report.rb +116 -6
- data/lib/rigor/cli/effects_snapshot_command.rb +75 -11
- data/lib/rigor/cli/unused_command.rb +65 -16
- data/lib/rigor/cli.rb +33 -9
- data/lib/rigor/configuration.rb +37 -25
- data/lib/rigor/configuration_error.rb +20 -0
- data/lib/rigor/effects/collector.rb +38 -1
- data/lib/rigor/effects/definition_lines.rb +100 -0
- data/lib/rigor/effects/entry_points.rb +47 -1
- data/lib/rigor/effects/envelope_check.rb +18 -1
- data/lib/rigor/effects/file_collection.rb +18 -4
- data/lib/rigor/effects/framework_units.rb +68 -13
- data/lib/rigor/effects/inline_anchor.rb +134 -0
- data/lib/rigor/effects/liskov_check.rb +17 -8
- data/lib/rigor/effects/plugin_facts.rb +62 -13
- data/lib/rigor/effects/propagator.rb +79 -19
- data/lib/rigor/effects/registry.rb +10 -3
- data/lib/rigor/effects/scanner.rb +21 -9
- data/lib/rigor/effects/signature_sources.rb +29 -2
- data/lib/rigor/effects/snapshot.rb +74 -26
- data/lib/rigor/effects/snapshot_diff.rb +26 -3
- data/lib/rigor/effects/taint_cause.rb +1 -0
- data/lib/rigor/effects/unit_scan.rb +87 -12
- data/lib/rigor/inference/synthetic_method_scanner.rb +7 -0
- data/lib/rigor/plugin/base.rb +4 -0
- data/lib/rigor/plugin/box.rb +18 -2
- data/lib/rigor/plugin/effect_ancestry.rb +80 -0
- data/lib/rigor/plugin/manifest.rb +34 -10
- data/lib/rigor/plugin/registry.rb +10 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +9 -0
- data/lib/rigor/rbs_extended.rb +22 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +17 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +27 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +56 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +75 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +13 -1
- data/skills/rigor-ci-setup/SKILL.md +2 -2
- data/skills/rigor-editor-setup/SKILL.md +2 -2
- data/skills/rigor-mcp-setup/SKILL.md +2 -2
- data/skills/rigor-monkeypatch-resolve/SKILL.md +1 -1
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2709c0d68fc616109c3300013f08fda3702484c6ca01ff67b4ccf9d0f354ad0
|
|
4
|
+
data.tar.gz: 77486c048ef9cd94a725b5ebb09ae4e161003b37f67919c769845ed36f6c60bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15a406b49bcd62e9efc2c1c143903a85157dbaf165ccca5defdf4abd69949d0c0d0107afd55469e5a0b27fa667e3c59a0ec58de854af503cad0a9e3952568a6e
|
|
7
|
+
data.tar.gz: fa429f47f4ed13b53fa07c7078510524bd6d3b304f7ffa48916b3cfab0e954b42167bda011b413946448dbdaeb63e78dfeaf444ec86a68cf928cefcf3fd70813
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Rigor
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/rigortype)
|
|
4
|
-
[](
|
|
4
|
+
[](LICENSE)
|
|
5
5
|
[](https://deepwiki.com/rigortype/rigor)
|
|
6
6
|
|
|
7
7
|
**Type-aware bug finding for Ruby — no annotations required, and a
|
|
@@ -47,7 +47,7 @@ s.lenght
|
|
|
47
47
|
|
|
48
48
|
```shell-session
|
|
49
49
|
$ rigor check demo.rb
|
|
50
|
-
demo.rb:7:3: error: undefined method `lenght' for "hello-world"
|
|
50
|
+
demo.rb:7:3: error: undefined method `lenght' for "hello-world" [call.undefined-method]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Note what the error says: not ``undefined method `lenght' for String``
|
|
@@ -185,9 +185,9 @@ plugins:
|
|
|
185
185
|
|
|
186
186
|
The full catalogue is in [`plugins/README.md`](plugins/README.md); the
|
|
187
187
|
`rigor-project-init` Skill picks the right set for you. To teach Rigor
|
|
188
|
-
your own DSL, the [`rigor-plugin-author`](skills/rigor-plugin-author
|
|
188
|
+
your own DSL, the [`rigor-plugin-author`](skills/rigor-plugin-author)
|
|
189
189
|
Skill authors a plugin step-by-step, and
|
|
190
|
-
[`rigor-baseline-reduce`](skills/rigor-baseline-reduce
|
|
190
|
+
[`rigor-baseline-reduce`](skills/rigor-baseline-reduce) drives the
|
|
191
191
|
baseline down once you are running.
|
|
192
192
|
|
|
193
193
|
## Going deeper
|
|
@@ -231,7 +231,7 @@ rigor docs --list # list every bundled page
|
|
|
231
231
|
|
|
232
232
|
## Status
|
|
233
233
|
|
|
234
|
-
Current release: **`v0.3.
|
|
234
|
+
Current release: **`v0.3.6`** (2026-08-30) — on the
|
|
235
235
|
`0.3.x` line, continuing the evaluation era opened by `v0.2.0`, the first
|
|
236
236
|
publicly-announced (general / evaluation) release. The line publishes
|
|
237
237
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
data/data/effects/core.yml
CHANGED
|
@@ -531,7 +531,19 @@ classes:
|
|
|
531
531
|
# ---------------------------------------------------------------------------------------------
|
|
532
532
|
# Network.
|
|
533
533
|
# ---------------------------------------------------------------------------------------------
|
|
534
|
-
Socket:
|
|
534
|
+
Socket:
|
|
535
|
+
posture: net
|
|
536
|
+
why: "the BSD socket surface; the rows below read the machine's own identity and send nothing"
|
|
537
|
+
singleton_methods:
|
|
538
|
+
# `io.net` on these was a wrong label, not a conservative one, and it travelled: Redmine builds
|
|
539
|
+
# its Message-IDs from `Socket.gethostname`, so 215 of 4,234 rows — every model `save`, every
|
|
540
|
+
# `Mailer#*`, and `Redmine::IMAP.check`, whose actual IMAP connection is invisible — read as
|
|
541
|
+
# network traffic on the strength of a hostname lookup (#458). `io` is the honest bound: the
|
|
542
|
+
# call leaves the process to ask the kernel, and nothing goes over a wire.
|
|
543
|
+
gethostname: { effects: [io], why: "reads this machine's own name; no packet leaves the process" }
|
|
544
|
+
gethostbyname: { effects: [io.net], why: "resolves a name — the resolver, and possibly DNS" }
|
|
545
|
+
getifaddrs: { effects: [io], why: "reads this machine's own interfaces; local configuration, not resolution" }
|
|
546
|
+
ip_address_list: { effects: [io], why: "reads this machine's own addresses; local configuration, not resolution" }
|
|
535
547
|
BasicSocket: { posture: net, why: "the shared socket surface" }
|
|
536
548
|
TCPSocket: { posture: net, why: "a TCP stream" }
|
|
537
549
|
TCPServer: { posture: net, why: "a listening TCP socket" }
|
|
@@ -572,6 +584,11 @@ classes:
|
|
|
572
584
|
start: { effects: [io.net.http], why: "opens the connection; the block joins by containment" }
|
|
573
585
|
new: { effects: [], why: "allocates the client; nothing is sent until start / request" }
|
|
574
586
|
"Net::SMTP": { posture: net, why: "an SMTP session; `email.send` is the plugin's semantic layer, not the transport's" }
|
|
587
|
+
# rbs ships `net-http` / `net-smtp` and not these, so a project's `Net::IMAP` types as `Dynamic` and
|
|
588
|
+
# only the constant the author wrote names the class. #463 is what lets a posture answer there; before
|
|
589
|
+
# it, `Net::FTP` had a row that could never fire and these two were not worth writing.
|
|
590
|
+
"Net::IMAP": { posture: net, why: "an IMAP session" }
|
|
591
|
+
"Net::POP3": { posture: net, why: "a POP3 session" }
|
|
575
592
|
"Net::FTP": { posture: net, why: "an FTP session" }
|
|
576
593
|
|
|
577
594
|
URI:
|
data/data/effects/registry.yml
CHANGED
|
@@ -11,6 +11,25 @@
|
|
|
11
11
|
# still reads.
|
|
12
12
|
vocabulary: 1
|
|
13
13
|
|
|
14
|
+
# What each root means, in one line. Read by `rigor effects --list-labels` (#429), which is the only
|
|
15
|
+
# way an installed Rigor can tell a reader what to write in `effects.envelopes:`, `effects.tolerated:`,
|
|
16
|
+
# `effects.attribution:` or an `%a{rigor:v1:effect …}` annotation — four surfaces that all require
|
|
17
|
+
# typing a label. `docs/manual/19-effect-labels.md`'s vocabulary table is the same wording for the same
|
|
18
|
+
# reason; a root without a line here simply prints without one, which is what a plugin-opened root does.
|
|
19
|
+
descriptions:
|
|
20
|
+
io: talks to something outside the process — a file, a socket, a database, a terminal, a subprocess
|
|
21
|
+
mutate: changes state in place, in the frame's own objects, the receiver, another object, or the class
|
|
22
|
+
nondet: reads something that differs between two otherwise identical runs
|
|
23
|
+
global: process-wide state — ENV, $stdout, a class variable
|
|
24
|
+
exit: can end the process
|
|
25
|
+
ffi: calls out through a foreign-function interface
|
|
26
|
+
telemetry: reports on the program rather than doing its work — logs, metrics, traces
|
|
27
|
+
email: sends mail, whatever the transport underneath is
|
|
28
|
+
job: hands work to a queue for another process to run
|
|
29
|
+
cache: reads or writes a cache, whatever backs it
|
|
30
|
+
failure: registered so a policy written for Steins, the sibling PHP analyzer, parses here; Rigor never
|
|
31
|
+
produces one, so a bound naming it is satisfied vacuously
|
|
32
|
+
|
|
14
33
|
labels:
|
|
15
34
|
# Steins v1, verbatim (rigortype/steins `docs/type-specification/effects.md`). Shared vocabulary:
|
|
16
35
|
# a policy naming these labels must read the same against a PHP service and a Rails app.
|
|
@@ -53,9 +72,18 @@ labels:
|
|
|
53
72
|
- io.db.write
|
|
54
73
|
- io.db.transaction
|
|
55
74
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
75
|
+
# Steins carries these (its ADR-0042) and Rigor does not produce them: they name a failure arm's
|
|
76
|
+
# value provenance rather than an effect. Registered for the same reason `io.output.buffer` is —
|
|
77
|
+
# so a policy written against Steins parses here — and never inferred (ADR-103 WD16).
|
|
78
|
+
- failure
|
|
79
|
+
- failure.environment
|
|
80
|
+
- failure.input
|
|
81
|
+
- failure.resource
|
|
82
|
+
|
|
83
|
+
# Application-meaning roots. These are the labels a policy actually names ("presenters do not
|
|
84
|
+
# enqueue jobs") and the ones `tolerated:` grips, so a project must not need a plugin before it
|
|
85
|
+
# can write one. Rigor-owned and proposed to Steins, which holds ecosystem labels outside its
|
|
86
|
+
# builtin set and supplies them through a plugin manifest (ADR-103 WD16).
|
|
59
87
|
- telemetry
|
|
60
88
|
- email.send
|
|
61
89
|
- job.enqueue
|
|
@@ -65,6 +65,9 @@ class Object
|
|
|
65
65
|
# `acts_like?(:string)` / `acts_like?(:date)` / `acts_like?(:time)`
|
|
66
66
|
# is ActiveSupport's "duck-typing helper" predicate.
|
|
67
67
|
def acts_like?: (Symbol | String) -> bool
|
|
68
|
+
|
|
69
|
+
# `core_ext/object/inclusion` — `x.in?([a, b])` / `x.in?(1..10)`.
|
|
70
|
+
def in?: (untyped) -> bool
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
# `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
|
|
@@ -108,6 +111,9 @@ class String
|
|
|
108
111
|
def pluralize: (?Integer count, ?Symbol locale) -> String
|
|
109
112
|
def singularize: (?Symbol locale) -> String
|
|
110
113
|
def humanize: (?capitalize: bool, ?keep_id_suffix: bool) -> String
|
|
114
|
+
def titlecase: () -> String
|
|
115
|
+
def dasherize: () -> String
|
|
116
|
+
def upcase_first: () -> String
|
|
111
117
|
|
|
112
118
|
# `core_ext/string/filters`
|
|
113
119
|
def squish: () -> String
|
|
@@ -115,6 +121,8 @@ class String
|
|
|
115
121
|
def truncate: (Integer truncate_at, ?omission: String, ?separator: String | Regexp | nil) -> String
|
|
116
122
|
def truncate_bytes: (Integer truncate_at, ?omission: String) -> String
|
|
117
123
|
def truncate_words: (Integer words_count, ?omission: String, ?separator: String | Regexp | nil) -> String
|
|
124
|
+
def remove: (*Regexp | String patterns) -> String
|
|
125
|
+
def remove!: (*Regexp | String patterns) -> String
|
|
118
126
|
|
|
119
127
|
# `core_ext/string/output_safety` — `html_safe` returns an
|
|
120
128
|
# `ActiveSupport::SafeBuffer` (a String subclass). The closest
|
|
@@ -282,6 +290,8 @@ class Time
|
|
|
282
290
|
# Re-declaring it raised `RBS::DuplicatedMethodDefinitionError` and took the WHOLE `Time` definition
|
|
283
291
|
# down with it (every `Time` call degraded to `Dynamic[top]`). This overlay only ADDS what
|
|
284
292
|
# ActiveSupport adds.
|
|
293
|
+
def advance: (untyped options) -> Time
|
|
294
|
+
def all_day: () -> Range[Time]
|
|
285
295
|
def acts_like_time?: () -> true
|
|
286
296
|
end
|
|
287
297
|
|
|
@@ -323,6 +333,17 @@ class Date
|
|
|
323
333
|
def at_beginning_of_day: () -> Time
|
|
324
334
|
def end_of_day: () -> Time
|
|
325
335
|
def at_end_of_day: () -> Time
|
|
336
|
+
def advance: (untyped options) -> Date
|
|
337
|
+
def all_day: () -> Range[Time]
|
|
338
|
+
|
|
339
|
+
# `Date#to_time` is an OVERLOAD CONTINUATION (`| ...`), not a plain declaration: stdlib `date`
|
|
340
|
+
# already types `to_time: () -> Time`, and re-declaring it raises
|
|
341
|
+
# `RBS::DuplicatedMethodDefinitionError`, which collapses `Date` to `Dynamic[top]` for every project
|
|
342
|
+
# this overlay reaches — the failure the `Time#utc?` note above records, and the one #437 hit on the
|
|
343
|
+
# plugin. The row cannot simply be omitted either: ActiveSupport genuinely widens the arity, so
|
|
344
|
+
# without it `date.to_time(:utc)` — correct Rails code — draws an arity diagnostic (#449). `| ...`
|
|
345
|
+
# appends this overload ahead of the stdlib one, so both arities resolve.
|
|
346
|
+
def to_time: (?Symbol form) -> Time | ...
|
|
326
347
|
end
|
|
327
348
|
|
|
328
349
|
# ---------------------------------------------------------------
|
|
@@ -475,3 +496,15 @@ class DateTime
|
|
|
475
496
|
def end_of_minute: () -> DateTime
|
|
476
497
|
def acts_like_time?: () -> true
|
|
477
498
|
end
|
|
499
|
+
|
|
500
|
+
# ---------------------------------------------------------------
|
|
501
|
+
# ERB::Util — ActionView extends it with `html_escape_once`
|
|
502
|
+
# ---------------------------------------------------------------
|
|
503
|
+
|
|
504
|
+
# `ERB` is a CLASS in Ruby / RBS (not a module), so reopen it as a class — a `module ERB` wrapper
|
|
505
|
+
# collides with upstream `class ERB` once the `erb` stdlib is in scope. `ERB::Util` itself is a module.
|
|
506
|
+
class ERB
|
|
507
|
+
module Util
|
|
508
|
+
def self.html_escape_once: (untyped) -> String
|
|
509
|
+
end
|
|
510
|
+
end
|
|
@@ -113,7 +113,7 @@ String, Symbol, Array, and Hash. The list is not in this
|
|
|
113
113
|
handbook (it would fill several pages); see
|
|
114
114
|
[`docs/types.md`](../types.md) and the per-class catalogues
|
|
115
115
|
under
|
|
116
|
-
[`data/builtins/ruby_core/`](../../data/builtins/ruby_core
|
|
116
|
+
[`data/builtins/ruby_core/`](../../data/builtins/ruby_core).
|
|
117
117
|
|
|
118
118
|
When folding is **not** safe (because a method has side
|
|
119
119
|
effects, depends on the environment, or is not in a
|
|
@@ -275,7 +275,7 @@ the underlying RBS sig advertises.
|
|
|
275
275
|
|
|
276
276
|
If you prefer the TS spellings (`Pick<T, K>` etc.) in
|
|
277
277
|
directives, opt into the
|
|
278
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
278
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types)
|
|
279
279
|
plugin. The plugin registers a `Plugin::TypeNodeResolver` that
|
|
280
280
|
translates each TS name onto the canonical projection:
|
|
281
281
|
|
|
@@ -236,6 +236,6 @@ deprecations, …). Most projects will never write one; the
|
|
|
236
236
|
chapter exists so you know the option is there.
|
|
237
237
|
[Chapter 10 — Coexisting with Sorbet](10-sorbet.md) is for
|
|
238
238
|
projects arriving from a Sorbet codebase: the
|
|
239
|
-
[`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
239
|
+
[`rigor-sorbet`](../../plugins/rigor-sorbet) adapter reads
|
|
240
240
|
`sig { ... }` blocks, RBI files, and `T.let` / `T.cast` /
|
|
241
241
|
`T.must` / `T.unsafe` assertions as type sources.
|
data/docs/handbook/09-plugins.md
CHANGED
|
@@ -122,7 +122,7 @@ Reach for a plugin only when:
|
|
|
122
122
|
|
|
123
123
|
If those are true, [`examples/README.md`](../../examples/README.md)
|
|
124
124
|
is your starting point. The
|
|
125
|
-
[`rigor-deprecations`](../../examples/rigor-deprecations
|
|
125
|
+
[`rigor-deprecations`](../../examples/rigor-deprecations)
|
|
126
126
|
example is the smallest fully-shaped plugin — manifest +
|
|
127
127
|
single per-file walk + a couple of diagnostic emissions —
|
|
128
128
|
and is the recommended template for "I want to author my
|
|
@@ -146,7 +146,7 @@ From here:
|
|
|
146
146
|
to deeper material in
|
|
147
147
|
[`docs/type-specification/`](../type-specification/README.md),
|
|
148
148
|
[`docs/internal-spec/`](../internal-spec/README.md), and
|
|
149
|
-
[`docs/adr/`](../adr
|
|
149
|
+
[`docs/adr/`](../adr).
|
|
150
150
|
- The [`CHANGELOG.md`](../../CHANGELOG.md) is the per-release
|
|
151
151
|
truth for what shipped when.
|
|
152
152
|
|
data/docs/handbook/10-sorbet.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Coexisting with Sorbet
|
|
2
2
|
|
|
3
3
|
If your project already uses [Sorbet](https://sorbet.org/),
|
|
4
|
-
the [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
4
|
+
the [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin
|
|
5
5
|
lets Rigor read your existing `sig` blocks, RBI files, and
|
|
6
6
|
`T.let` / `T.cast` / `T.must` / `T.unsafe` assertions as type
|
|
7
7
|
sources. You do not have to rewrite anything in RBS to start
|
data/docs/handbook/README.md
CHANGED
|
@@ -66,7 +66,7 @@ up the flag, key, or command that *acts* on it.
|
|
|
66
66
|
landing page.
|
|
67
67
|
10. [**Coexisting with Sorbet**](10-sorbet.md) — for users
|
|
68
68
|
arriving from a Sorbet-using project: the
|
|
69
|
-
[`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
69
|
+
[`rigor-sorbet`](../../plugins/rigor-sorbet) adapter
|
|
70
70
|
reads `sig { ... }` blocks, RBI files, and
|
|
71
71
|
`T.let` / `T.cast` / `T.must` / `T.unsafe` assertions
|
|
72
72
|
as type sources without rewriting in RBS.
|
|
@@ -255,7 +255,7 @@ ADRs:
|
|
|
255
255
|
- [`docs/internal-spec/`](../internal-spec/README.md) —
|
|
256
256
|
analyzer-internal contracts (engine surface, type-object
|
|
257
257
|
public API).
|
|
258
|
-
- [`docs/adr/`](../adr
|
|
258
|
+
- [`docs/adr/`](../adr) — architecture decision records.
|
|
259
259
|
|
|
260
260
|
## Non-goals
|
|
261
261
|
|
|
@@ -105,7 +105,7 @@ type switches. Rigor has direct analogues.
|
|
|
105
105
|
| `if x != nil` | `if x` (strips `nil`), or `unless x.nil?` |
|
|
106
106
|
| `v, ok := x.(string)` (comma-ok assertion) | `x.is_a?(String)` narrowing in an `if` |
|
|
107
107
|
| `switch v := x.(type) { case string: … }` | `case x; in String => v` |
|
|
108
|
-
| `x.(T)` (assertion, panics on fail) | (no panicking assertion) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
108
|
+
| `x.(T)` (assertion, panics on fail) | (no panicking assertion) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet) |
|
|
109
109
|
| user func returning `bool` | `%a{rigor:v1:predicate-if-true x is Foo}` directive |
|
|
110
110
|
|
|
111
111
|
Go's type switch is *not* exhaustive — you can omit cases and
|
|
@@ -58,7 +58,7 @@ advisor that only speaks when it is sure.
|
|
|
58
58
|
| `Set<T>` | `HashSet<T>` / `ISet<T>` | `Set[T]` | |
|
|
59
59
|
| `record Point(int x, int y)` | `record Point(int X, int Y)` | `Point = Data.define(:x, :y)` | See [Records ↔ Data.define](#records--datadefine). |
|
|
60
60
|
| `Optional<T>` | `T?` (nullable reference type) | `T?` (i.e. `T \| nil`) | Java models it as a *container*; C# as a *type modifier*. See [Nullability](#nullability). |
|
|
61
|
-
| `enum Color { RED, GREEN }` | `enum Color { Red, Green }` | `Constant<:red> \| Constant<:green>` (Symbol union) | Ruby has no native enum; the [`rigor-mangrove`](../../plugins
|
|
61
|
+
| `enum Color { RED, GREEN }` | `enum Color { Red, Green }` | `Constant<:red> \| Constant<:green>` (Symbol union) | Ruby has no native enum; the [`rigor-mangrove`](../../plugins) plugin types richer enum DSLs. |
|
|
62
62
|
| `sealed interface Shape permits …` | `abstract` base + sealed hierarchy | union of the subtypes | See [Sealed types & exhaustiveness](#sealed-types-and-exhaustiveness). |
|
|
63
63
|
| `<T>` (generic) | `<T>` (generic) | RBS `[T]` type parameter | |
|
|
64
64
|
| `? extends T` (use-site) | `out T` (declaration-site) | covariant type parameter | See [Generics & variance](#generics-and-variance). |
|
|
@@ -124,7 +124,7 @@ behaviour matches.
|
|
|
124
124
|
| `x instanceof String` | `x is string` | `x.is_a?(String)` |
|
|
125
125
|
| `x instanceof String s` (binding) | `x is string s` (binding) | `case x; in String => s` |
|
|
126
126
|
| `switch (x) { case Foo f -> … }` | `switch (x) { case Foo f => … }` | `case x; in Foo => f` |
|
|
127
|
-
| `(Foo) x` (cast) | `(Foo)x` (cast) | (no in-source cast) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
127
|
+
| `(Foo) x` (cast) | `(Foo)x` (cast) | (no in-source cast) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet) |
|
|
128
128
|
| `Objects.requireNonNull(x)` | `x!` (null-forgiving) | (no in-source assertion) — `unless x.nil?`, or `T.must` via `rigor-sorbet` |
|
|
129
129
|
| user method returning `boolean` | user method returning `bool` | `%a{rigor:v1:predicate-if-true x is Foo}` directive on the predicate |
|
|
130
130
|
|
|
@@ -296,7 +296,7 @@ draws the distinction in full.
|
|
|
296
296
|
Pyright's "type alias narrowing" and mypy's overload stacks
|
|
297
297
|
cover some cases; Rigor's plugin contract gives you full
|
|
298
298
|
Ruby code at the dispatch point. The
|
|
299
|
-
[`rigor-lisp-eval`](../../examples/rigor-lisp-eval
|
|
299
|
+
[`rigor-lisp-eval`](../../examples/rigor-lisp-eval) example
|
|
300
300
|
is the canonical demo — `Lisp.eval([:+, 1, 2])` returns
|
|
301
301
|
`Integer`, `Lisp.eval([:<, 1, 2])` returns `bool`.
|
|
302
302
|
|
|
@@ -228,8 +228,8 @@ author):
|
|
|
228
228
|
- The `missing-protocol-method` / `protocol-return-mismatch`
|
|
229
229
|
diagnostics are **plugin diagnostics**, emitted under the
|
|
230
230
|
plugin's `plugin.<id>.` provenance — not core Rigor rules. The
|
|
231
|
-
worked references are [`examples/rigor-web/`](../../examples/rigor-web
|
|
232
|
-
(the minimal tutorial) and [`plugins/rigor-hanami/`](../../plugins/rigor-hanami
|
|
231
|
+
worked references are [`examples/rigor-web/`](../../examples/rigor-web)
|
|
232
|
+
(the minimal tutorial) and [`plugins/rigor-hanami/`](../../plugins/rigor-hanami)
|
|
233
233
|
(production Hanami 2 actions).
|
|
234
234
|
|
|
235
235
|
## Interface vs protocol contract
|
|
@@ -91,7 +91,7 @@ The reflex to drop is `unwrap()`. In Rust you reach for
|
|
|
91
91
|
`.unwrap()` / `.expect()` when you *know* it is `Some`. Rigor
|
|
92
92
|
has no in-source assertion that lies to the checker; the
|
|
93
93
|
equivalents are a `nil?` guard (checked, not asserted) or
|
|
94
|
-
`T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
94
|
+
`T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet)
|
|
95
95
|
plugin (see [Chapter 10](10-sorbet.md)).
|
|
96
96
|
|
|
97
97
|
**`Result<T, E>` ↔ exceptions.** Here the models diverge. Ruby
|
|
@@ -709,7 +709,7 @@ Rigor maps onto this as:
|
|
|
709
709
|
| Dynamic type `?` | **`Dynamic[T]`** — a carrier that *wraps* a "best-guess" type `T` while marking the value as not-statically-verified. `Dynamic[top]` is the maximally-dynamic form. |
|
|
710
710
|
| Consistency `~` | The `maybe` arm of the trinary certainty — `Dynamic[T] ~ U` holds whenever `T ~ U` does. |
|
|
711
711
|
| Static/dynamic boundary | Per-method, per-file, per-plugin contribution — Rigor records *why* a value became `Dynamic[T]` in its dynamic-origin algebra. |
|
|
712
|
-
| Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
712
|
+
| Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin reads `T.let` / `T.cast` / `T.must` as cast forms; `RBS::Extended` `assert_type` directives serve the same role from `.rbs`. |
|
|
713
713
|
|
|
714
714
|
Two Rigor-specific extensions matter:
|
|
715
715
|
|
|
@@ -794,7 +794,7 @@ against:
|
|
|
794
794
|
### What Rigor explicitly does NOT do
|
|
795
795
|
|
|
796
796
|
- **Runtime contract insertion at the static / dynamic boundary.**
|
|
797
|
-
The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
797
|
+
The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin
|
|
798
798
|
reads Sorbet's `T.let` / `T.cast` / `T.must` as cast forms, but
|
|
799
799
|
the contract *enforcement* is `sorbet-runtime`'s job, not
|
|
800
800
|
Rigor's. Rigor's static analysis uses the cast as a hint, not
|
|
@@ -51,7 +51,7 @@ inference cannot see further).
|
|
|
51
51
|
| `{ name: string; age: number }` | `HashShape{name: String, age: Integer}` | Same per-key model; Ruby uses Symbol keys idiomatically. |
|
|
52
52
|
| `Array<T>` / `T[]` | `Array[T]` | Same. |
|
|
53
53
|
| `Record<K, V>` | `Hash[K, V]` | Same. |
|
|
54
|
-
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
54
|
+
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
|
|
55
55
|
| `Partial<T>` / `Required<T>` | `partial_of[T]` / `required_of[T]` (same plugin) | Flips every entry's required-ness on a `HashShape`. `Partial` does NOT widen value types to `nil` — Rigor's `HashShape` distinguishes "key absent" from "key present with nil value" (ADR-13 WD on required-ness flips). |
|
|
56
56
|
| `Pick<T, K>` / `Omit<T, K>` | `pick_of[T, K]` / `omit_of[T, K]` (same plugin) | Restrict / remove `HashShape` entries by literal-key union; Tuple receivers project by integer index. Non-shape carriers degrade conservatively and surface `dynamic.shape.lossy-projection`. |
|
|
57
57
|
| Conditional types `T extends U ? A : B` | (none in core; plugin contributions) | A plugin can vary return type by argument shape. |
|
|
@@ -159,7 +159,7 @@ inferred call-site instantiation as routinely as TypeScript.
|
|
|
159
159
|
| `Array<T>` | `Array[T]` |
|
|
160
160
|
| `Map<K, V>` | `Hash[K, V]` |
|
|
161
161
|
| `Promise<T>` | (no analogue — Ruby has no built-in Promise) |
|
|
162
|
-
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
162
|
+
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
|
|
163
163
|
| Conditional types | (no analogue — would need a plugin) |
|
|
164
164
|
|
|
165
165
|
Rigor reads RBS generics through its dispatcher and instantiates
|
|
@@ -214,7 +214,7 @@ Be honest about what you give up:
|
|
|
214
214
|
variation, not type-level expressions.
|
|
215
215
|
- **Mapped types.** `Pick`, `Omit`, `Partial`, `Required`, and
|
|
216
216
|
`Readonly` ship as opt-in plugin-supplied vocabulary via
|
|
217
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
217
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types),
|
|
218
218
|
which maps them onto the Rigor-canonical `pick_of` / `omit_of`
|
|
219
219
|
/ `partial_of` / `required_of` / `readonly_of` shape-projection
|
|
220
220
|
type functions on `HashShape` (and `pick_of` / `omit_of` on
|
|
@@ -294,7 +294,7 @@ def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
|
|
|
294
294
|
|
|
295
295
|
The RBS sig stays generic. If you want `Pick<T, K>`'s exact-
|
|
296
296
|
key-set tracking back, opt into the
|
|
297
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
297
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types)
|
|
298
298
|
plugin and annotate the return type with the `Pick` spelling:
|
|
299
299
|
|
|
300
300
|
```rbs
|
|
@@ -38,7 +38,7 @@ the `paths:` list from the configuration file.
|
|
|
38
38
|
| `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. See [Caching](12-caching.md). |
|
|
39
39
|
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics are byte-identical, then exit (0 on match, 1 with the differing diagnostics on mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
|
|
40
40
|
| `--clear-cache` | Delete the cache directory before running. |
|
|
41
|
-
| `--cache-stats` | Print the on-disk cache inventory when finished. |
|
|
41
|
+
| `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
|
|
42
42
|
| `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
|
|
43
43
|
| `--coverage` | Add a type-precision coverage block to the output (`coverage` object under `--format json`; a one-line summary in text mode). Off by default — it is a second precision pass over the analyzed files, the same scan [`rigor coverage`](#rigor-coverage) runs, so it is opt-in. |
|
|
44
44
|
| `--workers=N` | Dispatch analysis across `N` parallel worker processes (fork-based pool today; ADR-15). Default `0` (sequential). Applies to `--incremental` re-checks as well as full runs. |
|
|
@@ -169,7 +169,10 @@ Report what each method *does* — its effect labels — rather
|
|
|
169
169
|
than what it returns, and manage the committed **effect
|
|
170
170
|
snapshot** that gates drift. Opt-in and observational: nothing
|
|
171
171
|
here emits a diagnostic or changes `rigor check`'s output, and
|
|
172
|
-
only `rigor effects check` ever exits non-zero.
|
|
172
|
+
only `rigor effects check` ever exits non-zero. The workflow
|
|
173
|
+
around these verbs — the label vocabulary, how to read the
|
|
174
|
+
report at scale, the review loop and the CI step — is
|
|
175
|
+
[Effect labels](19-effect-labels.md).
|
|
173
176
|
|
|
174
177
|
```sh
|
|
175
178
|
rigor effects [PATH...] # the report
|
|
@@ -183,6 +186,13 @@ configuring anything; such an ad-hoc run shares no cache with
|
|
|
183
186
|
`rigor check`, because a run served from that cache would have
|
|
184
187
|
collected nothing.
|
|
185
188
|
|
|
189
|
+
Passing paths narrows the **analysis**, not just the output.
|
|
190
|
+
Labels are transitive over what was analysed, so a method whose
|
|
191
|
+
callees were not analysed reports fewer labels and a wider
|
|
192
|
+
"possibly more" than the same method in a whole-project run —
|
|
193
|
+
honest, but not a cheaper way to get the whole-project answer.
|
|
194
|
+
Filter the output when you want the real footprint.
|
|
195
|
+
|
|
186
196
|
Each line is one method, sorted by key:
|
|
187
197
|
|
|
188
198
|
```
|
|
@@ -199,10 +209,13 @@ lines say why. A taint is never a finding.
|
|
|
199
209
|
|
|
200
210
|
A ` ≤ [...]` clause after the labels is the **declared** lane:
|
|
201
211
|
what a source Rigor trusts but did not verify *claims* the
|
|
202
|
-
method does
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
212
|
+
method does. Two sources feed it — the plugins you activated,
|
|
213
|
+
which on a framework application contribute most of it
|
|
214
|
+
(`rails.session.write` and the rest of `rails.*` are Action
|
|
215
|
+
Pack's rows, not yours), and the `effects.attribution:` table
|
|
216
|
+
you wrote for gem methods nothing models. It is printed apart
|
|
217
|
+
from the proven labels and never folded in among them, because
|
|
218
|
+
the two answer different questions. It follows call edges exactly as
|
|
206
219
|
the proven labels do, so a controller two hops above an
|
|
207
220
|
attributed gem call carries the claim rather than only a
|
|
208
221
|
"possibly more"; a declared label the proven list already
|
|
@@ -213,10 +226,30 @@ Gateways::Client#fetch: [] ≤ [io.net.http] …?
|
|
|
213
226
|
plugin-attribution (Acme::Http.get)
|
|
214
227
|
```
|
|
215
228
|
|
|
216
|
-
|
|
217
|
-
beyond `mutate.local` — mutation of objects
|
|
218
|
-
allocated and never let out, which every effect
|
|
219
|
-
tolerates
|
|
229
|
+
Two kinds of method are omitted: one that proves nothing
|
|
230
|
+
beyond `mutate.local` and claims nothing — mutation of objects
|
|
231
|
+
its own frame allocated and never let out, which every effect
|
|
232
|
+
envelope tolerates — and one with no label in either lane,
|
|
233
|
+
which exists only to record that something below it was
|
|
234
|
+
unresolved. `--full` lists both. `--pure` asks for the first
|
|
235
|
+
group by name, which is the set worth annotating `%a{pure}`.
|
|
236
|
+
|
|
237
|
+
`--label=LABEL` prints only the methods carrying `LABEL` or a
|
|
238
|
+
label under it, in either lane. `--limit=N` caps the rows.
|
|
239
|
+
`--why` expands each row's unresolved reasons and the plugin
|
|
240
|
+
row behind each declared label, which are collapsed to a count
|
|
241
|
+
by default. The report closes with a footer counting the two
|
|
242
|
+
lanes apart, because they have different powers: a proven label
|
|
243
|
+
can fail a build and a declared one cannot
|
|
244
|
+
([ADR-103](../adr/103-effect-labels.md) § WD17).
|
|
245
|
+
|
|
246
|
+
A `PATH` argument selects which methods are **printed**, never
|
|
247
|
+
which are analysed: Rigor analyses your configured `paths:`
|
|
248
|
+
either way, so a selected row carries exactly the labels the
|
|
249
|
+
whole-project run gives it, and a note on stderr says how many
|
|
250
|
+
of how many you are looking at. A path outside `paths:` is
|
|
251
|
+
analysed as well as them, so pointing the command at a tree
|
|
252
|
+
your configuration does not cover still works.
|
|
220
253
|
|
|
221
254
|
`--format=text|json` selects the output format; the JSON
|
|
222
255
|
payload additionally carries each method's *direct* summary
|
|
@@ -226,9 +259,15 @@ subcommands and does nothing here: the report is an
|
|
|
226
259
|
observation, and observations are undischarged.
|
|
227
260
|
|
|
228
261
|
What is collected and how it propagates is
|
|
229
|
-
[the effect-summaries internal spec](../internal-spec/effect-summaries.md)
|
|
230
|
-
the
|
|
231
|
-
|
|
262
|
+
[the effect-summaries internal spec](../internal-spec/effect-summaries.md).
|
|
263
|
+
`--list-labels` prints the vocabulary this project can name —
|
|
264
|
+
every shipped label with its root's meaning, plus whatever your
|
|
265
|
+
plugins and your `effects.labels:` opened — and exits without
|
|
266
|
+
analysing anything. The same table is in
|
|
267
|
+
[Effect labels § The label vocabulary](19-effect-labels.md), and
|
|
268
|
+
the grammar is specified normatively in the effect-labels
|
|
269
|
+
specification, which the gem does not ship:
|
|
270
|
+
<https://rigor.typedduck.fail/type-specification/effect-labels/>.
|
|
232
271
|
|
|
233
272
|
### The effect snapshot
|
|
234
273
|
|
|
@@ -265,8 +304,8 @@ supposed to fan out — the fan-out is the blast radius.
|
|
|
265
304
|
|
|
266
305
|
```yaml
|
|
267
306
|
# .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
|
|
268
|
-
schema:
|
|
269
|
-
rigor: "0.3.
|
|
307
|
+
schema: 2
|
|
308
|
+
rigor: "0.3.5"
|
|
270
309
|
vocabulary: 1
|
|
271
310
|
config_digest: "9ec82bfc…"
|
|
272
311
|
methods:
|
|
@@ -275,7 +314,7 @@ methods:
|
|
|
275
314
|
"Reports::Nightly#perform":
|
|
276
315
|
effects: ["io.db.read"]
|
|
277
316
|
exhaustive: false
|
|
278
|
-
unresolved:
|
|
317
|
+
unresolved: 1
|
|
279
318
|
reach:
|
|
280
319
|
"OrdersController#create":
|
|
281
320
|
effects: ["io.db.read", "io.net.http", "job.enqueue"]
|
|
@@ -337,14 +376,17 @@ spelled out:
|
|
|
337
376
|
Effect drift against .rigor-effects.yml:
|
|
338
377
|
|
|
339
378
|
methods:
|
|
340
|
-
PaymentGateway#charge + io.net.http
|
|
379
|
+
PaymentGateway#charge + io.net.http (app/services/payment_gateway.rb:18)
|
|
341
380
|
|
|
342
381
|
reach:
|
|
343
|
-
OrdersController#create + io.net.http
|
|
382
|
+
OrdersController#create + io.net.http (app/controllers/orders_controller.rb:7)
|
|
344
383
|
|
|
345
|
-
Run `rigor effects
|
|
384
|
+
Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
|
|
346
385
|
```
|
|
347
386
|
|
|
387
|
+
Each row names where the method is defined, so the reviewer reads the report
|
|
388
|
+
rather than searching for the method.
|
|
389
|
+
|
|
348
390
|
The author runs `rigor effects explain` to see the route —
|
|
349
391
|
|
|
350
392
|
```
|
|
@@ -517,7 +559,7 @@ hand-adjudicated corpus target only **7% of the rows were genuinely
|
|
|
517
559
|
unused**; the rest were reachable by means static analysis cannot
|
|
518
560
|
see. That is why this is a separate command and never a `rigor check`
|
|
519
561
|
diagnostic — see
|
|
520
|
-
[ADR-102](
|
|
562
|
+
[ADR-102](../adr/102-unused-code-reachability-report.md).
|
|
521
563
|
|
|
522
564
|
Reachability is computed from **roots**, not by counting references,
|
|
523
565
|
so a cluster of classes that only reference each other is still
|
|
@@ -1053,9 +1095,10 @@ operational knobs read the environment instead.
|
|
|
1053
1095
|
| `RIGOR_CI_DETECT=0` | Turn off CI auto-detection — the same as `--no-ci-detect`. See [Running Rigor in CI § auto-detection](11-ci.md). |
|
|
1054
1096
|
| `RIGOR_RACTOR_WORKERS=N` | Worker count for parallel analysis. Sits between the CLI flag and the config key in precedence: `--workers=N` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > `0` (sequential). |
|
|
1055
1097
|
| `RIGOR_POOL_BACKEND=ractor` | Opt back into the (off-by-default) Ractor worker pool instead of the active fork-based pool ([ADR-15](../adr/15-ractor-concurrency.md)). Only relevant with a non-zero worker count; the fork pool is the supported backend. |
|
|
1098
|
+
| `RIGOR_LSP_POOL_MIN_BATCH=N` | Fewest buffers an [`rigor lsp`](#rigor-lsp) batch must carry before analysis is dispatched across the worker pool rather than run in-process (default `16`). Lower it if your project's per-file analysis is expensive enough that pooling pays off sooner. |
|
|
1056
1099
|
| `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
|
|
1057
1100
|
| `RIGOR_STRICT_VALIDATION=1` | Force full-content cache validation for one run (the same as `cache.validation: digest`, and winning over it) — re-hash every file's content instead of trusting its stat metadata. Use it if a filesystem's timestamps or inode numbers cannot be trusted. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). |
|
|
1058
|
-
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through
|
|
1101
|
+
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through any long run so short runs never pay the JIT warm-up; this variable leaves it off entirely. Diagnostics and allocations are identical either way — the effect is wall-time only. |
|
|
1059
1102
|
| `RIGOR_YJIT_DEADLINE=<seconds>` | Advanced: tune how long a run must last before deferred YJIT enables (default `5.0`). Lower it if your runs are long and you want the JIT sooner; raise it to protect short runs. Ignored when `RIGOR_DISABLE_YJIT=1` is set or YJIT is unavailable. |
|
|
1060
1103
|
|
|
1061
1104
|
Three further variables (`RIGOR_BUDGET_TRACE`,
|
|
@@ -1069,7 +1112,15 @@ diagnostics about Rigor's own inference cutoffs and memory — see
|
|
|
1069
1112
|
| --- | --- |
|
|
1070
1113
|
| `0` | Success — no error-severity diagnostics. |
|
|
1071
1114
|
| `1` | Diagnostics found, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
|
|
1072
|
-
| `64` | Usage error — unknown command, bad flag, malformed argument. |
|
|
1115
|
+
| `64` | Usage error — unknown command, bad flag, malformed argument, or a value in `.rigor.yml` the loader cannot proceed on. |
|
|
1073
1116
|
|
|
1074
1117
|
`rigor triage` is the exception: it is advisory and always
|
|
1075
1118
|
exits `0`.
|
|
1119
|
+
|
|
1120
|
+
A configuration mistake prints one `rigor:` line naming the key
|
|
1121
|
+
and nothing else — no backtrace, on every command:
|
|
1122
|
+
|
|
1123
|
+
```
|
|
1124
|
+
$ rigor effects update
|
|
1125
|
+
rigor: effects.attribution key is not a method key (`Owner#method` / `Owner.method`): "Net::HTTP get"
|
|
1126
|
+
```
|