rigortype 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +28 -5
- data/docs/manual/02-cli-reference.md +23 -4
- data/docs/manual/03-configuration.md +21 -8
- data/docs/manual/04-diagnostics.md +44 -2
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +32 -3
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +26 -9
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/rule_ids.rb +64 -0
- data/lib/rigor/analysis/check_rules.rb +68 -57
- data/lib/rigor/analysis/effects_cache_probe.rb +1 -1
- data/lib/rigor/analysis/incremental.rb +12 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +3 -3
- data/lib/rigor/analysis/incremental_session.rb +207 -40
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +139 -15
- data/lib/rigor/analysis/reachability/scan.rb +374 -173
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +6 -1
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +6 -4
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +74 -13
- data/lib/rigor/analysis/runner/pool_coordinator.rb +133 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +46 -1
- data/lib/rigor/analysis/runner.rb +62 -18
- data/lib/rigor/analysis/severity_stamp.rb +34 -1
- data/lib/rigor/analysis/worker_session.rb +58 -2
- data/lib/rigor/cache/descriptor.rb +40 -6
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +56 -12
- data/lib/rigor/cli/check_command.rb +138 -39
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +157 -24
- data/lib/rigor/cli.rb +30 -4
- data/lib/rigor/config_audit.rb +111 -15
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration.rb +15 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_loader.rb +79 -10
- data/lib/rigor/environment/required_features.rb +4 -2
- data/lib/rigor/environment.rb +12 -5
- data/lib/rigor/inference/block_call_timing.rb +5 -0
- data/lib/rigor/inference/builtins/cruby_definers.rb +22 -5
- data/lib/rigor/inference/captured_locals.rb +69 -6
- data/lib/rigor/inference/definer_resolution.rb +16 -7
- data/lib/rigor/inference/expression_typer.rb +117 -29
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +21 -17
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -0
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -16
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +52 -3
- data/lib/rigor/inference/module_function_state.rb +80 -13
- data/lib/rigor/inference/narrowing.rb +184 -68
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +38 -24
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +112 -40
- data/lib/rigor/inference/refined_dispatch.rb +62 -14
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +416 -1
- data/lib/rigor/inference/scope_indexer.rb +565 -61
- data/lib/rigor/inference/self_call_ivar_writes.rb +341 -0
- data/lib/rigor/inference/statement_evaluator.rb +307 -60
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/language_server/project_context.rb +56 -5
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +133 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +802 -0
- data/lib/rigor/lens/members.rb +203 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +322 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +41 -0
- data/lib/rigor/mcp/server.rb +61 -20
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/project_environment.rb +23 -1
- data/lib/rigor/protection/mutation_cache.rb +2 -1
- data/lib/rigor/scope.rb +33 -17
- data/lib/rigor/sig_gen/generator.rb +45 -5
- data/lib/rigor/sig_gen/layout_index.rb +36 -0
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/writer.rb +47 -34
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +28 -6
- 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-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/sig/rigor/scope.rbs +3 -0
- data/sig/rigor.rbs +6 -3
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a09068aa2810a8d01758e3ce78f52dc6020f0d197bb83a561684b96969e5eddf
|
|
4
|
+
data.tar.gz: 0105753f063477bea3a85ba3dea3fe660fbe58d846d9388867eafd3b6fed1055
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb111fc503dbb60810cbcdf4bfb0a717f694f771fe16bf41007ef1b9eb2728014aa19d112183e79652e4a1ae03c09d6e227ad13ebc063a9804b7a6ba916ae71b
|
|
7
|
+
data.tar.gz: 4c3d925ef588248fbd84529262632bcfa5e081eedd340c164f0de89b3a0f83abd5cdaad37be922a90571068966ec204f77e075993994db8b106abbaa8d135cd8
|
data/README.md
CHANGED
|
@@ -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.4`** (2026-10-16) — 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
|
|
@@ -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
|
@@ -188,11 +188,11 @@ changes which calls the signature accepts:
|
|
|
188
188
|
$ rigor sig-gen --diff lib/c.rb
|
|
189
189
|
--- lib/c.rb: C#m
|
|
190
190
|
- def m: (Integer x) -> untyped
|
|
191
|
-
+ def m: (Integer x) ->
|
|
191
|
+
+ def m: (Integer x) -> Float
|
|
192
192
|
|
|
193
193
|
--- lib/c.rb: C#n
|
|
194
194
|
- def n: (?) -> untyped
|
|
195
|
-
+ def n: (?) ->
|
|
195
|
+
+ def n: (?) -> Float
|
|
196
196
|
```
|
|
197
197
|
|
|
198
198
|
The `-` line is the declaration the proposal replaces. A method
|
|
@@ -523,9 +523,16 @@ Neither does a declaration the body proves as a literal.
|
|
|
523
523
|
-> String` whose body is the string `"bot"`: the declared
|
|
524
524
|
type is your abstraction over that body, and pinning the
|
|
525
525
|
literal would rewrite a contract every sibling class shares.
|
|
526
|
-
A
|
|
527
|
-
|
|
528
|
-
|
|
526
|
+
A declared `untyped` that you wrote in `sig/` is treated
|
|
527
|
+
the same way for a value literal: an Integer, String or
|
|
528
|
+
Symbol the body proves is widened to its class (`-> 1`
|
|
529
|
+
becomes `-> Integer`, `-> ("S" | nil)` becomes
|
|
530
|
+
`-> (String | nil)`), while `nil`, `true`, `false` and a
|
|
531
|
+
computed `Float` are proposed as they are. An `untyped` that
|
|
532
|
+
only the inline reader synthesizes for a `def` you did not
|
|
533
|
+
annotate keeps the literal, as does a method no `.rbs`
|
|
534
|
+
declares — the literal is still the strictest thing the body
|
|
535
|
+
proves, and that is what gets written.
|
|
529
536
|
|
|
530
537
|
A comment rather than a `%a{…}` annotation, for three
|
|
531
538
|
reasons: it stays out of the `rigor:v1:` directive namespace
|
|
@@ -744,6 +751,22 @@ by side without coordination.
|
|
|
744
751
|
every other byte of the file verbatim.
|
|
745
752
|
- **Will** append a new `class Foo … end` block when the
|
|
746
753
|
target file does not declare the class yet.
|
|
754
|
+
- **Will** treat a method as already declared when a file
|
|
755
|
+
under the signature tree declares it on the same class
|
|
756
|
+
with a `def` or an `attr_*` of the same kind (instance or
|
|
757
|
+
singleton), in any opening of the class in that file. A
|
|
758
|
+
`def self?.` declares both kinds, and a `def self?.`
|
|
759
|
+
proposal counts as declared when either kind is. The
|
|
760
|
+
method is updated (`--overwrite`) or skipped in the file
|
|
761
|
+
that declares it instead of being added to the class's
|
|
762
|
+
first file. A replacement lands only on a `def` of the
|
|
763
|
+
proposal's own kind in the first opening of the class:
|
|
764
|
+
a `def self?.` proposal over a lone `def self.f` or
|
|
765
|
+
`def f`, a plain proposal over a `def self?.f`, or a
|
|
766
|
+
method declared in a later opening is skipped. A
|
|
767
|
+
`def f: ... | ...` overload is never replaced and never
|
|
768
|
+
counts as the file that declares `f`. An `alias` in
|
|
769
|
+
`sig/` is not yet recognised as declaring its name.
|
|
747
770
|
- **Will not** touch files outside the configured signature
|
|
748
771
|
tree.
|
|
749
772
|
- **Will not** replace an existing method declaration
|
|
@@ -36,7 +36,7 @@ the `paths:` list from the configuration file.
|
|
|
36
36
|
| `--explain` | Surface fail-soft fallback events as `info` diagnostics. |
|
|
37
37
|
| `--no-cache` | Skip the persistent cache for this run. With `--incremental` it is a cold full analysis: the incremental snapshot and the run-result cache are neither read nor written, so the next plain `--incremental` run still uses the snapshot it had before. |
|
|
38
38
|
| `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. A run with nothing changed is answered from the cache without starting the analyzer. See [Caching](12-caching.md). |
|
|
39
|
-
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics
|
|
39
|
+
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics agree as a set and in the same order, then exit (0 on match, 1 on mismatch: the differing diagnostics for a set mismatch, or the first index where the order diverges for a same-set, different-order mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
|
|
40
40
|
| `--clear-cache` | Delete the cache directory before running. |
|
|
41
41
|
| `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
|
|
42
42
|
| `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
|
|
@@ -171,6 +171,24 @@ the two builds against each other — but if you are chasing a
|
|
|
171
171
|
disagreement between a probe and a `check` run, comparing
|
|
172
172
|
against `rigor check --no-cache` removes that variable.
|
|
173
173
|
|
|
174
|
+
## `rigor lens`
|
|
175
|
+
|
|
176
|
+
Map the declarations of one or more files with their types and
|
|
177
|
+
type provenance (experimental). See
|
|
178
|
+
[Inspecting inferred types](05-inspecting-types.md).
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
rigor lens [--format=text|json] [--config=PATH] [--workers=N] FILE...
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A lens runs the same incremental analysis as
|
|
185
|
+
`rigor check --incremental` and reads and writes its cache.
|
|
186
|
+
`--workers` applies to a cold run. `--format=json` writes one
|
|
187
|
+
`lens/v0` document ([specification](../internal-spec/lens.md)).
|
|
188
|
+
Exit `0` when at least one file has a result, `1` when none
|
|
189
|
+
does, `64` on a usage error; diagnostics in the code never
|
|
190
|
+
change the exit status.
|
|
191
|
+
|
|
174
192
|
## `rigor trace`
|
|
175
193
|
|
|
176
194
|
Replay HOW the engine typed a file, step by step, as a
|
|
@@ -895,10 +913,11 @@ rigor mcp [--transport=stdio] [--config=PATH]
|
|
|
895
913
|
```
|
|
896
914
|
|
|
897
915
|
`stdio` is the only transport. The server is a pure-Ruby
|
|
898
|
-
JSON-RPC 2.0 implementation exposing
|
|
899
|
-
`rigor_check`, `rigor_type_of`, `rigor_triage`,
|
|
916
|
+
JSON-RPC 2.0 implementation exposing eight read-only tools:
|
|
917
|
+
`rigor_check`, `rigor_type_of`, `rigor_triage`, `rigor_lens`,
|
|
900
918
|
`rigor_annotate`, `rigor_sig_gen`, `rigor_explain`,
|
|
901
|
-
`rigor_coverage`.
|
|
919
|
+
`rigor_coverage`. `rigor_lens` writes only the incremental
|
|
920
|
+
cache `rigor check --incremental` writes.
|
|
902
921
|
|
|
903
922
|
## `rigor lsp` vs `rigor mcp`
|
|
904
923
|
|
|
@@ -19,8 +19,10 @@ To *layer* configs rather than replace, a config file can name
|
|
|
19
19
|
a base with `includes:` (recursive). `--config=PATH` bypasses
|
|
20
20
|
discovery entirely.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
Relative paths in a config file resolve against that file's own directory, with three exceptions:
|
|
23
|
+
`bundler.bundle_path:`, `bundler.lockfile:` and `rbs_collection.lockfile:` resolve against the working directory
|
|
24
|
+
`rigor` runs from. They match only when you run `rigor` from the directory that holds the config file, which is
|
|
25
|
+
the usual case; with `--config path/to/.rigor.yml` from elsewhere, give these three as absolute paths.
|
|
24
26
|
A leading `~/` (or a bare `~`) in a path setting expands to your home directory; `~name` is an ordinary
|
|
25
27
|
directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `test_paths:`,
|
|
26
28
|
`pre_eval:`, `plugins_io.allowed_paths:`, `includes:`, `bundler.bundle_path:`, `bundler.lockfile:` and
|
|
@@ -64,7 +66,7 @@ cache:
|
|
|
64
66
|
| --- | --- | --- | --- |
|
|
65
67
|
| `target_ruby` | String | `"4.0"` | The lowest Ruby version *your* project runs on — `"X.Y"`, `"X.Y.Z"`, or `"latest"`. Independent of the Ruby Rigor itself runs on. Every value selects the syntax Rigor parses. A version you set explicitly also turns on the Ruby-deprecation rules for that version ([`call.deprecated-ruby2-keywords`](04-diagnostics.md#rule-call-deprecated-ruby2-keywords) from `"4.1"`); the default and `"latest"` never do, which is why `rigor init` writes the key commented out. |
|
|
66
68
|
| `paths` | Array | `["lib"]` | Directories or files to analyse. |
|
|
67
|
-
| `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
69
|
+
| `exclude` | Array | `[]` | Glob patterns to skip. A relative pattern is matched against the file's path relative to the directory of the config file in use (the working directory when no config file is used), so it excludes the same files whether the path came from `paths:` or from a command-line argument, and it never matches on a directory above that base (a file outside it is seen as `../…`, so `../shared/lib/gen/**` reaches a sibling directory that `paths:` names). A leading `./` is ignored, and a pattern starting with `.` (such as `.*`) also matches files outside the base, which appear as `../…`. A symlink to a file is matched only under its own name: the realpath pass resolves directories, not files. `*` and `**` also cross `/`, so `lib/*.rb` matches nested files too. An absolute pattern is matched against the absolute path. Patterns from an `includes:` file resolve against the top-level config's directory, not the included file's. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
68
70
|
| `test_paths` | Array | `nil` | The project's test roots: the directories (or files) holding its tests. Relative entries resolve against the config file's directory. Unset auto-detects whichever of `spec/` and `test/` exist; `[]` declares none. `rigor sig-gen --params=observed` reads call sites there to type parameters, and names on stderr a declared root that does not exist. Test roots are not analysed unless `paths:` also lists them, and changing them invalidates no cache. |
|
|
69
71
|
| `includes` | Array | `[]` | Other config files to layer underneath this one. |
|
|
70
72
|
| `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
|
|
@@ -76,7 +78,7 @@ cache:
|
|
|
76
78
|
| --- | --- | --- | --- |
|
|
77
79
|
| `libraries` | Array | `[]` | Standard-library / gem names whose bundled RBS to load. |
|
|
78
80
|
| `signature_paths` | Array | `nil` | Extra directories of `.rbs` files. Relative entries resolve against the config file's directory. |
|
|
79
|
-
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. |
|
|
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. |
|
|
80
82
|
| `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
|
|
81
83
|
|
|
82
84
|
### Signature generation
|
|
@@ -101,6 +103,8 @@ rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from
|
|
|
101
103
|
rigor: signature_paths: "/path/to/sig" matched 0 signature files
|
|
102
104
|
rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
|
|
103
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
|
|
104
108
|
rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the override has no effect
|
|
105
109
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
106
110
|
```
|
|
@@ -123,10 +127,19 @@ the JSON schema as you type rather than at check time.
|
|
|
123
127
|
These are warnings, not errors — partial or optional bundles and
|
|
124
128
|
forward-looking config are valid setups. The audit only fires on explicit,
|
|
125
129
|
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
126
|
-
auto-detected bundle) is never warned about, and a
|
|
127
|
-
`severity_overrides:`
|
|
130
|
+
auto-detected bundle) is never warned about, and a
|
|
131
|
+
`severity_overrides:` key under a non-built-in family (`rspec.…`,
|
|
128
132
|
`rbs_extended.…`) is left alone, since its rule id cannot be enumerated
|
|
129
|
-
statically and may resolve at run time.
|
|
133
|
+
statically and may resolve at run time. `disable:` is checked more
|
|
134
|
+
closely, because what it 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 token under `rbs_extended.…`, `rbs.…` or `dynamic.…` is left
|
|
141
|
+
alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
142
|
+
`dynamic.dependency-source.…`), which are flagged. The same findings appear in the
|
|
130
143
|
`--format=json` payload under `config_warnings` (each tagged with a
|
|
131
144
|
`kind`), so CI can assert on them.
|
|
132
145
|
|
|
@@ -134,7 +147,7 @@ statically and may resolve at run time. The same findings appear in the
|
|
|
134
147
|
|
|
135
148
|
| Key | Type | Default | Meaning |
|
|
136
149
|
| --- | --- | --- | --- |
|
|
137
|
-
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide. |
|
|
150
|
+
| `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). |
|
|
138
151
|
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
139
152
|
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
140
153
|
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
@@ -253,7 +253,12 @@ config.merge(extra) # rigor:disable call.undefined-method
|
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
It accepts qualified IDs, family wildcards (`call`), a
|
|
256
|
-
comma- or space-separated list, or `all`.
|
|
256
|
+
comma- or space-separated list, or `all`. A plugin's diagnostic
|
|
257
|
+
is reached only by its qualified ID as printed in brackets
|
|
258
|
+
(`plugin.shoulda-matchers.unknown-column`) or by the plugin's
|
|
259
|
+
whole family (`plugin.shoulda-matchers`); its bare rule
|
|
260
|
+
(`unknown-column`) and `all` leave it alone, because several
|
|
261
|
+
plugins share bare rules. The comment must sit
|
|
257
262
|
on the line the diagnostic points at; there is no
|
|
258
263
|
`disable-block` form, so an expression spread over several
|
|
259
264
|
lines needs the comment on each line that fires.
|
|
@@ -283,7 +288,8 @@ suppressible like any other rule.
|
|
|
283
288
|
|
|
284
289
|
**In-source, whole file.** `# rigor:disable-file <rules>`
|
|
285
290
|
anywhere in a file suppresses those rules for every line;
|
|
286
|
-
`# rigor:disable-file all` silences
|
|
291
|
+
`# rigor:disable-file all` silences every built-in rule in the file
|
|
292
|
+
(a plugin's diagnostics need their qualified ID, as above). Convention is to
|
|
287
293
|
put it near the top — typically on a generated file, a fixture,
|
|
288
294
|
or a vendored snippet — but every comment in the file is
|
|
289
295
|
scanned, so any placement works.
|
|
@@ -300,6 +306,42 @@ disable:
|
|
|
300
306
|
- flow.dead-assignment
|
|
301
307
|
```
|
|
302
308
|
|
|
309
|
+
`disable: [all]` turns off every built-in check rule, including
|
|
310
|
+
`effect.annotations-unchecked`, which is reported at a signature
|
|
311
|
+
file.
|
|
312
|
+
|
|
313
|
+
A plugin's own diagnostics are turned off by their qualified ID —
|
|
314
|
+
`plugin.<id>.<rule>` for one rule, `plugin.<id>` for every rule
|
|
315
|
+
of that plugin:
|
|
316
|
+
|
|
317
|
+
```yaml
|
|
318
|
+
disable:
|
|
319
|
+
- plugin.shoulda-matchers.unknown-column
|
|
320
|
+
- plugin.rails-i18n
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
A bare rule (`unknown-column`, `load-error`) and `all` do not
|
|
324
|
+
reach them, so one plugin's suppression never hides another
|
|
325
|
+
plugin's finding of the same name.
|
|
326
|
+
|
|
327
|
+
The list does not reach the run-level rows reported at
|
|
328
|
+
`.rigor.yml` — a plugin that failed to load or raised in
|
|
329
|
+
`prepare` (`plugin_loader.*`), a `dependencies.source_inference:`
|
|
330
|
+
gem that did not resolve (`dynamic.dependency-source.*`), the
|
|
331
|
+
`rbs.coverage.*` notices, `plugin_trust.*`, `pool-degraded` —
|
|
332
|
+
under any spelling, `plugin_loader.load-error` included. Turn
|
|
333
|
+
those off with [`severity_overrides:`](#severity-profiles) set
|
|
334
|
+
to `off`, or record them in a [baseline](06-baseline.md).
|
|
335
|
+
`severity_overrides:` matches the row's rule ID without the
|
|
336
|
+
source prefix, so the plugin loader's rows are `load-error` and
|
|
337
|
+
`runtime-error` there — and that key also re-stamps any plugin
|
|
338
|
+
diagnostic whose bare rule is the same. `rigor check` warns when a
|
|
339
|
+
`disable:` entry names one of these rows, names no rule at all,
|
|
340
|
+
or names a plugin `plugins:` does not configure (see
|
|
341
|
+
[Config validation warnings](03-configuration.md#config-validation-warnings)).
|
|
342
|
+
A path or parse error, which has no rule ID, is always
|
|
343
|
+
reported.
|
|
344
|
+
|
|
303
345
|
For a *known backlog* you want to keep visible but not fail
|
|
304
346
|
on, prefer a [baseline](06-baseline.md) over a blanket
|
|
305
347
|
`disable:` — `disable:` also hides any new occurrences.
|
|
@@ -89,6 +89,14 @@ array. Line queries add `line_enumerations` metadata with the
|
|
|
89
89
|
shown and total expression counts. An exact position is the same
|
|
90
90
|
query the editor integration answers on hover.
|
|
91
91
|
|
|
92
|
+
Inside a `rescue` or `ensure` clause, `type-of` and hover can
|
|
93
|
+
disagree with `rigor check`. `check` reads a variable that the
|
|
94
|
+
`begin` body assigns as untyped there, because the clause may run
|
|
95
|
+
after the body raised partway through. `type-of` shows the value
|
|
96
|
+
the variable had before the `begin`. In
|
|
97
|
+
`conn = nil; begin; conn = open_conn; work; rescue; conn.close; end`,
|
|
98
|
+
`type-of` reports `conn` as `nil` and `check` reports nothing.
|
|
99
|
+
|
|
92
100
|
## `rigor trace` — watch the inference happen
|
|
93
101
|
|
|
94
102
|
Where `annotate` and `type-of` show the *answer*, `rigor trace
|
|
@@ -110,11 +118,53 @@ typer visits; the default keeps only the three teachable event
|
|
|
110
118
|
kinds. The JSON stream is stable enough to build course
|
|
111
119
|
material or figures from.
|
|
112
120
|
|
|
121
|
+
## `rigor lens` — a file's declarations and where their types come from
|
|
122
|
+
|
|
123
|
+
`rigor lens` lists every class, module, method and attribute a
|
|
124
|
+
file declares, with the type each one has and where that type
|
|
125
|
+
came from: `sig/`, an inline `#:` / `@rbs` comment, a plugin, a
|
|
126
|
+
library, or Rigor's inference from the body. It is experimental:
|
|
127
|
+
the command and its `lens/v0` JSON may still change.
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
rigor lens lib/shop.rb # text, for reading
|
|
131
|
+
rigor lens --format=json lib/*.rb # one JSON document
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
lib/shop.rb xxh3-64:845f7426b6acfe12 (2 omitted)
|
|
136
|
+
4:7c90 class Shop
|
|
137
|
+
8:e37e Shop#label: (::Integer n) -> ::String [contract inline]
|
|
138
|
+
14:ce93 Shop#total: () -> Integer [single_site inferred]
|
|
139
|
+
16:182b Shop#fetch [contested; also lib/legacy_shop.rb:2]
|
|
140
|
+
omitted constant Shop::LIMIT (line 5)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The answer is the one `rigor check` reaches. A lens runs the
|
|
144
|
+
same incremental analysis as `rigor check --incremental` and
|
|
145
|
+
shares its cache, so the first run costs a full check and later
|
|
146
|
+
runs reuse it. The diagnostics it reports for a file are the
|
|
147
|
+
ones `check` reports.
|
|
148
|
+
|
|
149
|
+
- A method Ruby could take from more than one definition (the
|
|
150
|
+
same method defined in two files) is `contested`. Rigor does
|
|
151
|
+
not guess which one runs, so it prints no signature for it.
|
|
152
|
+
- A declaration this version does not model yet (a constant, an
|
|
153
|
+
`alias`, a `define_method`) is listed as omitted, never
|
|
154
|
+
dropped.
|
|
155
|
+
- `LINE:HASH` is an anchor: it changes when the declaration or
|
|
156
|
+
the type comments above it change, and not when prose
|
|
157
|
+
comments do.
|
|
158
|
+
|
|
159
|
+
The JSON document is specified in
|
|
160
|
+
[the lens output contract](../internal-spec/lens.md).
|
|
161
|
+
|
|
113
162
|
## Which to reach for
|
|
114
163
|
|
|
115
164
|
| You want… | Use |
|
|
116
165
|
| --- | --- |
|
|
117
166
|
| One expression's type, from the shell | `rigor type-of` |
|
|
167
|
+
| A file's declarations, their types and where each came from | `rigor lens` |
|
|
118
168
|
| Every line of a file surveyed | `rigor annotate` |
|
|
119
169
|
| The derivation replayed step by step | `rigor trace` |
|
|
120
170
|
| A type printed mid-analysis, in context | `dump_type` |
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -95,7 +95,7 @@ with the `plugins_isolation:` configuration key or the
|
|
|
95
95
|
| --- | --- |
|
|
96
96
|
| `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
97
|
| `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. |
|
|
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. 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
99
|
|
|
100
100
|
The environment variable wins over `plugins_isolation:`, so you can
|
|
101
101
|
override a project's committed choice for one invocation. The legacy
|
|
@@ -33,6 +33,7 @@ There is no conflict in running both simultaneously.
|
|
|
33
33
|
| `rigor_check` | `rigor check --format json` | JSON diagnostic report |
|
|
34
34
|
| `rigor_type_of` | `rigor type-of --format json` | JSON type at `FILE:LINE:COL` |
|
|
35
35
|
| `rigor_triage` | `rigor triage --format json` | JSON distribution + class/method selectors + hotspots + hints |
|
|
36
|
+
| `rigor_lens` | `rigor lens --format json` | JSON `lens/v0` declaration map (experimental) |
|
|
36
37
|
| `rigor_annotate` | `rigor annotate --no-color` | Annotated Ruby source |
|
|
37
38
|
| `rigor_sig_gen` | `rigor sig-gen --print --format json` | JSON RBS skeleton candidates |
|
|
38
39
|
| `rigor_explain` | `rigor explain --format json` | JSON rule catalog entries |
|
|
@@ -320,6 +321,34 @@ by `count` for the highest-leverage class/method, or filter by
|
|
|
320
321
|
|
|
321
322
|
---
|
|
322
323
|
|
|
324
|
+
### rigor_lens
|
|
325
|
+
|
|
326
|
+
Map the classes, modules, methods and attributes the given files declare,
|
|
327
|
+
with the type `rigor check` gives each one and where it came from. The
|
|
328
|
+
document is the experimental `lens/v0` format
|
|
329
|
+
([specification](../internal-spec/lens.md)); see
|
|
330
|
+
[Inspecting inferred types](05-inspecting-types.md) for the command.
|
|
331
|
+
|
|
332
|
+
**Input:**
|
|
333
|
+
|
|
334
|
+
| Argument | Type | Required |
|
|
335
|
+
|---|---|---|
|
|
336
|
+
| `files` | `string[]` | yes |
|
|
337
|
+
| `config` | `string` | no |
|
|
338
|
+
|
|
339
|
+
**Returns:** JSON. Each file carries its `status`, `sites`, `omissions`
|
|
340
|
+
and `diagnostics`; `members` holds the signatures.
|
|
341
|
+
|
|
342
|
+
**Typical agent use:** learn what a file or class offers, and the
|
|
343
|
+
contracts its methods already have, before editing it or writing a type.
|
|
344
|
+
Pass every file in one call: each call runs one analysis, and the first
|
|
345
|
+
call on a project costs a full `rigor check`. Paths, site keys and the cache are
|
|
346
|
+
relative to the directory the server was started in, so start it from the
|
|
347
|
+
project root; `--config` fixes which files belong to the project, not how
|
|
348
|
+
paths are written.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
323
352
|
### rigor_annotate
|
|
324
353
|
|
|
325
354
|
Return the given Ruby source file with each line's last-expression
|
|
@@ -482,7 +511,7 @@ You should get a JSON response. If you get nothing or a shell error,
|
|
|
482
511
|
`rigor` is not on your `PATH` — fix the install path first (see
|
|
483
512
|
[Installing Rigor](01-installation.md)).
|
|
484
513
|
|
|
485
|
-
**`rigor_check` / `rigor_triage` return an empty diagnostics array on a project that has errors
|
|
514
|
+
**`rigor_check` / `rigor_triage` return an empty diagnostics array on a project that has errors, or `rigor_lens` reports every file `outside_project`.**
|
|
486
515
|
|
|
487
516
|
The tool uses `Configuration.discover` from the working directory the
|
|
488
517
|
MCP server was started in. If the client launches `rigor mcp` from a
|
|
@@ -517,8 +546,8 @@ warmed by a prior `rigor check` run reduces it significantly.
|
|
|
517
546
|
|
|
518
547
|
## Status and roadmap
|
|
519
548
|
|
|
520
|
-
`rigor mcp` shipped in v0.1.10 with
|
|
521
|
-
transport. Queued follow-ups are demand-driven:
|
|
549
|
+
`rigor mcp` shipped in v0.1.10 with seven tools and stdio
|
|
550
|
+
transport; `rigor_lens` joined them in v0.4.4. Queued follow-ups are demand-driven:
|
|
522
551
|
|
|
523
552
|
- **HTTP transport** (slice 2) — `--transport=http` for CI / remote
|
|
524
553
|
agent use; a minimal Rack endpoint.
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -184,7 +184,8 @@ file, which other files its analysis read from, so it knows
|
|
|
184
184
|
exactly which files an edit can affect. A continuous-integration
|
|
185
185
|
gate, `rigor check --verify-incremental`, asserts this on every
|
|
186
186
|
build: it runs the incremental analyzer and a full analysis and
|
|
187
|
-
fails if they disagree on a single diagnostic
|
|
187
|
+
fails if they disagree on a single diagnostic, or list the same
|
|
188
|
+
diagnostics in a different order.
|
|
188
189
|
|
|
189
190
|
Rigor is also precise about *what* an edit changed. Re-checking a
|
|
190
191
|
file that many others depend on — a base class, a widely-used
|
|
@@ -259,6 +260,22 @@ version change does, or `--clear-cache`. So the shortcut can stay
|
|
|
259
260
|
off indefinitely; run `rigor check --incremental --clear-cache`
|
|
260
261
|
once to turn it back on.
|
|
261
262
|
|
|
263
|
+
|
|
264
|
+
A warm `--incremental` run says on stderr how far the edit spread,
|
|
265
|
+
for example `rigor: --incremental warm — re-analysed 3 of 1295 files
|
|
266
|
+
(1 changed), reused the rest`. The first count is the files the
|
|
267
|
+
recheck re-analysed, out of M, the analysed set: every project file
|
|
268
|
+
the run covers, or only the subset under a path argument such as
|
|
269
|
+
`rigor check --incremental lib`. The re-analysed files are M minus
|
|
270
|
+
those served from the snapshot. They include files that a change
|
|
271
|
+
reached without editing them, such as a file whose inferred
|
|
272
|
+
parameter types moved (ADR-67), not only the edited files and their
|
|
273
|
+
dependents. The parenthesis lists `changed`, and `added` or
|
|
274
|
+
`removed` when there are some. View templates from a plugin are not
|
|
275
|
+
in M. The recheck re-runs every template on every warm run, so they
|
|
276
|
+
are counted after the file clause, as `plus 2 view templates`. An
|
|
277
|
+
unchanged run reads `re-analysed 0 of 1295 files, reused all`.
|
|
278
|
+
|
|
262
279
|
`--incremental` is most useful for fast local re-checks and CI
|
|
263
280
|
on a changed branch.
|
|
264
281
|
|
|
@@ -116,8 +116,9 @@ gap in coverage rather than a finding about your code.
|
|
|
116
116
|
rigor unused --entry-point='lib/cli.rb' --entry-point='lib/workers/**/*.rb'
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Repeatable. Each glob is matched against every declaration's path
|
|
120
|
-
|
|
119
|
+
Repeatable. Each glob is matched against every declaration's path as
|
|
120
|
+
written, relative to the project root, and relative to the directory
|
|
121
|
+
you run in, and roots every declaration in a matching file.
|
|
121
122
|
|
|
122
123
|
**File-level references**, free. Code that runs on load — an
|
|
123
124
|
initializer naming a class, a `.rake` task — roots what it names. This
|
|
@@ -162,9 +163,12 @@ section held 22 policy classes whose only remaining caller was
|
|
|
162
163
|
`spec/policies/`. Those 22 were not adjudicated, so treat the section
|
|
163
164
|
as high-signal rather than high-yield; the yield is unmeasured.
|
|
164
165
|
|
|
165
|
-
"Test code" is decided by path, relative to the directory
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
"Test code" is decided by path, relative to the project root: the directory of the `.rigor.yml` in
|
|
167
|
+
use, or the directory you run `rigor unused` in when there is none or when the config sits below it
|
|
168
|
+
(`--config=ci/rigor.yml` from the project root). Running it from a subdirectory with `--config` reads
|
|
169
|
+
the same project, and references are read from the directory you run it in as well. A file counts
|
|
170
|
+
as test code when its path as written below either directory says so. The test paths are `spec/` and `test/` anywhere below the root,
|
|
171
|
+
`*_spec.rb` and `*_test.rb`, and the end-to-end trees `qa/`, `e2e/` and `features/` at the root. Tooling directories such as
|
|
168
172
|
`rubocop/`, `keeps/`, `tooling/` and `scripts/` count as production, so a class used only by them is
|
|
169
173
|
rooted rather than listed: deleting it would break the tooling.
|
|
170
174
|
|
|
@@ -257,10 +261,23 @@ can only bound the namespace, so everything under `Foo` is demoted;
|
|
|
257
261
|
`"V#{version}"` is the start of a name, so `V1`, `V2_0` and everything
|
|
258
262
|
under them are. `const_get` looks the name up from its receiver, so
|
|
259
263
|
`const_get("V#{version}")` inside `class Migration` demotes
|
|
260
|
-
`Migration::V1` — and the
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
same
|
|
264
|
+
`Migration::V1` — and the `V*` names under `Migration`'s superclasses
|
|
265
|
+
and mixins and at the top level as well, unless the call passes `false`
|
|
266
|
+
as its second argument. A literal `const_get("Handler")` is a reference
|
|
267
|
+
looked up in the same order, so it names `Migration::Handler` when
|
|
268
|
+
that exists rather than a top-level `Handler`. When the receiver is
|
|
269
|
+
implicit, `self` or `self.class` inside a method (or a block in a class
|
|
270
|
+
body, such as a lambda or `define_method`), the call may run on a
|
|
271
|
+
subclass or on an includer, so a `Handler` declared in a descendant is
|
|
272
|
+
kept reachable too. Descendants are the classes written `class D < Base`
|
|
273
|
+
and the declarations whose body has a plain `include`, `prepend` or
|
|
274
|
+
`extend` of the module; `K.include(Mix)`, `K.send(:include, Mix)`,
|
|
275
|
+
`K.extend(Ext)` and `K = Class.new(Base)` are not followed yet. A call
|
|
276
|
+
written directly in a class body is not widened. Inside
|
|
277
|
+
`Other.instance_eval { }` (or `class_eval`, `module_eval` and their
|
|
278
|
+
`_exec` forms) the receiver is `Other`. A name built into a
|
|
279
|
+
local variable first (`name = "V#{version}"; const_get(name)`) is read
|
|
280
|
+
the same way. A
|
|
264
281
|
class name appearing as a string in a `.yml`, `.json` or template file
|
|
265
282
|
demotes the same way — weaker evidence than a constant reference, and
|
|
266
283
|
neither proof of use nor grounds to call it dead.
|
|
@@ -127,8 +127,8 @@ routed. Helper-name recognition for all three is unaffected.
|
|
|
127
127
|
metaprogramming the parser can't unfold (routes built in a loop
|
|
128
128
|
over runtime data, helpers injected by an engine the parser
|
|
129
129
|
doesn't model) may not register, which can surface a false
|
|
130
|
-
`unknown-helper`. Record those in a baseline, or
|
|
131
|
-
`# rigor:disable` the line.
|
|
130
|
+
`unknown-helper`. Record those in a baseline, or add
|
|
131
|
+
`# rigor:disable plugin.rails-routes.unknown-helper` to the line.
|
|
132
132
|
- **The "routes file did not load" warning is run-scoped.** It is a
|
|
133
133
|
fact about your configuration, not about any one source file, so
|
|
134
134
|
it is reported once per run on `.rigor.yml` rather than on the
|
|
@@ -48,7 +48,10 @@ expected kind: `belong_to` / `have_one` (singular), `have_many` /
|
|
|
48
48
|
|
|
49
49
|
Suppress with the qualified rule, e.g.
|
|
50
50
|
`# rigor:disable plugin.shoulda-matchers.unknown-column`, or silence
|
|
51
|
-
the whole family with `# rigor:disable plugin.shoulda-matchers`.
|
|
51
|
+
the whole family with `# rigor:disable plugin.shoulda-matchers`. The
|
|
52
|
+
same spellings work project-wide in `.rigor.yml`'s `disable:`. The
|
|
53
|
+
bare rule (`# rigor:disable unknown-column`) does not reach these
|
|
54
|
+
diagnostics.
|
|
52
55
|
|
|
53
56
|
## No configuration
|
|
54
57
|
|
data/exe/rigor
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
# (`RUBY_BOX=1`, not toggleable at runtime). When a project selects that
|
|
7
7
|
# strategy (`RIGOR_PLUGIN_ISOLATION=ruby_box`, or the legacy `RIGOR_BOX`
|
|
8
8
|
# alias) we re-exec the same command with the flag set before anything
|
|
9
|
-
# else loads. The
|
|
10
|
-
# inherited environment (RUBYOPT / BUNDLE_*) preserves the Bundler
|
|
9
|
+
# else loads. The inherited environment (RUBYOPT / BUNDLE_*) preserves the Bundler
|
|
11
10
|
# context across the exec. The `none` and `process` (default) strategies
|
|
12
11
|
# need no flag, so this is a no-op for them — behaviour is unchanged
|
|
13
12
|
# unless a project opts into `ruby_box`.
|
|
@@ -17,22 +16,18 @@
|
|
|
17
16
|
# Rubies through 4.0.7 segfault on Ruby Bug #22260). When it cannot,
|
|
18
17
|
# the request is dropped with a warning and the run proceeds under the
|
|
19
18
|
# configured strategy (`process` by default) instead of crashing.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
#
|
|
20
|
+
# When `RUBY_BOX` is already set (by hand or by a wrapper), this process
|
|
21
|
+
# booted inside the box before any Rigor code ran, so the same probe
|
|
22
|
+
# runs here too, and a refused Ruby is re-exec'ed with `RUBY_BOX`
|
|
23
|
+
# removed. The probe's verdict is cached per Ruby build, and the
|
|
24
|
+
# re-exec is marked so the child neither probes nor re-execs again.
|
|
25
|
+
# Nothing above `require "rigor/cli"` may define a Ractor-shareable
|
|
26
|
+
# lambda: that is what crashes inside the box.
|
|
27
|
+
if !ENV["RUBY_BOX"].to_s.empty? || ENV["RIGOR_PLUGIN_ISOLATION"].to_s == "ruby_box" ||
|
|
28
|
+
!ENV["RIGOR_BOX"].to_s.empty? || ENV.key?("RIGOR_BOX_PROBED")
|
|
23
29
|
require_relative "../lib/rigor/plugin/box_probe"
|
|
24
|
-
|
|
25
|
-
rigor_box_warning = "rigor: ruby_box plugin isolation is unavailable: #{rigor_box_unsupported}. " \
|
|
26
|
-
"Falling back to the configured plugins_isolation strategy (default: process)."
|
|
27
|
-
# `$stderr.puts`, not `warn`: `-W0` silences `warn`, and this fallback must never be silent.
|
|
28
|
-
$stderr.puts rigor_box_warning # rubocop:disable Style/StderrPuts
|
|
29
|
-
ENV.delete("RIGOR_PLUGIN_ISOLATION") if rigor_isolation == "ruby_box"
|
|
30
|
-
ENV.delete("RIGOR_BOX")
|
|
31
|
-
else
|
|
32
|
-
ENV["RUBY_BOX"] = "1"
|
|
33
|
-
ENV["RIGOR_PLUGIN_ISOLATION"] = "ruby_box" if rigor_isolation.empty?
|
|
34
|
-
exec(RbConfig.ruby, __FILE__, *ARGV)
|
|
35
|
-
end
|
|
30
|
+
exec(RbConfig.ruby, __FILE__, *ARGV) if Rigor::Plugin::BoxProbe.needs_reexec?(ENV)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
lib = File.expand_path("../lib", __dir__)
|