rigortype 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +1 -0
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +98 -4
- data/docs/manual/02-cli-reference.md +49 -12
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +4 -4
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +33 -3
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +6 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +19 -27
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +361 -113
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +127 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +451 -36
- data/lib/rigor/inference/statement_evaluator.rb +64 -15
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +42 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7cea93d0b50519bad210f33907d4135c61043d910618153ad76f1f25b0163b4d
|
|
4
|
+
data.tar.gz: 45cba56af68f2ee34c7e8674310fb35bb74e366a3fc67bc89262ad8cb9556cc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 626ef3d0d0967cfd0f8e887a7ab2f53ed741a848fd25f90239c69e7a40e84c3b4a658acd945da0fd4d344d9de9cc650c3be8236f107468b70e2d32441ee9d7d3
|
|
7
|
+
data.tar.gz: e23af83633c18c05dce1a01814f235838143a31f3a34cf719ca35239a40bc854fff8f8e4bd55f4d0c4f39547b4b600c7c543019055576e297fed36ef60a70537
|
data/README.md
CHANGED
|
@@ -231,8 +231,8 @@ rigor docs --list # list every bundled page
|
|
|
231
231
|
|
|
232
232
|
## Status
|
|
233
233
|
|
|
234
|
-
Current release: **`v0.
|
|
235
|
-
`0.
|
|
234
|
+
Current release: **`v0.3.0`** (2026-07-19) — the first cut on the
|
|
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
|
|
238
238
|
minor-non-break trial, rehearsing the contract that hard-freezes at
|
|
@@ -275,7 +275,8 @@ end
|
|
|
275
275
|
(Narrowing the truthy edge further to specific refinement
|
|
276
276
|
carriers — so `\d{4}` would produce `decimal-int-string` — is a
|
|
277
277
|
demand-driven follow-up on the regex-pattern → refinement-name
|
|
278
|
-
recogniser track
|
|
278
|
+
recogniser track, tracked as
|
|
279
|
+
[#164](https://github.com/rigortype/rigor/issues/164).)
|
|
279
280
|
|
|
280
281
|
## Negation and `unless`
|
|
281
282
|
|
|
@@ -221,9 +221,13 @@ arr = [1, ARGV.first]
|
|
|
221
221
|
# Array[Constant<1> | String?]
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
The same goes for hashes whose keys are not provably
|
|
225
|
-
|
|
226
|
-
`
|
|
224
|
+
The same goes for hashes whose keys are not provably scalar
|
|
225
|
+
literals (symbols, plain strings, integers, floats, `true` /
|
|
226
|
+
`false` / `nil`) — Rigor produces `Hash[K, V]` rather than
|
|
227
|
+
`HashShape`. Within a shape, key identity follows `Hash#eql?`:
|
|
228
|
+
`1` and `1.0` are distinct keys, and a repeated literal key
|
|
229
|
+
keeps the last entry, exactly like the runtime. Non-symbol,
|
|
230
|
+
non-string keys display in hashrocket form (`{ 1 => 2 }`).
|
|
227
231
|
|
|
228
232
|
## Deriving new shapes — `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of`
|
|
229
233
|
|
data/docs/handbook/06-classes.md
CHANGED
|
@@ -170,8 +170,8 @@ discovery composes uniformly across both shapes.
|
|
|
170
170
|
|
|
171
171
|
`Struct.new(*Symbol)` produces a positional-arg constructor
|
|
172
172
|
plus the same accessors as `Data.define`. Rigor folds struct
|
|
173
|
-
member reads too,
|
|
174
|
-
|
|
173
|
+
member reads too, and — because a `Struct` is mutable — tracks a
|
|
174
|
+
straight-line setter so the read stays precise:
|
|
175
175
|
|
|
176
176
|
```ruby
|
|
177
177
|
Coord = Struct.new(:x, :y)
|
|
@@ -181,18 +181,27 @@ c = Coord.new(10, 20)
|
|
|
181
181
|
assert_type("10", c.x)
|
|
182
182
|
assert_type("20", c.y)
|
|
183
183
|
|
|
184
|
-
# A
|
|
185
|
-
# its reads degrade to Dynamic, never a stale value.
|
|
184
|
+
# A straight-line setter re-types the member; the sibling stays precise.
|
|
186
185
|
m = Coord.new(1, 2)
|
|
187
186
|
m.x = 9
|
|
188
|
-
assert_type("
|
|
187
|
+
assert_type("9", m.x)
|
|
188
|
+
assert_type("2", m.y)
|
|
189
|
+
|
|
190
|
+
# A local that is aliased, escapes, or is mutated inside a loop or
|
|
191
|
+
# block is not fold-safe — its reads degrade to Dynamic, never a
|
|
192
|
+
# stale value, because a write Rigor cannot see could have changed it.
|
|
193
|
+
a = Coord.new(1, 2)
|
|
194
|
+
alias_ref = a
|
|
195
|
+
a.x = 9
|
|
196
|
+
assert_type("Dynamic[top]", a.x)
|
|
189
197
|
```
|
|
190
198
|
|
|
191
199
|
Because `Struct` accessors are also writers, the fold is gated:
|
|
192
|
-
a member read off a freshly constructed instance (`Coord.new(1,
|
|
193
|
-
2).x`)
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
a member read folds off a freshly constructed instance (`Coord.new(1,
|
|
201
|
+
2).x`), off a local the analysis proves is never written, aliased, or
|
|
202
|
+
passed away, or off one whose only writes are straight-line member
|
|
203
|
+
setters (their assigned type flows into the later read); anything else
|
|
204
|
+
widens to `Dynamic[top]`. `Data` is read-only, so its reads always fold.
|
|
196
205
|
|
|
197
206
|
## Inheritance and method resolution
|
|
198
207
|
|
|
@@ -87,6 +87,7 @@ Fire when the control flow itself is unsound.
|
|
|
87
87
|
| `flow.always-truthy-condition` | The predicate of an `if` / `unless` / ternary is provably truthy (or falsey) by inferred type, with surgical skips inside loop bodies and on defensive predicate calls. | warning |
|
|
88
88
|
| `flow.unreachable-clause` | A `case <local>; when <Class>` (or bare-class `case`/`in`) clause whose subject narrowing proves it can never match — disjoint from the subject's type, or already exhausted by an earlier clause. | info under `balanced`, warning under `strict`, info under `lenient` |
|
|
89
89
|
| `flow.dead-assignment` | A plain local-variable write whose target name is never read in the same `def` body. | warning |
|
|
90
|
+
| `flow.duplicate-hash-key` | Two entries of one Hash literal carry the same literal key (`{ a: 1, a: 2 }`, `m("x" => 1, "x" => 2)`) — the last entry wins silently at runtime. Literal keys only; `:a` vs `"a"` and `1` vs `1.0` are distinct keys and never compared. | warning |
|
|
90
91
|
|
|
91
92
|
`flow.unreachable-branch`, `flow.always-truthy-condition`, and
|
|
92
93
|
`flow.unreachable-clause` are the **reachability family** — each
|
data/docs/handbook/11-sig-gen.md
CHANGED
|
@@ -93,7 +93,7 @@ states:
|
|
|
93
93
|
| `equivalent` | The inferred return is not a strict subtype of the declared one — identical, wider, or unrelated — so there is nothing to tighten. Silently skipped. |
|
|
94
94
|
| `skipped` | Disqualified for one of the reasons below. |
|
|
95
95
|
|
|
96
|
-
The
|
|
96
|
+
The `sig.skipped.*` reasons are:
|
|
97
97
|
|
|
98
98
|
- `sig.skipped.complex-shape` — the method has optional, rest,
|
|
99
99
|
keyword, block, or forwarding parameters. The MVP's
|
|
@@ -105,6 +105,16 @@ The three `sig.skipped.*` reasons are:
|
|
|
105
105
|
- `sig.skipped.user-authored` — `--overwrite` was not set
|
|
106
106
|
and the method's existing RBS declaration would have to
|
|
107
107
|
be replaced.
|
|
108
|
+
- `sig.skipped.unrenderable-rbs` — the signature Rigor
|
|
109
|
+
rendered for this method does not parse as RBS. This one
|
|
110
|
+
is a **bug in Rigor**, not a property of your code: every
|
|
111
|
+
generated line is parsed before it is emitted, and a line
|
|
112
|
+
`rbs` rejects is dropped rather than written, because an
|
|
113
|
+
unparseable `.rbs` is quarantined *whole* by `rigor check`
|
|
114
|
+
— one bad line would take every other type in the file
|
|
115
|
+
down with it. The rest of the signatures are unaffected;
|
|
116
|
+
the skipped method is reported on stderr, and it is worth
|
|
117
|
+
reporting to us.
|
|
108
118
|
|
|
109
119
|
The three `sig.generated.*` identifiers
|
|
110
120
|
(`sig.generated.new-file` / `new-method` / `tighter-return`)
|
|
@@ -345,9 +345,10 @@ def shout: (String s) -> non-empty-string
|
|
|
345
345
|
|
|
346
346
|
You gain: `s.empty?` is a recognised refinement narrower (no
|
|
347
347
|
need for `assert s`). `match?(/\A\d+\z/)` does not yet narrow
|
|
348
|
-
to `numeric-string` (
|
|
349
|
-
[
|
|
350
|
-
behaviour will mirror `s.isdigit()` narrowing in
|
|
348
|
+
to `numeric-string` (queued as
|
|
349
|
+
[#164](https://github.com/rigortype/rigor/issues/164)), but the
|
|
350
|
+
eventual behaviour will mirror `s.isdigit()` narrowing in
|
|
351
|
+
Pyright.
|
|
351
352
|
|
|
352
353
|
## What's next
|
|
353
354
|
|
data/docs/install.md
CHANGED
|
@@ -37,19 +37,27 @@ Then proceed to the **first** matching case below.
|
|
|
37
37
|
and pins language runtimes (Ruby, Node, Python, …) and tool gems
|
|
38
38
|
(like `rigortype`) per project, recording versions in a `mise.toml`
|
|
39
39
|
that can be committed alongside the code. Other contributors — and CI
|
|
40
|
-
— run `mise install` to restore
|
|
41
|
-
|
|
40
|
+
— run `mise install` to restore those versions with no `Gemfile`
|
|
41
|
+
involvement.
|
|
42
42
|
|
|
43
43
|
Run in the project root:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
46
|
mise use ruby@4.0
|
|
47
|
-
mise use gem:rigortype
|
|
47
|
+
mise use --pin gem:rigortype
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
`mise use` installs the tools and writes their versions to `mise.toml`
|
|
51
51
|
in one step. Commit `mise.toml` so the version is shared.
|
|
52
52
|
|
|
53
|
+
`--pin` records the exact Rigor version (`"gem:rigortype" = "0.2.9"`).
|
|
54
|
+
Without it mise writes `"gem:rigortype" = "latest"`, which every
|
|
55
|
+
machine re-resolves to whatever is newest when it first installs — so
|
|
56
|
+
a committed `latest` does not give the team one shared version. The
|
|
57
|
+
trade-off: a pin will not move on its own, and `mise outdated` cannot
|
|
58
|
+
report a pinned tool as behind. Upgrade with `mise upgrade --bump
|
|
59
|
+
gem:rigortype`.
|
|
60
|
+
|
|
53
61
|
Then verify:
|
|
54
62
|
|
|
55
63
|
```sh
|
|
@@ -184,6 +192,6 @@ project is set up, re-run `rigor skill describe` for the step after
|
|
|
184
192
|
that.
|
|
185
193
|
|
|
186
194
|
If `rigor skill describe` is not recognised, your Rigor version predates
|
|
187
|
-
it. Run `rigor --version` and upgrade with `mise
|
|
188
|
-
`gem update rigortype` for Case C
|
|
189
|
-
`rigor skill rigor-project-init` directly.
|
|
195
|
+
it. Run `rigor --version` and upgrade with `mise upgrade --bump
|
|
196
|
+
gem:rigortype` (or `gem update rigortype` for Case B/C); on an older
|
|
197
|
+
version, run `rigor skill rigor-project-init` directly.
|
|
@@ -244,9 +244,23 @@ A few things worth knowing if you have not used mise before:
|
|
|
244
244
|
*current directory* recording the chosen versions, and installs
|
|
245
245
|
the tools as part of the same command — there is no separate
|
|
246
246
|
install step. (mise also reads the asdf-style `.tool-versions`.)
|
|
247
|
-
- **Commit the config to share versions
|
|
248
|
-
`mise.toml` into Git so every
|
|
249
|
-
|
|
247
|
+
- **Commit the config to share versions — and pass `--pin` if you
|
|
248
|
+
mean it.** Check the generated `mise.toml` into Git so every
|
|
249
|
+
contributor, and every CI run, resolves the same versions. Note
|
|
250
|
+
what the two commands above actually record, because they differ:
|
|
251
|
+
`mise use ruby@4.0` preserves the precision you asked for and
|
|
252
|
+
writes `ruby = "4.0"` (any 4.0.x), but `mise use gem:rigortype` has
|
|
253
|
+
no requested version to preserve and writes `"gem:rigortype" =
|
|
254
|
+
"latest"` — which resolves to whatever is newest *on each machine,
|
|
255
|
+
whenever that machine first installs it*. A committed `latest` is
|
|
256
|
+
not a shared version. To share one, pin it:
|
|
257
|
+
|
|
258
|
+
```sh
|
|
259
|
+
mise use --pin gem:rigortype # records e.g. "gem:rigortype" = "0.2.9"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Then read [Keeping Rigor up to date](#keeping-rigor-up-to-date):
|
|
263
|
+
a pin is a version that will not move until you move it.
|
|
250
264
|
- **For a machine-wide install, add `-g`.** `mise use -g
|
|
251
265
|
gem:rigortype` writes mise's global config
|
|
252
266
|
(`~/.config/mise/config.toml`) instead of a project `mise.toml`,
|
|
@@ -284,6 +298,48 @@ explicitly with `mise exec gem:rigortype -- rigor`. See
|
|
|
284
298
|
[Editor integration](09-editor-integration.md) for the editor
|
|
285
299
|
side.
|
|
286
300
|
|
|
301
|
+
### Keeping Rigor up to date
|
|
302
|
+
|
|
303
|
+
Rigor ships often. How you upgrade depends on what your config
|
|
304
|
+
records — and in one case, on knowing that mise will not tell you an
|
|
305
|
+
upgrade exists.
|
|
306
|
+
|
|
307
|
+
- **`"gem:rigortype" = "latest"`** — what a plain `mise use
|
|
308
|
+
gem:rigortype` writes. `mise upgrade gem:rigortype` moves you to the
|
|
309
|
+
newest release.
|
|
310
|
+
- **An exact pin** — `"gem:rigortype" = "0.2.9"`, from `--pin` or from
|
|
311
|
+
`mise use gem:rigortype@0.2.9`. Here `mise upgrade` **will not move
|
|
312
|
+
it, and `mise outdated` will not report it.** Both compare the
|
|
313
|
+
installed version against the range the config asks for, and an
|
|
314
|
+
exact pin is a range containing only itself, so a pinned Rigor
|
|
315
|
+
reports itself up to date forever, however far behind it has fallen.
|
|
316
|
+
Use `--bump`, which installs the newest release *and* rewrites the
|
|
317
|
+
pin:
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
mise upgrade --bump gem:rigortype
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Neither is a bug to work around — a pin doing nothing is a pin working.
|
|
324
|
+
But it does mean a pinned setup has no passive signal that a new Rigor
|
|
325
|
+
exists: `--bump` is both how you look and how you move.
|
|
326
|
+
|
|
327
|
+
**After a Ruby change.** mise's `gem:` backend installs each tool
|
|
328
|
+
against the Ruby that was active at install time, and its
|
|
329
|
+
[gem backend docs](https://mise.jdx.dev/dev-tools/backends/gem.html)
|
|
330
|
+
note that "if the ruby version used by a gem package changes, (by mise
|
|
331
|
+
or system ruby), you may need to reinstall the gem". Rigor's pinned
|
|
332
|
+
`ruby@4.0` makes this rare — patch upgrades within 4.0.x are followed
|
|
333
|
+
automatically — but if you remove or replace that Ruby, reinstall:
|
|
334
|
+
|
|
335
|
+
```sh
|
|
336
|
+
mise install -f gem:rigortype # or, for every gem-backend tool:
|
|
337
|
+
mise install -f "gem:*"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
On a plain `gem install` (below), the equivalent is `gem update
|
|
341
|
+
rigortype`.
|
|
342
|
+
|
|
287
343
|
## asdf
|
|
288
344
|
|
|
289
345
|
`asdf` follows the same model. Install a Ruby 4.0.x with the
|
|
@@ -300,7 +356,8 @@ gem install rigortype
|
|
|
300
356
|
`asdf` has no general-purpose gem backend, so the gem itself is
|
|
301
357
|
installed with `gem install` rather than an `asdf` command. `mise`
|
|
302
358
|
(above) is the more integrated option because its `gem:` backend
|
|
303
|
-
|
|
359
|
+
records the gem in the same config that records Ruby, and — as the
|
|
360
|
+
next section explains — keeps the two from interfering.
|
|
304
361
|
|
|
305
362
|
## Simple alternative — gem install
|
|
306
363
|
|
|
@@ -316,6 +373,43 @@ quickest path, but it records nothing per project: a version
|
|
|
316
373
|
manager keeps the Rigor version pinned next to the project, so
|
|
317
374
|
local runs and CI cannot drift apart.
|
|
318
375
|
|
|
376
|
+
It also leaves Rigor sharing a Ruby with your work, which is the
|
|
377
|
+
deeper reason the version managers are listed first. The executable
|
|
378
|
+
RubyGems installs begins with `#!/usr/bin/env ruby`, so it runs under
|
|
379
|
+
whatever `ruby` is first on `PATH` *at the moment you invoke it* — and
|
|
380
|
+
because Rigor requires Ruby 4.0 while your projects pin their own,
|
|
381
|
+
running `rigor` inside a project on Ruby 3.x fails before it starts.
|
|
382
|
+
mise's `gem:` backend does not have this failure mode: it installs each
|
|
383
|
+
tool into its own private gem directory and points the executable at
|
|
384
|
+
the Ruby the tool was installed with, so a project's Ruby pin cannot
|
|
385
|
+
reach it. A `gem install` under a Ruby 4.0 you never switch away from
|
|
386
|
+
is perfectly fine; one that shares a Ruby with projects you switch
|
|
387
|
+
between is a footgun.
|
|
388
|
+
|
|
389
|
+
## If you want Bundler to manage the version
|
|
390
|
+
|
|
391
|
+
The rule at the top of this chapter is about your *application's*
|
|
392
|
+
`Gemfile` — the one Bundler resolves against your app's gems, and whose
|
|
393
|
+
entries `Bundler.require` loads at boot. It is not a rule against
|
|
394
|
+
Bundler. A `Gemfile` holding nothing but Rigor, resolved separately and
|
|
395
|
+
selected with `BUNDLE_GEMFILE`, keeps every property the prohibition
|
|
396
|
+
exists to protect: your application's dependency graph never meets
|
|
397
|
+
Rigor's, and your application's Ruby is never constrained by Rigor's.
|
|
398
|
+
|
|
399
|
+
That arrangement — a `Gemfile` under `.github/rigor/`, plus the
|
|
400
|
+
Dependabot config that keeps it current — is written up under
|
|
401
|
+
[Pinning Rigor's version](11-ci.md#pinning-rigors-version) in the CI
|
|
402
|
+
chapter. Nothing about it is CI-specific:
|
|
403
|
+
|
|
404
|
+
```sh
|
|
405
|
+
BUNDLE_GEMFILE=.github/rigor/Gemfile bundle exec rigor check
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
It is more moving parts than `mise use --pin gem:rigortype`, which
|
|
409
|
+
records the same version in one line and needs no `bundle exec`. Reach
|
|
410
|
+
for it when your team already runs everything through Bundler and wants
|
|
411
|
+
Rigor on the same footing — not as a first choice.
|
|
412
|
+
|
|
319
413
|
## Nix
|
|
320
414
|
|
|
321
415
|
If you use Nix, Rigor's flake exposes the executable as a package,
|
|
@@ -41,12 +41,12 @@ the `paths:` list from the configuration file.
|
|
|
41
41
|
| `--cache-stats` | Print the on-disk cache inventory when finished. |
|
|
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
|
-
| `--workers=N` | Dispatch analysis across `N` parallel worker processes (fork-based pool today; ADR-15). Default `0` (sequential). |
|
|
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. |
|
|
45
45
|
| `--baseline=PATH` | Load a baseline file, overriding config. |
|
|
46
46
|
| `--no-baseline` | Ignore any configured baseline. |
|
|
47
47
|
| `--baseline-strict` | Fail the run on any baseline drift — a CI gate. |
|
|
48
48
|
| `--treat-all-as-inline-rbs` | Force-load `rigor-rbs-inline` with `require_magic_comment: false`, so every analysed file is treated as inline-RBS without the `# rbs_inline: enabled` comment (ADR-32). |
|
|
49
|
-
| `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids.
|
|
49
|
+
| `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids. Inspect it with [`rigor show-bleedingedge`](#rigor-show-bleedingedge). |
|
|
50
50
|
| `--no-bleeding-edge` | Ignore any configured `bleeding_edge:` selection for this run (adopt none). |
|
|
51
51
|
| `--tmp-file=PATH --instead-of=PATH` | Editor mode: analyse `PATH` using the buffer in `--tmp-file`. Both required together. |
|
|
52
52
|
|
|
@@ -183,6 +183,17 @@ rigor sig-gen [paths]
|
|
|
183
183
|
| `--new-files` / `--new-methods` / `--tighter-returns` | Emit only that classification. |
|
|
184
184
|
| `--format=text\|json` | Output format. |
|
|
185
185
|
|
|
186
|
+
Every signature is parsed before it is emitted. A method whose
|
|
187
|
+
generated RBS does not parse is **skipped** (`sig.skipped.unrenderable-rbs`)
|
|
188
|
+
and reported on stderr rather than written — an unparseable
|
|
189
|
+
`.rbs` is quarantined *whole* by `rigor check`, so one bad line
|
|
190
|
+
would take every other type in the file down with it. Under
|
|
191
|
+
`--write`, a file whose assembled content does not parse is
|
|
192
|
+
**refused** (the existing file is left untouched) and the command
|
|
193
|
+
exits `1`: you asked for a write and did not get one. Such a skip
|
|
194
|
+
is a bug in Rigor's RBS rendering, not in your code — please
|
|
195
|
+
report it.
|
|
196
|
+
|
|
186
197
|
## `rigor lsp`
|
|
187
198
|
|
|
188
199
|
Run the Language Server over stdio. See
|
|
@@ -400,6 +411,11 @@ Report the activation status of every plugin configured in
|
|
|
400
411
|
`.rigor.yml` — loaded, load-error (with reason), and each
|
|
401
412
|
plugin's declared extension surfaces. See [Plugins](07-plugins.md).
|
|
402
413
|
|
|
414
|
+
Each loaded plugin's row also reports the resolved file it
|
|
415
|
+
loaded from (a `path:` line in text, a `"path"` key in JSON), so
|
|
416
|
+
if a stale installed gem is shadowing a newer checkout's bundled
|
|
417
|
+
plugin copy the mismatch is visible at a glance.
|
|
418
|
+
|
|
403
419
|
```sh
|
|
404
420
|
rigor plugins [--format=text|json] [--strict] [--capabilities] [--config=PATH]
|
|
405
421
|
```
|
|
@@ -474,8 +490,9 @@ rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
|
|
|
474
490
|
| `--describe` | Probe the project's state (config / baseline / `sig/` / CI — presence only, never runs `rigor check`) and recommend the next skill to run. Also spelled `describe`; surfaced top-level as [`rigor describe`](#rigor-describe) below. |
|
|
475
491
|
|
|
476
492
|
The verb spellings `rigor skill list` / `print <name>` / `path <name>`
|
|
477
|
-
|
|
478
|
-
|
|
493
|
+
were **removed in v0.3.0** — the positional slot is a skill name, so
|
|
494
|
+
they now read as an unknown skill. Use the forms above.
|
|
495
|
+
`describe` / `--describe` stay first-class.
|
|
479
496
|
|
|
480
497
|
## `rigor describe`
|
|
481
498
|
|
|
@@ -518,8 +535,9 @@ rigor docs [<name>] [--path <name>] [--list [<category>]]
|
|
|
518
535
|
| `--path <name>` | Print the single-line absolute path of a doc, suitable as input to a file-reading tool. |
|
|
519
536
|
| `--list [<category>]` | Table of every bundled doc (name + absolute path); pass `manual` or `handbook` to filter. |
|
|
520
537
|
|
|
521
|
-
The verb spellings `rigor docs list` / `path <name>`
|
|
522
|
-
|
|
538
|
+
The verb spellings `rigor docs list` / `path <name>` were **removed in
|
|
539
|
+
v0.3.0** — the positional slot is a doc name, so they now read as an
|
|
540
|
+
unknown doc. Use `--list` / `--path`.
|
|
523
541
|
|
|
524
542
|
The canonical web copy of the index is
|
|
525
543
|
<https://rigor.typedduck.fail/llms.txt>; `rigor docs` serves the same
|
|
@@ -543,12 +561,16 @@ rigor show-bleedingedge [--config PATH] [--format text|json]
|
|
|
543
561
|
| `--config PATH` | Use this `.rigor.yml` instead of auto-discovery. |
|
|
544
562
|
| `--format text\|json` | Output format. Default `text`. |
|
|
545
563
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
564
|
+
Each queued feature appears with its stable id, the severity it imposes,
|
|
565
|
+
and whether your configuration adopts it. See
|
|
566
|
+
[`docs/compatibility.md`](../compatibility.md) for how bleeding-edge fits
|
|
567
|
+
the stability model.
|
|
568
|
+
|
|
569
|
+
Queued today:
|
|
570
|
+
|
|
571
|
+
| Feature id | What it changes |
|
|
572
|
+
| --- | --- |
|
|
573
|
+
| `reject-unparseable-signatures` | An unparseable `.rbs` under `signature_paths:` **fails the run** (`rbs.coverage.quarantined-signature` → `error`) instead of being skipped with a warning. |
|
|
552
574
|
|
|
553
575
|
## `rigor doctor`
|
|
554
576
|
|
|
@@ -575,6 +597,18 @@ Runs a scoped analysis and audits:
|
|
|
575
597
|
from the saved baseline.
|
|
576
598
|
- **Rails plugin gap** — whether `Gemfile.lock` contains Rails gems
|
|
577
599
|
but no Rails plugin is enabled.
|
|
600
|
+
- **Gemfile install** — whether Rigor itself resolves as one of the
|
|
601
|
+
project's dependencies, which
|
|
602
|
+
[Installing Rigor](01-installation.md) tells you not to do. Only a
|
|
603
|
+
`rigortype` resolved from a **GEM** remote counts: a `PATH` or `GIT`
|
|
604
|
+
source means you are developing or vendoring Rigor deliberately,
|
|
605
|
+
and Rigor's own repository looks exactly like that.
|
|
606
|
+
- **Plugin installation skew** — whether a plugin Rigor bundles
|
|
607
|
+
loaded from a different `rigortype` installation than the engine,
|
|
608
|
+
naming both paths. The engine and its bundled plugins are
|
|
609
|
+
versioned together, so a copy from another installation can run
|
|
610
|
+
the engine with a mismatched plugin. A warning, not a failure;
|
|
611
|
+
third-party plugins from your own bundle are never flagged.
|
|
578
612
|
|
|
579
613
|
Text output prints `[PASS]`, `[FAIL]`, or `[WARN]` per check plus a
|
|
580
614
|
routed hint (e.g. "Run `rigor baseline regenerate`"). JSON output
|
|
@@ -618,6 +652,9 @@ operational knobs read the environment instead.
|
|
|
618
652
|
| `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). |
|
|
619
653
|
| `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. |
|
|
620
654
|
| `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`. |
|
|
655
|
+
| `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). |
|
|
656
|
+
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through a long `check` / `coverage` 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. |
|
|
657
|
+
| `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. |
|
|
621
658
|
|
|
622
659
|
Three further variables (`RIGOR_BUDGET_TRACE`,
|
|
623
660
|
`RIGOR_HEAP_PROFILE`, `RIGOR_HEAP_TRACE`) enable developer-facing
|
|
@@ -22,6 +22,24 @@ discovery entirely.
|
|
|
22
22
|
All relative paths in a config file resolve against that
|
|
23
23
|
file's own directory.
|
|
24
24
|
|
|
25
|
+
## Editor validation
|
|
26
|
+
|
|
27
|
+
Rigor ships a JSON Schema for this file. Editors that
|
|
28
|
+
understand the [`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server)
|
|
29
|
+
magic comment — VS Code's YAML extension, the IntelliJ
|
|
30
|
+
family, Helix, Neovim with `yaml-ls` — give you
|
|
31
|
+
autocomplete, hover docs, and structural validation as you
|
|
32
|
+
type:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
# yaml-language-server: $schema=https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`rigor init` writes that line for you. The schema is not a
|
|
39
|
+
copy of this page — it is a source of truth in its own
|
|
40
|
+
right, kept in step with the loader by a spec, so a key it
|
|
41
|
+
rejects is a key Rigor does not accept.
|
|
42
|
+
|
|
25
43
|
## A minimal config
|
|
26
44
|
|
|
27
45
|
```yaml
|
|
@@ -44,6 +62,7 @@ cache:
|
|
|
44
62
|
| `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
45
63
|
| `includes` | Array | `[]` | Other config files to layer underneath this one. |
|
|
46
64
|
| `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
|
|
65
|
+
| `parameter_inference` | Boolean | `false` | Opt-in call-site parameter type inference on the `check` walk ([ADR-67](../adr/67-parameter-type-inference.md) WD6). When `true`, an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved call-site argument types, sharpening downstream ivar reads, folds, and protection coverage. Precision-additive only — the negative rules never fire against an inferred parameter. Cannot be combined with `--incremental`. |
|
|
47
66
|
|
|
48
67
|
### Type sources
|
|
49
68
|
|
|
@@ -65,6 +84,7 @@ types it was meant to describe into a high-confidence
|
|
|
65
84
|
of real type errors. The audit covers:
|
|
66
85
|
|
|
67
86
|
```
|
|
87
|
+
rigor: `excludee` is not a recognized configuration key; it has no effect. Did you mean `exclude`?
|
|
68
88
|
rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from it)
|
|
69
89
|
rigor: signature_paths: "/path/to/sig" matched 0 signature files
|
|
70
90
|
rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
|
|
@@ -73,6 +93,11 @@ rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the overrid
|
|
|
73
93
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
74
94
|
```
|
|
75
95
|
|
|
96
|
+
The unrecognised-key check covers **top-level** keys, and skips
|
|
97
|
+
the namespaces reserved for other implementations (see below).
|
|
98
|
+
A typo *inside* a group — `cache: { pth: … }` — is caught by
|
|
99
|
+
the JSON schema as you type rather than at check time.
|
|
100
|
+
|
|
76
101
|
These are warnings, not errors — partial or optional bundles and
|
|
77
102
|
forward-looking config are valid setups. The audit only fires on explicit,
|
|
78
103
|
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
@@ -91,7 +116,7 @@ statically and may resolve at run time. The same findings appear in the
|
|
|
91
116
|
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
92
117
|
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
93
118
|
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
94
|
-
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines 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).
|
|
119
|
+
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines 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). |
|
|
95
120
|
|
|
96
121
|
### Dependency RBS discovery
|
|
97
122
|
|
|
@@ -128,11 +153,18 @@ explicitly with `bundler.bundle_path:`, or supply signatures another way:
|
|
|
128
153
|
| --- | --- | --- | --- |
|
|
129
154
|
| `cache.path` | String | `.rigor/cache` | Persistent cache directory. See [Caching](12-caching.md). |
|
|
130
155
|
| `cache.max_bytes` | Integer or `null` | `268435456` (256 MB) | LRU eviction cap for the cache directory; `null` disables eviction. See [Caching § Size and eviction](12-caching.md#size-and-eviction). |
|
|
131
|
-
| `
|
|
156
|
+
| `cache.validation` | String | `"auto"` | How the cache checks whether a file is unchanged: `auto` behaves as `digest` when a CI environment is detected and as `stat` otherwise; `stat` compares size + nanosecond timestamps + inode and only re-hashes a file whose stat moved; `digest` re-hashes every file's content every run. Both keep the content hash as the sole change authority — `stat` just skips the hash when the stat proves a file untouched. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). The `RIGOR_STRICT_VALIDATION=1` environment variable forces `digest` for one run and wins over this key; `RIGOR_CI_DETECT=0` disables the CI detection. |
|
|
157
|
+
| `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. Applies to `--incremental` re-checks as well as full runs. |
|
|
132
158
|
| `plugins_io.network` | String | `"disabled"` | Plugin network policy — `disabled` or `allowlist`. |
|
|
133
159
|
| `plugins_io.allowed_paths` | Array | `[]` | Filesystem paths plugins may read. |
|
|
134
160
|
| `plugins_io.allowed_url_hosts` | Array | `[]` | URL hosts plugins may fetch from when `network: allowlist`. |
|
|
135
161
|
|
|
162
|
+
### Reserved for other implementations
|
|
163
|
+
|
|
164
|
+
| Key | Type | Default | Meaning |
|
|
165
|
+
| --- | --- | --- | --- |
|
|
166
|
+
| `rigor_rs` | Hash | — | **Reserved; this implementation skips it.** Another Rigor implementation reads keys under this namespace, so one `.rigor.yml` can serve both. Rigor validates its shape at the schema level only — it never reads the value, and an invalid one is never a runtime error here. Leave it alone unless the tool that reads it tells you otherwise. |
|
|
167
|
+
|
|
136
168
|
## A worked example
|
|
137
169
|
|
|
138
170
|
```yaml
|
|
@@ -41,18 +41,26 @@ carries no `documentation_url`.
|
|
|
41
41
|
| <a id="rule-call-wrong-arity"></a>`call.wrong-arity` | The positional-argument count matches no signature. | high |
|
|
42
42
|
| <a id="rule-call-argument-type-mismatch"></a>`call.argument-type-mismatch` | An argument's type provably violates the parameter contract. | high |
|
|
43
43
|
| <a id="rule-call-possible-nil-receiver"></a>`call.possible-nil-receiver` | The receiver is `T \| nil` and the method is not defined on `NilClass`. | high |
|
|
44
|
+
| <a id="rule-call-raise-non-exception"></a>`call.raise-non-exception` | A `raise` / `fail` argument's concrete type is provably not an Exception class, an Exception instance, a String, or an object defining `#exception` — a runtime `TypeError`. | high |
|
|
44
45
|
| <a id="rule-call-unresolved-toplevel"></a>`call.unresolved-toplevel` | A top-level implicit-self call resolves against no same-file `def`, `pre_eval:` patch, or `Kernel` / `Object` method. | low |
|
|
45
46
|
| <a id="rule-flow-always-raises"></a>`flow.always-raises` | The expression provably raises on every reachable path. | high |
|
|
46
47
|
| <a id="rule-flow-unreachable-branch"></a>`flow.unreachable-branch` | An `if` / `unless` / ternary branch is statically dead. | high |
|
|
47
48
|
| <a id="rule-flow-always-truthy-condition"></a>`flow.always-truthy-condition` | A condition is provably always truthy or always falsey. | medium |
|
|
48
49
|
| <a id="rule-flow-dead-assignment"></a>`flow.dead-assignment` | A local is written but never read in the same method. | medium |
|
|
49
50
|
| <a id="rule-flow-unreachable-clause"></a>`flow.unreachable-clause` | A `case`/`when` or `case`/`in` clause is statically dead — its subject type is disjoint with the pattern, or a prior clause already exhausted the subject. | medium |
|
|
51
|
+
| <a id="rule-flow-duplicate-hash-key"></a>`flow.duplicate-hash-key` | A Hash literal repeats a literal key (symbol, plain string, integer, float, `true`/`false`/`nil`) — the last entry silently overwrites the earlier one at runtime. Literal keys only; symbol vs string and `1` vs `1.0` never collide, and interpolated / constant / computed keys are never compared. A `**splat` between two identical literal keys does not rescue the pair. | high |
|
|
52
|
+
| <a id="rule-flow-return-in-ensure"></a>`flow.return-in-ensure` | An explicit `return` inside an `ensure` clause — it overrides the method's in-flight return value and silently swallows any in-flight exception. A `return` in a nested `def`, lambda, or `define_method` block inside the `ensure` does not fire (it exits that inner frame). | high |
|
|
53
|
+
| <a id="rule-flow-shadowed-rescue-clause"></a>`flow.shadowed-rescue-clause` | A `rescue` clause can never run because an earlier clause of the same chain already catches a superclass (or the same class) of every exception class it names. | high |
|
|
50
54
|
| <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
|
|
51
55
|
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write. | high |
|
|
52
56
|
| <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
|
|
53
57
|
| <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
|
|
54
58
|
| <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
|
|
55
59
|
| <a id="rule-def-override-param-narrowed"></a>`def.override-param-narrowed` | An override narrows an inherited parameter type (contravariance). | high |
|
|
60
|
+
| <a id="rule-static-value-use-void"></a>`static.value-use.void` | A value recovered from an author-declared `-> void` return is used in value context (an assignment right-hand side, a call receiver, or a call argument). Off by default; reaches a run only through the `use-of-void-value` bleeding-edge feature (ADR-100). A bare-statement `void` call and a legitimate `top` value both stay silent. | high |
|
|
61
|
+
| <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
|
|
62
|
+
| <a id="rule-suppression-empty"></a>`suppression.empty` | A `# rigor:disable[-file]` comment lists no rules, so it suppresses nothing. | high |
|
|
63
|
+
| <a id="rule-suppression-unknown-marker"></a>`suppression.unknown-marker` | A comment uses a suppression marker Rigor does not recognise — typically the RuboCop reflex `# rigor:disable-next-line <rule>` or `# rigor:enable <rule>`. Rigor's only markers are `# rigor:disable <rules>` (suppresses on its own line) and `# rigor:disable-file <rules>`, so the comment suppresses nothing. | high |
|
|
56
64
|
| <a id="rule-rbs_extended-unsatisfied-conformance"></a>`rbs_extended.unsatisfied-conformance` | A class declares `%a{rigor:v1:conforms-to _Interface}` in its RBS but is missing a method the interface requires. Presence-based: only definitively-absent required methods fire. | — |
|
|
57
65
|
| <a id="rule-assert-type-mismatch"></a>`assert.type-mismatch` | An `assert_type` expectation does not match the inferred type. | high |
|
|
58
66
|
| <a id="rule-dump-type"></a>`dump.type` | A `dump_type` call — informational, prints the inferred type. | — |
|
|
@@ -192,6 +200,21 @@ config.merge(extra) # rigor:disable call.undefined-method
|
|
|
192
200
|
It accepts qualified IDs, family wildcards (`call`), a
|
|
193
201
|
comma- or space-separated list, or `all`.
|
|
194
202
|
|
|
203
|
+
A marker that cannot work is flagged rather than silently
|
|
204
|
+
ignored: a token that names no known rule (a typo like
|
|
205
|
+
`call.undefined-metod`) fires
|
|
206
|
+
[`suppression.unknown-rule`](#rule-suppression-unknown-rule),
|
|
207
|
+
a bare marker with no rules at all fires
|
|
208
|
+
[`suppression.empty`](#rule-suppression-empty), and a marker
|
|
209
|
+
word outside Rigor's grammar (the RuboCop reflex
|
|
210
|
+
`# rigor:disable-next-line <rule>`, or `# rigor:enable`)
|
|
211
|
+
fires
|
|
212
|
+
[`suppression.unknown-marker`](#rule-suppression-unknown-marker)
|
|
213
|
+
— all `:warning` in every profile. Tokens under the `plugin.`
|
|
214
|
+
prefix are never flagged (plugin rule vocabularies load
|
|
215
|
+
dynamically), and the surveillance diagnostics are themselves
|
|
216
|
+
suppressible like any other rule.
|
|
217
|
+
|
|
195
218
|
**In-source, whole file.** `# rigor:disable-file <rules>`
|
|
196
219
|
anywhere in a file suppresses those rules for every line;
|
|
197
220
|
`# rigor:disable-file all` silences the file.
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -40,10 +40,10 @@ each plugin's options — but the families today are:
|
|
|
40
40
|
- **Rails** — `rigor-activerecord`, `rigor-actionpack`,
|
|
41
41
|
`rigor-rails-routes`, `rigor-rails-i18n`,
|
|
42
42
|
`rigor-actionmailer`, `rigor-activejob`,
|
|
43
|
-
`rigor-activestorage`, `rigor-actioncable`.
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
`rigor-activestorage`, `rigor-actioncable`. Enumerate the ones
|
|
44
|
+
you want under `plugins:` — there is no umbrella entry that
|
|
45
|
+
turns on the Rails set as a group
|
|
46
|
+
([ADR-96](../adr/96-plugin-target-gems.md) WD3 proposes one).
|
|
47
47
|
- **Testing** — `rigor-rspec`, `rigor-rspec-rails`,
|
|
48
48
|
`rigor-minitest`, `rigor-shoulda-matchers`,
|
|
49
49
|
`rigor-factorybot`.
|
|
@@ -201,8 +201,8 @@ For a legacy bundler-based install, swap the connection list to
|
|
|
201
201
|
analyzer assigns the receiver.
|
|
202
202
|
- Mid-edit buffer support is best-effort. If parse fails AND the
|
|
203
203
|
cursor isn't right after `.` / `::`, the v1 LSP returns no
|
|
204
|
-
completions; deeper recovery is queued (see
|
|
205
|
-
|
|
204
|
+
completions; deeper recovery is queued (see the open
|
|
205
|
+
[`area:editor` issues](https://github.com/rigortype/rigor/issues?q=is%3Aissue+is%3Aopen+label%3Aarea%3Aeditor)).
|
|
206
206
|
|
|
207
207
|
**Hover shows `untyped` everywhere.**
|
|
208
208
|
|
|
@@ -236,8 +236,8 @@ LSP v1 + v2 landed in v0.1.6 and ship in the `0.1.x` line. Queued
|
|
|
236
236
|
follow-ups (`textDocument/signatureHelp`, hash-key completion,
|
|
237
237
|
`textDocument/definition`, incremental `didChange` sync, Ractor
|
|
238
238
|
pool dispatch, codeAction / rename / semanticTokens / inlayHint)
|
|
239
|
-
are demand-driven;
|
|
240
|
-
|
|
239
|
+
are demand-driven; the current queue is the open
|
|
240
|
+
[`area:editor` issues](https://github.com/rigortype/rigor/issues?q=is%3Aissue+is%3Aopen+label%3Aarea%3Aeditor).
|
|
241
241
|
|
|
242
242
|
To request a queued feature or report an LSP issue, open a GitHub
|
|
243
243
|
issue with: the editor + version, the Rigor version
|