rigortype 0.4.3 → 0.4.5
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/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +104 -15
- data/docs/manual/02-cli-reference.md +43 -12
- data/docs/manual/03-configuration.md +20 -7
- data/docs/manual/04-diagnostics.md +56 -3
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +3 -2
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +40 -4
- data/docs/manual/18-removing-dead-code.md +23 -2
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-rspec.md +101 -1
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +60 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
- data/lib/rigor/analysis/check_rules.rb +274 -51
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +61 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +270 -28
- data/lib/rigor/analysis/path_expansion.rb +3 -1
- data/lib/rigor/analysis/reachability/graph.rb +111 -14
- data/lib/rigor/analysis/reachability/scan.rb +305 -23
- data/lib/rigor/analysis/rule_catalog.rb +22 -4
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +5 -3
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
- data/lib/rigor/analysis/runner/pool_coordinator.rb +179 -31
- data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
- data/lib/rigor/analysis/runner.rb +102 -72
- data/lib/rigor/analysis/severity_stamp.rb +45 -1
- data/lib/rigor/analysis/worker_session.rb +69 -2
- data/lib/rigor/cache/descriptor.rb +79 -12
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +39 -5
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +32 -11
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +163 -22
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +29 -3
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +67 -11
- data/lib/rigor/inference/block_call_timing.rb +12 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/captured_locals.rb +67 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/definer_resolution.rb +101 -25
- data/lib/rigor/inference/expression_typer.rb +94 -9
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher.rb +67 -3
- data/lib/rigor/inference/module_function_state.rb +213 -51
- data/lib/rigor/inference/narrowing.rb +235 -75
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +120 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +414 -1
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer.rb +1061 -122
- data/lib/rigor/inference/self_call_ivar_writes.rb +346 -0
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +512 -150
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/lens/anchor.rb +3 -3
- data/lib/rigor/lens/contract_reader.rb +60 -2
- data/lib/rigor/lens/document.rb +4 -3
- data/lib/rigor/lens/file_reader.rb +452 -73
- data/lib/rigor/lens/members.rb +81 -17
- data/lib/rigor/lens/run.rb +73 -4
- data/lib/rigor/lens.rb +2 -1
- data/lib/rigor/mcp/server.rb +63 -22
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/project_environment.rb +6 -3
- data/lib/rigor/protection/discovery_seed.rb +9 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +27 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +56 -8
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +167 -22
- data/lib/rigor/sig_gen/layout_index.rb +40 -1
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/observation_collector.rb +137 -11
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/renderer.rb +13 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +50 -35
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/constant.rb +10 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +57 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +4 -3
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4da9b991a97cd586806b416facb6dbf6c3a3b938c7c9433e330bc44c0757fdaf
|
|
4
|
+
data.tar.gz: 1167e8d91ccbc8740ac4094f67db82937bd3b374d2488a43e514f7922c7a98b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db2047e49879c399812704702d4064304bf4ac9be8fe25132cc65ac441e2962f7b8c45cf59371727dffaf52caab9accbfb7c8ea0abe1976f264a7a4506e46e6f
|
|
7
|
+
data.tar.gz: e426da15aeb714ab906df710946ae3b2c96b35509632b9c35efba289d303da519f58e3cd362a8b47c4ba8a9f883e54e657edeb3e09c1934b4cd542f119440678
|
data/README.md
CHANGED
|
@@ -106,7 +106,7 @@ https://raw.githubusercontent.com/rigortype/rigor/refs/heads/master/docs/install
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
Ready-made prompts — 日本語, 简体中文, 繁體中文, 한국어, Español,
|
|
109
|
-
Português, Français, Deutsch, Italiano, Tiếng Việt, ภาษาไทย,
|
|
109
|
+
Português (Brasil), Português, Français, Deutsch, Italiano, Tiếng Việt, ภาษาไทย,
|
|
110
110
|
Bahasa Indonesia, Polski, Українська, Русский, Română, Türkçe,
|
|
111
111
|
العربية, فارسی — are in
|
|
112
112
|
the [installation guide](https://rigor.typedduck.fail/manual/01-installation/#set-up-in-your-language).
|
|
@@ -232,7 +232,7 @@ rigor docs --list # list every bundled page
|
|
|
232
232
|
|
|
233
233
|
## Status
|
|
234
234
|
|
|
235
|
-
Current release: **`v0.4.
|
|
235
|
+
Current release: **`v0.4.5`** (2026-10-19) — on the
|
|
236
236
|
`0.4.x` line, continuing the evaluation era opened by `v0.2.0`, the first
|
|
237
237
|
publicly-announced (general / evaluation) release. The line publishes
|
|
238
238
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
|
@@ -256,9 +256,9 @@ Mozilla Public License Version 2.0. See [`LICENSE`](LICENSE).
|
|
|
256
256
|
|
|
257
257
|
## Sponsors
|
|
258
258
|
|
|
259
|
-
Rigor is built and maintained by one person, who has
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
Rigor is built and maintained by one person, who has put nearly every
|
|
260
|
+
spare hour of personal time into it since development began in April
|
|
261
|
+
2026. Sustainable development depends on sustainable funding.
|
|
262
262
|
|
|
263
263
|
**The current monthly budget is well short of the $200 minimum needed
|
|
264
264
|
to keep this pace going; $600 or more would let me work on it
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures.
|
|
2
|
+
#
|
|
3
|
+
# --- OpenStruct#method_missing (issue #1456) ---
|
|
4
|
+
#
|
|
5
|
+
# rbs 4.2 ships no `ostruct` signatures (since Ruby 4.0 `ostruct` is a bundled
|
|
6
|
+
# gem, not a default one), and the gem ships no `sig/`. The only `OpenStruct`
|
|
7
|
+
# declaration in the environment is json's add-on reopening in
|
|
8
|
+
# `stdlib/json/0/json.rbs` (`json_create` / `as_json` / `to_json`), so the
|
|
9
|
+
# class read as fully declared and `OpenStruct.new(name: "x").name` reported
|
|
10
|
+
# `call.undefined-method`.
|
|
11
|
+
#
|
|
12
|
+
# Upstream: ostruct 0.6.3 `lib/ostruct.rb` defines each field as a
|
|
13
|
+
# per-instance singleton method (`new_ostruct_member!` through
|
|
14
|
+
# `define_singleton_method!`) and answers the rest through
|
|
15
|
+
# `private def method_missing(mid, *args)`, which this file declares. It
|
|
16
|
+
# defines no `respond_to_missing?` of its own (Kernel's answers, and the
|
|
17
|
+
# fields are real singleton methods), so none is declared here.
|
|
18
|
+
#
|
|
19
|
+
# The per-field accessors cannot be declared on the class at all, so
|
|
20
|
+
# `Rigor::Analysis::CheckRules::CORE_OVERLAY_OPEN_RECEIVERS` names
|
|
21
|
+
# `OpenStruct` as an open receiver: an RBS `method_missing` alone does not
|
|
22
|
+
# stop the undefined-method rule. That list is what quiets `rigor check`;
|
|
23
|
+
# this file supplies the declaration the maintainer decision asked for.
|
|
24
|
+
#
|
|
25
|
+
# The declaration sits on a module the class includes, as `process.rbs`
|
|
26
|
+
# does, so a project `sig/` that declares `OpenStruct#method_missing` itself
|
|
27
|
+
# overrides it instead of raising `DuplicatedMethodDefinitionError`.
|
|
28
|
+
#
|
|
29
|
+
# Maintainer decision on #1456 (2026-10-16, option c): this narrow overlay.
|
|
30
|
+
# The general rule for a class declared only by another library's partial
|
|
31
|
+
# reopening is #1978.
|
|
32
|
+
module RigorCoreOverlay
|
|
33
|
+
module OpenStructMissing
|
|
34
|
+
private def method_missing: (Symbol mid, *untyped args) -> untyped
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class OpenStruct
|
|
39
|
+
include RigorCoreOverlay::OpenStructMissing
|
|
40
|
+
end
|
data/docs/handbook/11-sig-gen.md
CHANGED
|
@@ -165,6 +165,11 @@ The `sig.skipped.*` reasons are:
|
|
|
165
165
|
RBS for it, so `--write` and `--check` are unaffected. Its
|
|
166
166
|
JSON row has no `class` key. `def self.x`, a `def` inside a
|
|
167
167
|
block body and one inside `class << obj` are not reported.
|
|
168
|
+
- `sig.skipped.uncertain-module-function` — a bare `public`,
|
|
169
|
+
`private` or `protected` that may not run (under an `if`, in
|
|
170
|
+
a block) sits between a bare `module_function` and the
|
|
171
|
+
method, so it is either a module function or an instance
|
|
172
|
+
method only. sig-gen writes neither.
|
|
168
173
|
- `sig.skipped.unrenderable-rbs` — the signature Rigor
|
|
169
174
|
rendered for this method does not parse as RBS. This one
|
|
170
175
|
is a **bug in Rigor**, not a property of your code: every
|
|
@@ -527,7 +532,13 @@ A declared `untyped` that you wrote in `sig/` is treated
|
|
|
527
532
|
the same way for a value literal: an Integer, String or
|
|
528
533
|
Symbol the body proves is widened to its class (`-> 1`
|
|
529
534
|
becomes `-> Integer`, `-> ("S" | nil)` becomes
|
|
530
|
-
`-> (String | nil)`),
|
|
535
|
+
`-> (String | nil)`), and `true` or `false` is widened to
|
|
536
|
+
`bool` (written `(false | true)`), so a subclass that
|
|
537
|
+
overrides the method with the other one is not reported as
|
|
538
|
+
widening the return. A body that returns only `nil` keeps
|
|
539
|
+
the `untyped`: it is the shape of a template method a
|
|
540
|
+
subclass fills in, and `-> nil` would make every override
|
|
541
|
+
that returns a value a widening. `nil` inside a union and a
|
|
531
542
|
computed `Float` are proposed as they are. An `untyped` that
|
|
532
543
|
only the inline reader synthesizes for a `def` you did not
|
|
533
544
|
annotate keeps the literal, as does a method no `.rbs`
|
|
@@ -590,9 +601,16 @@ list is the current state):
|
|
|
590
601
|
`attr_writer` / `attr_accessor` ivar reads untyped.
|
|
591
602
|
The accessor, and any other method returning that
|
|
592
603
|
ivar, is skipped even beside a concrete write such as
|
|
593
|
-
`@logger = Logger.new
|
|
594
|
-
|
|
595
|
-
from.
|
|
604
|
+
`@logger = Logger.new`. `--params=observed` types an
|
|
605
|
+
`attr_reader` from the constructor parameter its ivar
|
|
606
|
+
is assigned from (`Box.new(0)` gives
|
|
607
|
+
`def count: () -> 0`) only when that assignment is the
|
|
608
|
+
ivar's only write in the class's body in that file: an
|
|
609
|
+
attr writer, a hand-written setter, or a write in any
|
|
610
|
+
other instance method leaves both reader and writer
|
|
611
|
+
skipped. A writer declared in another file, in a
|
|
612
|
+
superclass or module, or through `class_eval` from
|
|
613
|
+
outside is not seen. The
|
|
596
614
|
generator emits the long-form `def name: () -> T`
|
|
597
615
|
spelling so the writer's merge path applies unchanged;
|
|
598
616
|
existing short-form `attr_reader name: T` declarations
|
|
@@ -616,9 +634,28 @@ two are wired today, one is reserved.
|
|
|
616
634
|
| Policy | Behaviour |
|
|
617
635
|
| --- | --- |
|
|
618
636
|
| `untyped` (default) | Every parameter is spelled `untyped`. No inference-derived parameter contract is imposed on future callers. The user retains complete authorship over parameter typing. |
|
|
619
|
-
| `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. With no test root to observe, or a declared root that does not exist, sig-gen says so on stderr. |
|
|
637
|
+
| `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. With no test root to observe, or a declared root that does not exist or is not read, sig-gen says so on stderr, and `--format=json` carries the same fact in a top-level `observation` key (below). |
|
|
620
638
|
| `observed-strict` | Reserved. Will additionally widen to capability roles (`_ToStr`, `_ToS`, …) once the role catalog ships. Currently rejected with a usage error. |
|
|
621
639
|
|
|
640
|
+
Under `--params=observed`, the `--print` / `--diff` JSON payload gains
|
|
641
|
+
a top-level `observation` key next to `candidates`, so a consumer that
|
|
642
|
+
never reads stderr (the MCP `rigor_sig_gen` tool included) can tell
|
|
643
|
+
"no test root was observed" from "the tests pass no typed arguments":
|
|
644
|
+
|
|
645
|
+
```json
|
|
646
|
+
{ "candidates": [ … ],
|
|
647
|
+
"observation": { "paths": ["/app/spec"], "missing": ["/app/tests"] } }
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
`paths` are the roots the collector reads (a directory or a `.rb`
|
|
651
|
+
file); `missing` are the other declared roots: ones that do not exist,
|
|
652
|
+
or exist but are not read (`README.md`). An identical repeat is listed once. `paths: []` with `missing: []` means
|
|
653
|
+
there was no test root at all. Paths are spelled as the run resolved
|
|
654
|
+
them: `--observe=PATH` as typed, `test_paths:` as absolute paths, resolved against the
|
|
655
|
+
config file's directory, auto-detected `spec/` / `test/` absolute. The key is
|
|
656
|
+
absent under `--params=untyped`, and absent from the `--write` and
|
|
657
|
+
`--check` JSON payloads, which have their own shapes.
|
|
658
|
+
|
|
622
659
|
The default deliberately favours `untyped` because of
|
|
623
660
|
[ADR-5](../adr/5-robustness-principle.md)'s clause 2: a
|
|
624
661
|
method's parameter contract should be the **most permissive**
|
|
@@ -704,7 +741,7 @@ output it got before.
|
|
|
704
741
|
## RSpec-aware observations
|
|
705
742
|
|
|
706
743
|
When the observed test roots hold an RSpec suite, the
|
|
707
|
-
generator recognises
|
|
744
|
+
generator recognises four RSpec-shaped binding patterns
|
|
708
745
|
and uses them to type receivers that would otherwise
|
|
709
746
|
degrade to `Dynamic[top]`:
|
|
710
747
|
|
|
@@ -712,10 +749,12 @@ degrade to `Dynamic[top]`:
|
|
|
712
749
|
RSpec.describe Calc do
|
|
713
750
|
subject { Calc.new } # binds :subject → Nominal[Calc]
|
|
714
751
|
let(:other) { Calc.new } # binds :other → Nominal[Calc]
|
|
752
|
+
before { @calc = Calc.new } # binds @calc → Nominal[Calc]
|
|
715
753
|
|
|
716
754
|
it "..." do
|
|
717
755
|
subject.greet("Alice") # observed: Calc#greet receives String
|
|
718
756
|
other.greet("Bob") # observed: same
|
|
757
|
+
@calc.greet("Carol") # observed: same
|
|
719
758
|
described_class.new.add(1, 2) # observed: Calc#add receives Integer, Integer
|
|
720
759
|
end
|
|
721
760
|
end
|
|
@@ -724,17 +763,36 @@ end
|
|
|
724
763
|
The recogniser handles `RSpec.describe Foo`, bare
|
|
725
764
|
`describe Foo` (no `RSpec.` receiver), `subject { … }`,
|
|
726
765
|
`subject(:name) { … }`, `let(:name) { … }`, `let!(:name)`,
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
766
|
+
an ivar assigned by a top-level statement of a `before`
|
|
767
|
+
block, and `described_class.new(...)`. Same-name `subject` /
|
|
768
|
+
`let` bindings across nested scopes are last-wins; the
|
|
769
|
+
recogniser does not re-implement RSpec's full scope rules —
|
|
770
|
+
the typical one-spec-file shape is the target. A `before`
|
|
771
|
+
ivar is scoped: it types reads only inside the `describe` /
|
|
772
|
+
`context` block that holds the `before`, through nested
|
|
773
|
+
`describe` / `context` / `it` / hook blocks but not through
|
|
774
|
+
any other block (`Class.new { … }`, `instance_eval { … }`,
|
|
775
|
+
`each { … }`), and a read that two
|
|
776
|
+
enclosing `before`s assign different classes is not
|
|
777
|
+
observed.
|
|
731
778
|
|
|
732
779
|
A Minitest suite under `test/` is observed too, with no
|
|
733
780
|
recogniser of its own: a call whose receiver types to one
|
|
734
|
-
class counts. A receiver assigned in `setup`
|
|
735
|
-
inside a `test_*` method
|
|
736
|
-
a
|
|
737
|
-
|
|
781
|
+
class counts. A receiver assigned in `setup` reads as
|
|
782
|
+
`Foo | nil` inside a `test_*` method, since `setup` is not
|
|
783
|
+
`initialize`; a receiver of that shape — one class or class
|
|
784
|
+
object plus `nil` — is observed as that class when `nil`
|
|
785
|
+
does not answer the method, because such a call cannot
|
|
786
|
+
have been made on `nil`. A call of a method `nil` does
|
|
787
|
+
answer (`==`, `respond_to?`, `is_a?`) is not observed, as
|
|
788
|
+
it may have run on `nil`; with `&.` it is, since `&.` skips
|
|
789
|
+
`nil`. Which methods `nil` answers comes from the loaded
|
|
790
|
+
RBS for `NilClass`, `Object` and `Kernel`, so a method a
|
|
791
|
+
library adds to `nil` (ActiveSupport's `try`, `as_json`)
|
|
792
|
+
counts only when that library's RBS is loaded.
|
|
793
|
+
`@foo&.bar(1)` counts the same way. A receiver
|
|
794
|
+
that could be one of two classes (`Foo | Bar | nil`), or
|
|
795
|
+
`untyped | nil`, is still not observed.
|
|
738
796
|
|
|
739
797
|
The recogniser is part of the generator itself; you do not
|
|
740
798
|
need to install `rigor-rspec` to benefit from it. If you
|
|
@@ -751,14 +809,45 @@ by side without coordination.
|
|
|
751
809
|
every other byte of the file verbatim.
|
|
752
810
|
- **Will** append a new `class Foo … end` block when the
|
|
753
811
|
target file does not declare the class yet.
|
|
812
|
+
- **Will** treat a method as already declared when a file
|
|
813
|
+
under the signature tree declares it on the same class
|
|
814
|
+
with a `def` or an `attr_*` of the same kind (instance or
|
|
815
|
+
singleton), in any opening of the class in that file. A
|
|
816
|
+
`def self?.` declares both kinds, and a `def self?.`
|
|
817
|
+
proposal counts as declared when either kind is. The
|
|
818
|
+
method is updated (`--overwrite`) or skipped in the file
|
|
819
|
+
that declares it instead of being added to the class's
|
|
820
|
+
first file. A replacement lands only on a `def` of the
|
|
821
|
+
proposal's own kind in the first opening of the class:
|
|
822
|
+
a `def self?.` proposal over a lone `def self.f` or
|
|
823
|
+
`def f`, a plain proposal over a `def self?.f`, or a
|
|
824
|
+
method declared in a later opening is skipped. A
|
|
825
|
+
`def f: ... | ...` overload is never replaced and never
|
|
826
|
+
counts as the file that declares `f`. An `alias new old`
|
|
827
|
+
(or `alias self.new self.old`) declares `new` under its own
|
|
828
|
+
kind and is never replaced, so a proposal for that name is
|
|
829
|
+
skipped.
|
|
754
830
|
- **Will not** touch files outside the configured signature
|
|
755
831
|
tree.
|
|
832
|
+
- **Will** name every proposal it skipped, one
|
|
833
|
+
` skipped Class#method (reason): <proposed RBS>` line under
|
|
834
|
+
its target (`Class.method` for a singleton), with the same
|
|
835
|
+
rows under `skipped` in `--format=json`. The reason is
|
|
836
|
+
`user-authored` (the target declares the member and the
|
|
837
|
+
writer does not replace it) or `outside-sig-root` (the
|
|
838
|
+
member routes to a file in a signature path other than the
|
|
839
|
+
first, which `--write` does not modify, whether the member
|
|
840
|
+
is declared there or is new to a class that lives there).
|
|
841
|
+
A target left unchanged by skips alone prints
|
|
842
|
+
`unchanged <file>`. The exit status stays `0`: a skip is
|
|
843
|
+
not a refusal, and `--check` still reports `sig/` as up to
|
|
844
|
+
date.
|
|
756
845
|
- **Will not** replace an existing method declaration
|
|
757
846
|
unless `--overwrite` is set AND the candidate is a
|
|
758
847
|
`tighter-return`, and then only its return changes: the
|
|
759
848
|
rest of the declaration is kept. Without `--overwrite`,
|
|
760
849
|
existing declarations are user-authored and the new method
|
|
761
|
-
is
|
|
850
|
+
is skipped.
|
|
762
851
|
- **Will not** change an existing method declaration that
|
|
763
852
|
disagrees with the method's inline declaration unless
|
|
764
853
|
`--overwrite` is set; it reports the method as `REFUSED`
|
|
@@ -224,7 +224,7 @@ rigor type-scan PATH...
|
|
|
224
224
|
`--show-recognized` includes fully-covered classes,
|
|
225
225
|
`--threshold=RATIO` makes the command exit non-zero when the
|
|
226
226
|
unrecognized-node ratio exceeds `RATIO`, and `--format=text|json`
|
|
227
|
-
selects the output format.
|
|
227
|
+
selects the output format. `--config=PATH` overrides config discovery.
|
|
228
228
|
|
|
229
229
|
## `rigor effects`
|
|
230
230
|
|
|
@@ -354,6 +354,8 @@ accept `--config=PATH`, `--format=text|json` and `--full`;
|
|
|
354
354
|
configured one — `--baseline <(git show
|
|
355
355
|
origin/main:.rigor-effects.yml)` in a bot),
|
|
356
356
|
`--strict-tolerated` and `--no-tolerated-effects`.
|
|
357
|
+
`explain` additionally accepts `--symbol=KEY` to explain one
|
|
358
|
+
specific unit instead of the changed ones.
|
|
357
359
|
|
|
358
360
|
The file records two tables. `methods:` holds each method's
|
|
359
361
|
**direct** summary — what its own body does, block literals
|
|
@@ -476,12 +478,13 @@ Print the catalogue entry for a diagnostic rule, or list every
|
|
|
476
478
|
rule when called with no argument.
|
|
477
479
|
|
|
478
480
|
```sh
|
|
479
|
-
rigor explain [rule]
|
|
481
|
+
rigor explain [options] [rule]
|
|
480
482
|
```
|
|
481
483
|
|
|
482
484
|
`rule` is a rule ID (`call.undefined-method`), a legacy alias,
|
|
483
|
-
or a family wildcard (`call`, `flow`, `def`, `assert`, `dump`
|
|
484
|
-
|
|
485
|
+
or a family wildcard (`call`, `flow`, `def`, `assert`, `dump`, `global`,
|
|
486
|
+
`suppression`, `static`, `effect`).
|
|
487
|
+
`--format=text|json` selects the output format. Exit `64` for an unknown rule.
|
|
485
488
|
|
|
486
489
|
It also answers the `sig.skipped.*` identifiers `rigor sig-gen`
|
|
487
490
|
prints when it declines to write a signature — `rigor explain
|
|
@@ -495,11 +498,13 @@ Compare the current diagnostics against a saved baseline JSON
|
|
|
495
498
|
and report only what is new.
|
|
496
499
|
|
|
497
500
|
```sh
|
|
498
|
-
rigor diff <baseline.json> [paths...]
|
|
501
|
+
rigor diff [options] <baseline.json> [paths...]
|
|
499
502
|
```
|
|
500
503
|
|
|
501
504
|
`--current=PATH` compares against a saved diagnostics JSON
|
|
502
|
-
instead of running a fresh check.
|
|
505
|
+
instead of running a fresh check. `--format=text|json` selects the
|
|
506
|
+
output format (default `text`), and `--config=PATH` passes a custom
|
|
507
|
+
configuration to the implicit check. Exit `1` when new
|
|
503
508
|
diagnostics appear.
|
|
504
509
|
|
|
505
510
|
## `rigor sig-gen`
|
|
@@ -533,6 +538,10 @@ refuse a file, so a tighter return `--write` declines without
|
|
|
533
538
|
`--overwrite` does not fail it; `--check --overwrite` counts one.
|
|
534
539
|
See [handbook chapter 11](../handbook/11-sig-gen.md#keeping-sig-current-in-ci).
|
|
535
540
|
|
|
541
|
+
`--write` names each proposal it skipped (a member `sig/` already declares, an `alias` included, or one that
|
|
542
|
+
routes to a signature path it does not write to, because it is declared there or its class lives there) on its own `skipped Class#method (reason)` line; the exit status is
|
|
543
|
+
unchanged. See [handbook chapter 11](../handbook/11-sig-gen.md#safety-what---write-will-and-will-not-do).
|
|
544
|
+
|
|
536
545
|
A method declared inline with `# @rbs` / `#:` is written as that
|
|
537
546
|
declaration, not as what its body infers; a parameter-only
|
|
538
547
|
annotation keeps its parameters and takes the return from the body,
|
|
@@ -616,7 +625,8 @@ rigor baseline <generate|regenerate|dump|drift|prune> [options]
|
|
|
616
625
|
|
|
617
626
|
`generate` and `regenerate` accept `--output=PATH` and
|
|
618
627
|
`--match-mode=rule|message`; `dump`, `drift`, and `prune` accept
|
|
619
|
-
`--baseline=PATH` to read a non-default baseline file.
|
|
628
|
+
`--baseline=PATH` to read a non-default baseline file. `dump`
|
|
629
|
+
also accepts `--format=text|json` for machine-readable output.
|
|
620
630
|
|
|
621
631
|
## `rigor triage`
|
|
622
632
|
|
|
@@ -913,10 +923,11 @@ rigor mcp [--transport=stdio] [--config=PATH]
|
|
|
913
923
|
```
|
|
914
924
|
|
|
915
925
|
`stdio` is the only transport. The server is a pure-Ruby
|
|
916
|
-
JSON-RPC 2.0 implementation exposing
|
|
917
|
-
`rigor_check`, `rigor_type_of`, `rigor_triage`,
|
|
926
|
+
JSON-RPC 2.0 implementation exposing eight read-only tools:
|
|
927
|
+
`rigor_check`, `rigor_type_of`, `rigor_triage`, `rigor_lens`,
|
|
918
928
|
`rigor_annotate`, `rigor_sig_gen`, `rigor_explain`,
|
|
919
|
-
`rigor_coverage`.
|
|
929
|
+
`rigor_coverage`. `rigor_lens` writes only the incremental
|
|
930
|
+
cache `rigor check --incremental` writes.
|
|
920
931
|
|
|
921
932
|
## `rigor lsp` vs `rigor mcp`
|
|
922
933
|
|
|
@@ -1162,8 +1173,17 @@ Runs a scoped analysis and audits:
|
|
|
1162
1173
|
- **Plugin load errors** — whether every configured plugin loaded.
|
|
1163
1174
|
- **Baseline drift** — whether the current diagnostics have drifted
|
|
1164
1175
|
from the saved baseline.
|
|
1165
|
-
- **
|
|
1166
|
-
|
|
1176
|
+
- **Plugin gap** — whether the project's direct dependencies (the
|
|
1177
|
+
`DEPENDENCIES` section of `Gemfile.lock`, plus the Rails components
|
|
1178
|
+
a direct `rails` stands for) include gems a bundled plugin models —
|
|
1179
|
+
Rails, Devise, Pundit, Sidekiq, ... — while `plugins:` does not list
|
|
1180
|
+
that plugin ([ADR-96](../adr/96-plugin-target-gems.md)). An entry
|
|
1181
|
+
with `enabled: false` counts as listed. Each missing plugin is a
|
|
1182
|
+
warning, since leaving one out can be deliberate; it is a failure
|
|
1183
|
+
only when `plugins:` lists none of the bundled plugins that model
|
|
1184
|
+
gems (those declaring `target_gems:`).
|
|
1185
|
+
Skipped when no `.rigor.yml` or `.rigor.dist.yml` is discovered in
|
|
1186
|
+
the current directory.
|
|
1167
1187
|
- **Gemfile install** — whether Rigor itself resolves as one of the
|
|
1168
1188
|
project's dependencies, which
|
|
1169
1189
|
[Installing Rigor](01-installation.md) tells you not to do. Only a
|
|
@@ -1176,6 +1196,16 @@ Runs a scoped analysis and audits:
|
|
|
1176
1196
|
versioned together, so a copy from another installation can run
|
|
1177
1197
|
the engine with a mismatched plugin. A warning, not a failure;
|
|
1178
1198
|
third-party plugins from your own bundle are never flagged.
|
|
1199
|
+
- **Bundle layout** — whether Rigor can locate the bundle a
|
|
1200
|
+
`Gemfile.lock` in the current directory was installed into; when it
|
|
1201
|
+
cannot (typically gems in the active Ruby's default gem home), no
|
|
1202
|
+
gem-shipped `sig/` is discovered. Rigor finds a bundle only where the
|
|
1203
|
+
project records it. A `bundler.bundle_path:` in `.rigor.yml` is used
|
|
1204
|
+
alone and must exist. Without one — and unless
|
|
1205
|
+
`bundler.auto_detect: false` — Rigor reads `.bundle/config`'s
|
|
1206
|
+
`BUNDLE_PATH`, then an in-tree `vendor/bundle`, then the global
|
|
1207
|
+
Bundler config. A warning, not a failure: the default layout is
|
|
1208
|
+
correct, and most gems ship no `sig/`.
|
|
1179
1209
|
|
|
1180
1210
|
Text output prints `[PASS]`, `[FAIL]`, or `[WARN]` per check plus a
|
|
1181
1211
|
routed hint (e.g. "Run `rigor baseline regenerate`"). JSON output
|
|
@@ -1236,6 +1266,7 @@ diagnostics about Rigor's own inference cutoffs and memory — see
|
|
|
1236
1266
|
| `0` | Success — no diagnostic at or above the exit threshold (`error` by default; `rigor check --fail-on=SEVERITY` lowers it to `warning` or `info`). |
|
|
1237
1267
|
| `1` | Diagnostics found at or above the threshold, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
|
|
1238
1268
|
| `64` | Usage error — unknown command, bad flag, malformed argument, or a value in `.rigor.yml` the loader cannot proceed on. |
|
|
1269
|
+
| `70` | Internal fatal error — `SystemStackError` or `NoMemoryError` inside the analysis; Rigor could not finish the run, so its report on disk is empty rather than clean. |
|
|
1239
1270
|
|
|
1240
1271
|
`rigor triage` is the exception: it is advisory and always
|
|
1241
1272
|
exits `0`.
|
|
@@ -78,7 +78,7 @@ cache:
|
|
|
78
78
|
| --- | --- | --- | --- |
|
|
79
79
|
| `libraries` | Array | `[]` | Standard-library / gem names whose bundled RBS to load. |
|
|
80
80
|
| `signature_paths` | Array | `nil` | Extra directories of `.rbs` files. Relative entries resolve against the config file's directory. |
|
|
81
|
-
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. The walker does not follow `require` / `require_relative`, so list the files in load order: a method declared in two files takes the last-listed one, and a file that requires a later-listed one is still read in list order. |
|
|
81
|
+
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. The walker does not follow `require` / `require_relative`, so list the files in load order: a method declared in two files takes the last-listed one, and a file that requires a later-listed one is still read in list order. `type-of`, `type-scan`, `trace`, `annotate` and `sig-gen` read the patches too, so they type a patched method as `rigor check` does. |
|
|
82
82
|
| `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
|
|
83
83
|
|
|
84
84
|
### Signature generation
|
|
@@ -103,6 +103,8 @@ rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from
|
|
|
103
103
|
rigor: signature_paths: "/path/to/sig" matched 0 signature files
|
|
104
104
|
rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
|
|
105
105
|
rigor: disable: "call.undefined-methdo" is not a recognized rule id; the suppression has no effect
|
|
106
|
+
rigor: disable: "plugin_loader.load-error" names a run-level row, which `disable:` does not reach; use `severity_overrides:` or a baseline
|
|
107
|
+
rigor: disable: "plugin.shoulda-matchers.unknown-column" names no plugin `plugins:` configures; the suppression has no effect
|
|
106
108
|
rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the override has no effect
|
|
107
109
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
108
110
|
```
|
|
@@ -125,10 +127,21 @@ the JSON schema as you type rather than at check time.
|
|
|
125
127
|
These are warnings, not errors — partial or optional bundles and
|
|
126
128
|
forward-looking config are valid setups. The audit only fires on explicit,
|
|
127
129
|
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
128
|
-
auto-detected bundle) is never warned about, and a
|
|
129
|
-
`
|
|
130
|
-
`
|
|
131
|
-
|
|
130
|
+
auto-detected bundle) is never warned about, and a
|
|
131
|
+
`rbs_extended.…`, `rbs.…` or `dynamic.…` key in `severity_overrides:` or
|
|
132
|
+
`disable:` is left alone, since the engine emits those ids outside the
|
|
133
|
+
catalogue. `disable:` and `severity_overrides:` are both checked
|
|
134
|
+
closely, because what they can reach is known: a plugin's rule counts
|
|
135
|
+
only in its qualified `plugin.<id>.<rule>` form, so a bare plugin rule
|
|
136
|
+
(`rspec.let-binding`) is warned about. A qualified token is checked
|
|
137
|
+
against `plugins:` only when every entry's plugin ID is known without
|
|
138
|
+
loading it — an explicit `id:`, or a bundled plugin's gem name minus
|
|
139
|
+
`rigor-`; one third-party gem listed without `id:` turns that check
|
|
140
|
+
off. A `disable:` token under `rbs_extended.…`, `rbs.…` or `dynamic.…`
|
|
141
|
+
is left alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
142
|
+
`dynamic.dependency-source.…`), which are flagged; in `severity_overrides:`
|
|
143
|
+
those rows are the intended keys and are never flagged, while a bare plugin
|
|
144
|
+
rule, `all`, or a `plugin.`-qualified key for an unconfigured plugin is. The same findings appear in the
|
|
132
145
|
`--format=json` payload under `config_warnings` (each tagged with a
|
|
133
146
|
`kind`), so CI can assert on them.
|
|
134
147
|
|
|
@@ -136,9 +149,9 @@ statically and may resolve at run time. The same findings appear in the
|
|
|
136
149
|
|
|
137
150
|
| Key | Type | Default | Meaning |
|
|
138
151
|
| --- | --- | --- | --- |
|
|
139
|
-
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide. |
|
|
152
|
+
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide; `all` suppresses every built-in check rule. A plugin's diagnostics are named by qualified ID (`plugin.<id>.<rule>`, or `plugin.<id>` for the whole plugin); a bare ID or `all` does not reach them. The run-level `.rigor.yml` rows (`plugin_loader.*`, `dynamic.dependency-source.*`, `rbs.coverage.*`, `plugin_trust.*`, `pool-degraded`) are not reached by any spelling; use `severity_overrides:` for those. See [Diagnostics](04-diagnostics.md#suppressing-a-diagnostic). |
|
|
140
153
|
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
141
|
-
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
154
|
+
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. A plugin's diagnostics are named by qualified ID (`plugin.<id>.<rule>`, or `plugin.<id>` for the whole plugin); a bare rule or family does not reach them. The plugin loader's own rows are `plugin_loader.load-error` / `plugin_loader.runtime-error` (or the bare `load-error` / `runtime-error`). |
|
|
142
155
|
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
143
156
|
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued changes early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge). |
|
|
144
157
|
|
|
@@ -162,6 +162,15 @@ the diagnostic from the result entirely, which makes
|
|
|
162
162
|
below — both silence a rule; the override reads as "this one
|
|
163
163
|
rule, at this severity" alongside the rest of the profile.
|
|
164
164
|
|
|
165
|
+
A plugin's diagnostic is named by its qualified ID, as in `disable:`:
|
|
166
|
+
`plugin.<id>.<rule>` for one rule (`plugin.rails-i18n.load-error: off`),
|
|
167
|
+
or `plugin.<id>` for every row of that plugin. A bare rule or a family
|
|
168
|
+
never reaches a plugin's row, so `load-error: off` leaves each plugin's
|
|
169
|
+
own `load-error` alone. The plugin loader's rows are
|
|
170
|
+
`plugin_loader.load-error` and `plugin_loader.runtime-error`, which also
|
|
171
|
+
answer to the bare `load-error` / `runtime-error`. `rigor check` warns
|
|
172
|
+
about a key it can tell reaches no row.
|
|
173
|
+
|
|
165
174
|
YAML reserves the bareword `off` as a boolean. If an override
|
|
166
175
|
that names it seems not to apply, quote it — `"off"` — and the
|
|
167
176
|
same for `on`.
|
|
@@ -252,8 +261,14 @@ named rules on that line:
|
|
|
252
261
|
config.merge(extra) # rigor:disable call.undefined-method
|
|
253
262
|
```
|
|
254
263
|
|
|
255
|
-
It accepts qualified IDs, family wildcards (`call`
|
|
256
|
-
|
|
264
|
+
It accepts qualified IDs, family wildcards (`call`, `flow`, `def`, `assert`,
|
|
265
|
+
`dump`, `global`, `suppression`, `static`, `effect`, `pre-eval`), a
|
|
266
|
+
comma- or space-separated list, or `all`. A plugin's diagnostic
|
|
267
|
+
is reached only by its qualified ID as printed in brackets
|
|
268
|
+
(`plugin.shoulda-matchers.unknown-column`) or by the plugin's
|
|
269
|
+
whole family (`plugin.shoulda-matchers`); its bare rule
|
|
270
|
+
(`unknown-column`) and `all` leave it alone, because several
|
|
271
|
+
plugins share bare rules. The comment must sit
|
|
257
272
|
on the line the diagnostic points at; there is no
|
|
258
273
|
`disable-block` form, so an expression spread over several
|
|
259
274
|
lines needs the comment on each line that fires.
|
|
@@ -283,7 +298,8 @@ suppressible like any other rule.
|
|
|
283
298
|
|
|
284
299
|
**In-source, whole file.** `# rigor:disable-file <rules>`
|
|
285
300
|
anywhere in a file suppresses those rules for every line;
|
|
286
|
-
`# rigor:disable-file all` silences
|
|
301
|
+
`# rigor:disable-file all` silences every built-in rule in the file
|
|
302
|
+
(a plugin's diagnostics need their qualified ID, as above). Convention is to
|
|
287
303
|
put it near the top — typically on a generated file, a fixture,
|
|
288
304
|
or a vendored snippet — but every comment in the file is
|
|
289
305
|
scanned, so any placement works.
|
|
@@ -300,6 +316,43 @@ disable:
|
|
|
300
316
|
- flow.dead-assignment
|
|
301
317
|
```
|
|
302
318
|
|
|
319
|
+
`disable: [all]` turns off every built-in check rule, including
|
|
320
|
+
`effect.annotations-unchecked`, which is reported at a signature
|
|
321
|
+
file.
|
|
322
|
+
|
|
323
|
+
A plugin's own diagnostics are turned off by their qualified ID —
|
|
324
|
+
`plugin.<id>.<rule>` for one rule, `plugin.<id>` for every rule
|
|
325
|
+
of that plugin:
|
|
326
|
+
|
|
327
|
+
```yaml
|
|
328
|
+
disable:
|
|
329
|
+
- plugin.shoulda-matchers.unknown-column
|
|
330
|
+
- plugin.rails-i18n
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
A bare rule (`unknown-column`, `load-error`) and `all` do not
|
|
334
|
+
reach them, so one plugin's suppression never hides another
|
|
335
|
+
plugin's finding of the same name.
|
|
336
|
+
|
|
337
|
+
The list does not reach the run-level rows reported at
|
|
338
|
+
`.rigor.yml` — a plugin that failed to load or raised in
|
|
339
|
+
`prepare` (`plugin_loader.*`), a `dependencies.source_inference:`
|
|
340
|
+
gem that did not resolve (`dynamic.dependency-source.*`), the
|
|
341
|
+
`rbs.coverage.*` notices, `plugin_trust.*`, `pool-degraded` —
|
|
342
|
+
under any spelling, `plugin_loader.load-error` included. Turn
|
|
343
|
+
those off with [`severity_overrides:`](#severity-profiles) set
|
|
344
|
+
to `off`, or record them in a [baseline](06-baseline.md).
|
|
345
|
+
In `severity_overrides:` the plugin loader's rows are
|
|
346
|
+
`plugin_loader.load-error` and `plugin_loader.runtime-error`, or the
|
|
347
|
+
bare `load-error` / `runtime-error` (the qualified key wins if both
|
|
348
|
+
are set); a bare key does not reach a
|
|
349
|
+
plugin's own row of the same name (`plugin.<id>.load-error`). `rigor check` warns when a
|
|
350
|
+
`disable:` entry names one of these rows, names no rule at all,
|
|
351
|
+
or names a plugin `plugins:` does not configure (see
|
|
352
|
+
[Config validation warnings](03-configuration.md#config-validation-warnings)).
|
|
353
|
+
A path or parse error, which has no rule ID, is always
|
|
354
|
+
reported.
|
|
355
|
+
|
|
303
356
|
For a *known backlog* you want to keep visible but not fail
|
|
304
357
|
on, prefer a [baseline](06-baseline.md) over a blanket
|
|
305
358
|
`disable:` — `disable:` also hides any new occurrences.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Rigor's analysis is invisible by default — it only speaks up to
|
|
4
4
|
report a diagnostic. When you want to *see* what type the
|
|
5
|
-
engine assigns an expression, there are
|
|
6
|
-
helpers and
|
|
5
|
+
engine assigns an expression, there are six tools: two source
|
|
6
|
+
helpers and four CLI commands.
|
|
7
7
|
|
|
8
8
|
## `dump_type` — print a type from source
|
|
9
9
|
|
data/docs/manual/06-baseline.md
CHANGED
|
@@ -73,7 +73,7 @@ a diagnostic another layer has already suppressed.
|
|
|
73
73
|
| --- | --- |
|
|
74
74
|
| `rigor baseline generate` | Write a fresh baseline from the current diagnostics. Refuses to clobber an existing file without `--force`. |
|
|
75
75
|
| `rigor baseline regenerate` | Rewrite unconditionally — run it after fixing diagnostics so the file shrinks. |
|
|
76
|
-
| `rigor baseline dump` | Print the baseline, filterable with `--rule` and `--file
|
|
76
|
+
| `rigor baseline dump` | Print the baseline, filterable with `--rule` and `--file` (`--format=json` for tooling). |
|
|
77
77
|
| `rigor baseline drift` | Show how buckets have moved — `--only=over` for buckets that grew, `reducible` for ones you have already improved past, `cleared` for empty ones. |
|
|
78
78
|
| `rigor baseline prune` | Drop buckets that match nothing any more. `--dry-run` previews. |
|
|
79
79
|
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -5,7 +5,8 @@ application DSL that plain inference cannot see — Rails route
|
|
|
5
5
|
helpers, RSpec `let` bindings, dry-rb struct attributes, and
|
|
6
6
|
so on. This page is about *activating* plugins. Writing one is
|
|
7
7
|
covered by [`examples/`](../../examples/README.md) and the
|
|
8
|
-
[`rigor-plugin-author` skill](08-skills.md)
|
|
8
|
+
[`rigor-plugin-author` skill](08-skills.md); reviewing and modernising
|
|
9
|
+
an existing plugin is [`rigor-plugin-review`](08-skills.md).
|
|
9
10
|
|
|
10
11
|
## Activating a plugin
|
|
11
12
|
|
|
@@ -95,7 +96,7 @@ with the `plugins_isolation:` configuration key or the
|
|
|
95
96
|
| --- | --- |
|
|
96
97
|
| `process` (default) | Run the call in a forked, crash-contained worker, so the target library's monkey-patches and any crash never contaminate Rigor. Falls back to `none` where `fork` is unavailable (Windows / JRuby). |
|
|
97
98
|
| `none` | Load the library into Rigor's own process and call it directly. |
|
|
98
|
-
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. It also needs a Ruby that fixes [Ruby Bug #22260](https://bugs.ruby-lang.org/issues/22260), which no release up to 4.0.7 does. The launcher checks for the fix first, and without it prints a warning and uses the configured strategy instead. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
|
|
99
|
+
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. It also needs a Ruby that fixes [Ruby Bug #22260](https://bugs.ruby-lang.org/issues/22260), which no release up to 4.0.7 does. The launcher checks for the fix first, and without it prints a warning and uses the configured strategy instead. The same check runs when `RUBY_BOX=1` is already in your environment: without the fix, the launcher prints a notice and restarts itself without `RUBY_BOX`, so the run completes but plugins are not boxed. The check result is cached per Ruby build under `$XDG_CACHE_HOME/rigor` (or `~/.cache/rigor`) and never expires, so if you rebuild a Ruby in place with the fix, delete `$XDG_CACHE_HOME/rigor/ruby-box-probe` (or `~/.cache/rigor/ruby-box-probe`). A check that times out is not cached, so it costs up to ten seconds on every run. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
|
|
99
100
|
|
|
100
101
|
The environment variable wins over `plugins_isolation:`, so you can
|
|
101
102
|
override a project's committed choice for one invocation. The legacy
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -87,6 +87,10 @@ is not repeated here.)
|
|
|
87
87
|
- **`rigor-monkeypatch-resolve`** — resolves an `undefined-method`
|
|
88
88
|
cluster that is really your project's own monkey-patches by wiring the
|
|
89
89
|
defining files into `pre_eval:`.
|
|
90
|
+
- **`rigor-unused-adjudicate`** — adjudicates a `rigor unused` report
|
|
91
|
+
safely before proposing dead-code removals, checking framework roots
|
|
92
|
+
and false-positive shapes. See
|
|
93
|
+
[Removing dead code with `rigor unused`](18-removing-dead-code.md).
|
|
90
94
|
- **`rigor-type-oracle`** — sources every type an agent writes from
|
|
91
95
|
Rigor (`type-of` / `annotate` / `sig-gen`) instead of from reading the
|
|
92
96
|
code, and reports the gaps rather than filling them. It is triggered by
|
|
@@ -119,6 +123,9 @@ is not repeated here.)
|
|
|
119
123
|
repository to teach Rigor an application DSL or metaprogramming
|
|
120
124
|
pattern it cannot infer. Reach for it when no bundled plugin covers a
|
|
121
125
|
framework or DSL your project depends on.
|
|
126
|
+
- **`rigor-plugin-review`** — audits an existing Rigor plugin against
|
|
127
|
+
the current authoring contract and produces a prioritized upgrade
|
|
128
|
+
path. The review/upgrade counterpart to `rigor-plugin-author`.
|
|
122
129
|
|
|
123
130
|
## Discovering skills from the CLI
|
|
124
131
|
|