rigortype 0.4.0 → 0.4.2
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 +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
data/docs/install.md
CHANGED
|
@@ -10,19 +10,42 @@ skill (`rigor-project-init` for a project that has never run Rigor).
|
|
|
10
10
|
**Do not add Rigor to the project's `Gemfile`** — Rigor is a
|
|
11
11
|
standalone tool, not a library.
|
|
12
12
|
|
|
13
|
+
**Install the latest release, not whatever is already on the
|
|
14
|
+
machine.** A `rigor` already on PATH, or a version manager that
|
|
15
|
+
resolves to an older release, is not a reason to stop early: Rigor
|
|
16
|
+
changes quickly, and the skills this guide hands off to are written
|
|
17
|
+
for the current release. Step 1 looks up the latest version; Step 2
|
|
18
|
+
installs it and Step 3 checks it.
|
|
19
|
+
|
|
13
20
|
---
|
|
14
21
|
|
|
15
22
|
## Step 1 — Detect the environment
|
|
16
23
|
|
|
17
|
-
Run these
|
|
24
|
+
Run these checks and note which tools are available:
|
|
18
25
|
|
|
19
26
|
```sh
|
|
20
27
|
which mise # preferred — see Step 2A
|
|
21
28
|
which asdf # fallback — see Step 2B
|
|
22
29
|
ruby --version 2>/dev/null | head -1 # is Ruby 4.0 already on PATH?
|
|
23
|
-
which docker # last resort — see
|
|
30
|
+
which docker # last resort — see "Last resort — Docker"
|
|
31
|
+
rigor --version 2>/dev/null # an existing install, if any
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then look up the latest Rigor release on RubyGems:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
curl -fsS https://rubygems.org/api/v1/versions/rigortype/latest.json
|
|
24
38
|
```
|
|
25
39
|
|
|
40
|
+
It prints `{"version":"X.Y.Z"}`. Below, `<LATEST>` stands for that
|
|
41
|
+
`X.Y.Z`: replace it with the version you got, never type `<LATEST>`
|
|
42
|
+
literally. Without `curl`, `gem search --remote --exact rigortype`
|
|
43
|
+
gives the same answer once Ruby is available. If neither works, tell
|
|
44
|
+
the user you could not determine the latest version instead of
|
|
45
|
+
guessing one.
|
|
46
|
+
|
|
47
|
+
An existing `rigor` older than `<LATEST>` still goes through Step 2;
|
|
48
|
+
tell the user which version they have and which you are installing.
|
|
26
49
|
Then proceed to the **first** matching case below.
|
|
27
50
|
|
|
28
51
|
---
|
|
@@ -40,23 +63,51 @@ that can be committed alongside the code. Other contributors — and CI
|
|
|
40
63
|
— run `mise install` to restore those versions with no `Gemfile`
|
|
41
64
|
involvement.
|
|
42
65
|
|
|
43
|
-
|
|
66
|
+
First ask mise which Rigor version it would pick:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
mise latest gem:rigortype
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If that is older than `<LATEST>`, mise is holding the newer release
|
|
73
|
+
back — usually its `minimum_release_age` quarantine, which hides
|
|
74
|
+
releases for a while after they are published (`mise ls-remote
|
|
75
|
+
gem:rigortype` then warns `1 newer gem:rigortype release hidden by
|
|
76
|
+
minimum_release_age` on stderr). A bare `mise use gem:rigortype`
|
|
77
|
+
would install the older version with no error, so this is the step where agents
|
|
78
|
+
silently end up on a stale Rigor. The quarantine is a supply-chain
|
|
79
|
+
safeguard, so do not bypass it on your own: tell the user both
|
|
80
|
+
versions and ask which to install. `mise settings get
|
|
81
|
+
minimum_release_age` tells you whose choice it is: an error ("not
|
|
82
|
+
set") means mise's built-in default is in effect and you can recommend
|
|
83
|
+
`<LATEST>`; a value means the user configured it, so recommend
|
|
84
|
+
respecting it.
|
|
85
|
+
|
|
86
|
+
Then run in the project root, with the version the user agreed to:
|
|
44
87
|
|
|
45
88
|
```sh
|
|
46
89
|
mise use ruby@4.0
|
|
47
|
-
mise use --pin gem:rigortype
|
|
90
|
+
mise use --pin gem:rigortype@<LATEST>
|
|
48
91
|
```
|
|
49
92
|
|
|
50
93
|
`mise use` installs the tools and writes their versions to `mise.toml`
|
|
51
|
-
in one step. Commit `mise.toml` so the version is shared.
|
|
94
|
+
in one step. Commit `mise.toml` so the version is shared. Name the
|
|
95
|
+
version explicitly, as above: an explicit version is installed even
|
|
96
|
+
while the quarantine hides it from `mise latest`.
|
|
52
97
|
|
|
53
|
-
`--pin` records the exact Rigor version (`"gem:rigortype" = "
|
|
98
|
+
`--pin` records the exact Rigor version (`"gem:rigortype" = "X.Y.Z"`).
|
|
54
99
|
Without it mise writes `"gem:rigortype" = "latest"`, which every
|
|
55
100
|
machine re-resolves to whatever is newest when it first installs — so
|
|
56
101
|
a committed `latest` does not give the team one shared version. The
|
|
57
102
|
trade-off: a pin will not move on its own, and `mise outdated` cannot
|
|
58
|
-
report a pinned tool as behind. Upgrade
|
|
59
|
-
gem:rigortype
|
|
103
|
+
report a pinned tool as behind. Upgrade by running `mise use --pin
|
|
104
|
+
gem:rigortype@<version>` again with the newer version; `mise upgrade
|
|
105
|
+
--bump gem:rigortype` goes through the same quarantine as a bare
|
|
106
|
+
`mise use`.
|
|
107
|
+
|
|
108
|
+
A user-wide `~/.config/mise/config.toml` entry for `gem:rigortype`
|
|
109
|
+
keeps applying outside this project. Mention it if it names an older
|
|
110
|
+
version, but do not edit global config without the user's consent.
|
|
60
111
|
|
|
61
112
|
Then verify:
|
|
62
113
|
|
|
@@ -88,7 +139,8 @@ gem is installed with `gem install` after setting the Ruby version.
|
|
|
88
139
|
```sh
|
|
89
140
|
asdf install ruby latest:4.0
|
|
90
141
|
asdf local ruby latest:4.0
|
|
91
|
-
gem install rigortype
|
|
142
|
+
gem install rigortype -v <LATEST>
|
|
143
|
+
asdf reshim ruby
|
|
92
144
|
```
|
|
93
145
|
|
|
94
146
|
Verify:
|
|
@@ -108,7 +160,7 @@ pinning; see <https://mise.jdx.dev/getting-started.html>.
|
|
|
108
160
|
If `ruby --version` reports `ruby 4.0.*`, install the gem directly:
|
|
109
161
|
|
|
110
162
|
```sh
|
|
111
|
-
gem install rigortype
|
|
163
|
+
gem install rigortype -v <LATEST>
|
|
112
164
|
```
|
|
113
165
|
|
|
114
166
|
Verify:
|
|
@@ -152,6 +204,11 @@ and does not integrate well with editor LSP. Use it only when a
|
|
|
152
204
|
host-side Ruby 4.0 is genuinely unavailable (for example, Windows
|
|
153
205
|
without WSL). For all other environments, prefer Case A–D above.
|
|
154
206
|
|
|
207
|
+
With Docker there is no `rigor` on the host PATH, so Step 3 checks the
|
|
208
|
+
image instead: `docker run --rm ghcr.io/rigortype/rigor:latest rigor
|
|
209
|
+
--version`. The image can trail RubyGems by a release; report its
|
|
210
|
+
version to the user rather than treating a mismatch as a failure.
|
|
211
|
+
|
|
155
212
|
---
|
|
156
213
|
|
|
157
214
|
## Step 3 — Verify the installation
|
|
@@ -160,8 +217,23 @@ without WSL). For all other environments, prefer Case A–D above.
|
|
|
160
217
|
rigor --version
|
|
161
218
|
```
|
|
162
219
|
|
|
163
|
-
|
|
164
|
-
If
|
|
220
|
+
The reported version should be the one you installed in Step 2
|
|
221
|
+
(normally `<LATEST>`). If it is older, you are running a different
|
|
222
|
+
`rigor` than the one just installed. With mise, first check what the
|
|
223
|
+
project resolves to:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
mise exec -- rigor --version
|
|
227
|
+
mise which rigor
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
If `mise exec` reports the new version, the shell's PATH is stale (a
|
|
231
|
+
non-interactive shell does not re-run mise's activation after `mise
|
|
232
|
+
use`); run `eval "$(mise hook-env)"`, or use `mise exec -- rigor` for
|
|
233
|
+
the remaining steps. Otherwise run `which -a rigor` to find the older
|
|
234
|
+
install. Do not uninstall anything to fix this without asking, and do
|
|
235
|
+
not continue on the older version without telling the user. If the
|
|
236
|
+
command is not found, revisit Step 2 for your case.
|
|
165
237
|
|
|
166
238
|
---
|
|
167
239
|
|
|
@@ -192,6 +264,6 @@ project is set up, re-run `rigor skill describe` for the step after
|
|
|
192
264
|
that.
|
|
193
265
|
|
|
194
266
|
If `rigor skill describe` is not recognised, your Rigor version predates
|
|
195
|
-
it
|
|
196
|
-
|
|
197
|
-
|
|
267
|
+
it: you are not on the version Step 1 found. Go back to Step 3. If
|
|
268
|
+
you are staying on an older version with the user's agreement, run
|
|
269
|
+
`rigor skill rigor-project-init` directly.
|
|
@@ -34,8 +34,8 @@ the `paths:` list from the configuration file.
|
|
|
34
34
|
| `--format=FORMAT` | Output format. Default `text`. Also `json` (structured stream), plus the CI-native renderings `sarif`, `github`, `gitlab`, `checkstyle`, `junit`, and `teamcity` — see [Running Rigor in CI](11-ci.md). |
|
|
35
35
|
| `--no-ci-detect` | Disable CI auto-detection — by default `text` output also emits the running CI's native annotations / hint (`RIGOR_CI_DETECT=0` does the same). See [Running Rigor in CI § auto-detection](11-ci.md). |
|
|
36
36
|
| `--explain` | Surface fail-soft fallback events as `info` diagnostics. |
|
|
37
|
-
| `--no-cache` | Skip the persistent cache for this run. |
|
|
38
|
-
| `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. See [Caching](12-caching.md). |
|
|
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
|
+
| `--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
39
|
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics are byte-identical, then exit (0 on match, 1 with the differing diagnostics on mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
|
|
40
40
|
| `--clear-cache` | Delete the cache directory before running. |
|
|
41
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. |
|
|
@@ -21,6 +21,11 @@ discovery entirely.
|
|
|
21
21
|
|
|
22
22
|
All relative paths in a config file resolve against that
|
|
23
23
|
file's own directory.
|
|
24
|
+
A leading `~/` (or a bare `~`) in a path setting expands to your home directory; `~name` is an ordinary
|
|
25
|
+
directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `test_paths:`,
|
|
26
|
+
`pre_eval:`, `plugins_io.allowed_paths:`, `includes:`, `bundler.bundle_path:`, `bundler.lockfile:` and
|
|
27
|
+
`rbs_collection.lockfile:`. It does not apply to `cache.path:`, `baseline:` or `effects.snapshot.path:`, which stay
|
|
28
|
+
literal and relative to the working directory.
|
|
24
29
|
|
|
25
30
|
## Editor validation
|
|
26
31
|
|
|
@@ -57,7 +62,7 @@ cache:
|
|
|
57
62
|
|
|
58
63
|
| Key | Type | Default | Meaning |
|
|
59
64
|
| --- | --- | --- | --- |
|
|
60
|
-
| `target_ruby` | String | `"4.0"` | The Ruby version *your* project runs — `"X.Y"`, `"X.Y.Z"`, or `"latest"`. Independent of the Ruby Rigor itself runs on. |
|
|
65
|
+
| `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. |
|
|
61
66
|
| `paths` | Array | `["lib"]` | Directories or files to analyse. |
|
|
62
67
|
| `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
63
68
|
| `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. |
|
|
@@ -56,6 +56,7 @@ carries no `documentation_url`.
|
|
|
56
56
|
| <a id="rule-call-argument-type-mismatch"></a>`call.argument-type-mismatch` | An argument's type provably violates the parameter contract. | high |
|
|
57
57
|
| <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 |
|
|
58
58
|
| <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 |
|
|
59
|
+
| <a id="rule-call-deprecated-ruby2-keywords"></a>`call.deprecated-ruby2-keywords` | A call to the `ruby2_keywords` family Ruby 4.1 deprecates: `Module#ruby2_keywords`, top-level `ruby2_keywords` and `Proc#ruby2_keywords` (removed in Ruby 4.4), or `Hash.ruby2_keywords_hash?` and `Hash.ruby2_keywords_hash` (removed in Ruby 4.5), directly or through `send` with a literal Symbol. Runs only when `.rigor.yml` sets `target_ruby` explicitly to `"4.1"` or later: the default, an older version and `"latest"` report nothing, because on Ruby 4.0 the call is correct. Not reported when a project or `pre_eval:` file defines a method by the same name, inside a block at the top level, an `instance_eval` / `instance_exec` block or an instance method of a class or module, or where a `RUBY_VERSION` guard rules the call out on the stated Ruby or cannot be decided. `info` under `lenient`, `warning` under `balanced`, `error` under `strict`. | high |
|
|
59
60
|
| <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 |
|
|
60
61
|
| <a id="rule-flow-always-raises"></a>`flow.always-raises` | The expression provably raises on every reachable path. | high |
|
|
61
62
|
| <a id="rule-flow-unreachable-branch"></a>`flow.unreachable-branch` | An `if` / `unless` / ternary branch is statically dead. | high |
|
|
@@ -66,7 +67,7 @@ carries no `documentation_url`.
|
|
|
66
67
|
| <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 |
|
|
67
68
|
| <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 |
|
|
68
69
|
| <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
|
|
69
|
-
| <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 |
|
|
70
|
+
| <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, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. | high |
|
|
70
71
|
| <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
|
|
71
72
|
| <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 |
|
|
72
73
|
| <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
|
data/docs/manual/06-baseline.md
CHANGED
|
@@ -77,6 +77,8 @@ a diagnostic another layer has already suppressed.
|
|
|
77
77
|
| `rigor baseline drift` | Show how buckets have moved — `--only=over` for buckets that grew, `reducible` for ones you have already improved past, `cleared` for empty ones. |
|
|
78
78
|
| `rigor baseline prune` | Drop buckets that match nothing any more. `--dry-run` previews. |
|
|
79
79
|
|
|
80
|
+
`generate` and `regenerate` analyse with your loaded configuration unchanged except that `baseline:` is off, so options such as `bleeding_edge`, `target_ruby` or `severity_overrides` shape the recorded set exactly as they shape `rigor check`.
|
|
81
|
+
|
|
80
82
|
`generate` and `regenerate` take `--match-mode=rule` (the
|
|
81
83
|
default — one bucket per file × rule) or `--match-mode=message`
|
|
82
84
|
(a bucket per distinct message: more precise, more churn).
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -54,7 +54,7 @@ each plugin's options — but the families today are:
|
|
|
54
54
|
`rigor-ffi` provides the `ffi_binding_recognizer` class DSL on
|
|
55
55
|
`Rigor::Plugin::Base` to recognize custom binding definitions.
|
|
56
56
|
- **Other ecosystems** — `rigor-sinatra`, `rigor-hanami`,
|
|
57
|
-
`rigor-devise`, `rigor-pundit`, `rigor-sidekiq`,
|
|
57
|
+
`rigor-devise`, `rigor-pundit`, `rigor-alba`, `rigor-typelizer`, `rigor-sidekiq`,
|
|
58
58
|
`rigor-graphql`, `rigor-statesman`, `rigor-sorbet`,
|
|
59
59
|
`rigor-typescript-utility-types`,
|
|
60
60
|
`rigor-activesupport-core-ext`.
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -60,9 +60,10 @@ is not repeated here.)
|
|
|
60
60
|
- **`rigor-project-init`** — onboards a project from a cold start. It
|
|
61
61
|
detects the stack (Rails, RSpec, dry-rb, …), proposes the matching
|
|
62
62
|
[plugins](07-plugins.md), picks an adoption mode — a
|
|
63
|
-
[baseline](06-baseline.md) snapshot
|
|
64
|
-
zero-diagnostic gate for
|
|
65
|
-
|
|
63
|
+
[baseline](06-baseline.md) snapshot by default, new projects
|
|
64
|
+
included, or an opt-in zero-diagnostic gate for users fluent in type
|
|
65
|
+
theory and RBS — writes a `.rigor.dist.yml`, and generates the first
|
|
66
|
+
baseline. Reach for it when setting Rigor up for
|
|
66
67
|
the first time.
|
|
67
68
|
- **`rigor-rbs-setup`** — installs community RBS for your gems
|
|
68
69
|
(`rbs collection install`) so RBS-less dependencies stop typing as
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -135,7 +135,7 @@ CI detection that `auto` relies on.
|
|
|
135
135
|
|
|
136
136
|
| Flag | Effect |
|
|
137
137
|
| --- | --- |
|
|
138
|
-
| `rigor check --no-cache` | Run without reading or writing the persistent cache. |
|
|
138
|
+
| `rigor check --no-cache` | Run without reading or writing the persistent cache. With `--incremental` this includes the snapshot: every file is analysed and the snapshot on disk is left untouched. |
|
|
139
139
|
| `rigor check --clear-cache` | Delete the cache directory, then run. |
|
|
140
140
|
| `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes (on stderr under every `--format` but `text`, so the document on stdout stays parseable). |
|
|
141
141
|
| `rigor check --incremental` | Re-analyse only what changed; serve the rest from the incremental snapshot (see below). |
|
|
@@ -144,9 +144,12 @@ There is no config key to disable caching permanently — the
|
|
|
144
144
|
flags are per-run toggles. To run without a persistent cache
|
|
145
145
|
habitually, point `cache.path` at a disposable directory.
|
|
146
146
|
|
|
147
|
+
With `--incremental --no-cache` and an editor buffer (`--tmp-file` with `--instead-of`), the buffer is analysed on its own, as it is when no snapshot exists, and the note says that `--no-cache` skipped the snapshot rather than suggesting `rigor check --incremental`.
|
|
148
|
+
|
|
147
149
|
## Size and eviction
|
|
148
150
|
|
|
149
|
-
A project's active cache set is
|
|
151
|
+
A project's active cache set is a few megabytes (about 10 MB
|
|
152
|
+
for a Rails application the size of Mastodon). Entries
|
|
150
153
|
are content-keyed, so events like a gem upgrade or an `.rbs`
|
|
151
154
|
edit write fresh entries and leave the old ones *orphaned* —
|
|
152
155
|
nothing references them, and no run would otherwise delete
|
|
@@ -219,9 +222,45 @@ dependents of removed ones.) As with the rest of the cache, a
|
|
|
219
222
|
missing or corrupt snapshot is simply a full run; it can never
|
|
220
223
|
wedge analysis.
|
|
221
224
|
|
|
225
|
+
When nothing has changed since the last `--incremental` run, the
|
|
226
|
+
answer comes straight from the cache, without starting the
|
|
227
|
+
analyzer at all. Rigor first checks the inputs that answer was
|
|
228
|
+
computed from — your source files and view templates, your RBS,
|
|
229
|
+
your configuration and the files your plugins read — and runs the
|
|
230
|
+
ordinary incremental re-check the moment any of them differs. On
|
|
231
|
+
a Rails application the size of Mastodon, an unchanged
|
|
232
|
+
`--incremental` run takes about a quarter of a second. An
|
|
233
|
+
unchanged plain `rigor check` skips the analyzer the same way,
|
|
234
|
+
except when a plugin compiles your view templates, as
|
|
235
|
+
rigor-actionpack does.
|
|
236
|
+
|
|
237
|
+
The shortcut stands aside under `--workers=N` (or
|
|
238
|
+
`parallel.workers:`), `--explain`, `--cache-stats`, `--coverage`,
|
|
239
|
+
an editor buffer, and an `effects:` block, and when the cache
|
|
240
|
+
directory is on a different filesystem from the project; those
|
|
241
|
+
runs re-check as before. It also switches itself off, silently:
|
|
242
|
+
|
|
243
|
+
- when an input changes that the ordinary re-check does not look
|
|
244
|
+
at itself: an auto-detected `sig/` directory (one you did not
|
|
245
|
+
list under `signature_paths:`), a file a narrower
|
|
246
|
+
`rigor check --incremental DIR` only discovers from the rest of
|
|
247
|
+
your configured `paths:`, or a `pre_eval:` file outside the
|
|
248
|
+
analysed paths. The re-check may keep printing the answer from
|
|
249
|
+
before that change; the shortcut does not repeat it;
|
|
250
|
+
- when an `--incremental` run cannot record its answer safely:
|
|
251
|
+
after a run with `--workers=N` that re-analysed files, when a file or directory changes while a run is reading
|
|
252
|
+
it, after a CI cache is restored onto a fresh checkout, or when
|
|
253
|
+
two runs race.
|
|
254
|
+
|
|
255
|
+
Either way it stays off, and every run is an ordinary re-check,
|
|
256
|
+
until the next full analysis. The ordinary re-check never starts
|
|
257
|
+
one by itself: only a configuration, `Gemfile.lock` or Rigor
|
|
258
|
+
version change does, or `--clear-cache`. So the shortcut can stay
|
|
259
|
+
off indefinitely; run `rigor check --incremental --clear-cache`
|
|
260
|
+
once to turn it back on.
|
|
261
|
+
|
|
222
262
|
`--incremental` is most useful for fast local re-checks and CI
|
|
223
|
-
on a changed branch.
|
|
224
|
-
the ordinary cache already serves the whole result in one step.
|
|
263
|
+
on a changed branch.
|
|
225
264
|
|
|
226
265
|
## Concurrency
|
|
227
266
|
|
|
@@ -90,8 +90,9 @@ It runs eight phases in order:
|
|
|
90
90
|
2. **Choose an adoption mode** — proposes either *acknowledge*
|
|
91
91
|
(snapshot today's diagnostics into a baseline; catch
|
|
92
92
|
regressions going forward) or *strict* (drive the project to
|
|
93
|
-
zero and keep it there). It recommends acknowledge for
|
|
94
|
-
|
|
93
|
+
zero and keep it there). It recommends acknowledge for every
|
|
94
|
+
project, new ones included; strict is an opt-in for users
|
|
95
|
+
fluent in type theory and RBS.
|
|
95
96
|
3. **Select plugins** — proposes the plugin set matching your
|
|
96
97
|
detected stack; you confirm or trim the list.
|
|
97
98
|
4. **Write `.rigor.dist.yml`** — the committed shared config,
|
|
@@ -151,14 +152,19 @@ source-of-truth copy is
|
|
|
151
152
|
|
|
152
153
|
| Mode | When | What happens |
|
|
153
154
|
| --- | --- | --- |
|
|
154
|
-
| **Acknowledge** |
|
|
155
|
-
| **Strict** |
|
|
155
|
+
| **Acknowledge** | Recommended for every project, new ones included | Record today's diagnostics in a baseline; surface only new ones on each PR. |
|
|
156
|
+
| **Strict** | Opt-in, for users fluent in type theory and RBS | Zero outstanding diagnostics; no baseline. |
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
Start with acknowledge mode even on a brand-new project. Rigor's
|
|
159
|
+
inference still has gaps, so a zero-diagnostic gate meets
|
|
160
|
+
diagnostics on correct code, and clearing those means fixing
|
|
161
|
+
signatures or RBS rather than your code. Choose strict only if
|
|
162
|
+
you are comfortable doing that yourself. You can tighten later.
|
|
163
|
+
|
|
164
|
+
In `.rigor.dist.yml` (Step 3) the mode maps to a
|
|
165
|
+
`severity_profile:`. Acknowledge mode uses the default `balanced`
|
|
166
|
+
(omit the key), or `lenient` when the first `rigor check` reports
|
|
167
|
+
more than ~100 diagnostics; strict mode uses `strict`.
|
|
162
168
|
|
|
163
169
|
### Step 3 — Write .rigor.dist.yml
|
|
164
170
|
|
|
@@ -198,7 +204,7 @@ plugins:
|
|
|
198
204
|
- rigor-rspec
|
|
199
205
|
- rigor-factorybot
|
|
200
206
|
|
|
201
|
-
severity_profile: lenient # "strict" for strict mode
|
|
207
|
+
# severity_profile: lenient # only for >100 initial diagnostics; "strict" for strict mode
|
|
202
208
|
|
|
203
209
|
# baseline: .rigor-baseline.yml # uncomment after Step 6 (acknowledge mode only)
|
|
204
210
|
```
|
|
@@ -288,7 +294,9 @@ first, then large clusters to record in a baseline.
|
|
|
288
294
|
|
|
289
295
|
### Step 6 — Generate a baseline (acknowledge mode)
|
|
290
296
|
|
|
291
|
-
*Skip this step if you chose strict mode.*
|
|
297
|
+
*Skip this step if you chose strict mode.* Settle `severity_profile:`
|
|
298
|
+
first: changing it afterwards changes which rules fire and leaves the
|
|
299
|
+
baseline out of step.
|
|
292
300
|
|
|
293
301
|
```sh
|
|
294
302
|
rigor baseline generate
|
|
@@ -145,7 +145,7 @@ Four sections, needing four different decisions:
|
|
|
145
145
|
| **Reachable only from test code** | Live test, no production caller Rigor can see or suspect | Work these first |
|
|
146
146
|
| **Candidates** | Nothing reachable names it | Adjudicate — most are still live |
|
|
147
147
|
| **Cannot decide** | Something can name it at runtime | Read the reason; do not delete from here |
|
|
148
|
-
| **Namespace-only** | A module wrapping
|
|
148
|
+
| **Namespace-only** | A module wrapping code production reaches | Excluded from candidates; count only |
|
|
149
149
|
|
|
150
150
|
Work them in that order — which is not the order you *read* the
|
|
151
151
|
summary in, where `roots` comes first because it tells you whether to
|
|
@@ -162,6 +162,12 @@ section held 22 policy classes whose only remaining caller was
|
|
|
162
162
|
`spec/policies/`. Those 22 were not adjudicated, so treat the section
|
|
163
163
|
as high-signal rather than high-yield; the yield is unmeasured.
|
|
164
164
|
|
|
165
|
+
"Test code" is decided by path, relative to the directory you run `rigor unused` in (run it from the
|
|
166
|
+
project root): `spec/` and `test/` anywhere below it, `*_spec.rb` and `*_test.rb`, and the end-to-end
|
|
167
|
+
trees `qa/`, `e2e/` and `features/` at that root. Tooling directories such as
|
|
168
|
+
`rubocop/`, `keeps/`, `tooling/` and `scripts/` count as production, so a class used only by them is
|
|
169
|
+
rooted rather than listed: deleting it would break the tooling.
|
|
170
|
+
|
|
165
171
|
### Candidates: sort by how conventional the class is
|
|
166
172
|
|
|
167
173
|
A candidate row is its name and declaration site:
|
|
@@ -171,6 +177,23 @@ Candidates — nothing reachable references these (45)
|
|
|
171
177
|
1 Api::V1::Timelines::TopicController app/controllers/api/v1/timelines/topic_controller.rb:3
|
|
172
178
|
```
|
|
173
179
|
|
|
180
|
+
A base class is reachable whenever one of its subclasses is:
|
|
181
|
+
`class Sub < Base` names `Base` on `Sub`'s behalf. So a base class
|
|
182
|
+
that nothing else names appears together with its subclasses when none
|
|
183
|
+
of them is reachable, and the decision belongs to the whole family
|
|
184
|
+
rather than to one row. When a subclass sits under **Cannot decide**,
|
|
185
|
+
its base moves there too.
|
|
186
|
+
|
|
187
|
+
A class declared outside `paths:` — `config/application.rb`'s
|
|
188
|
+
`Application`, a class in an initializer, a spec helper — is not
|
|
189
|
+
itself in the report, so what it names counts as named from its file:
|
|
190
|
+
`config.middleware.use MyMw` keeps `MyMw` live, and a fake subclass in
|
|
191
|
+
`spec/support` keeps its base reachable from tests. The same holds for
|
|
192
|
+
a gem class you reopen inside `paths:`. A name the file declares
|
|
193
|
+
itself still means that declaration: a migration's local
|
|
194
|
+
`class LegacyThing < ActiveRecord::Base` stub does not keep the app's
|
|
195
|
+
`LegacyThing` model live.
|
|
196
|
+
|
|
174
197
|
The 53 false positives in the adjudicated run fell into a few recurring
|
|
175
198
|
shapes, and 28 of them were the first one. Recognising these lets you
|
|
176
199
|
skip most of a list quickly:
|
|
@@ -230,7 +253,14 @@ belongs here instead, with the file named.
|
|
|
230
253
|
|
|
231
254
|
`"Foo".constantize` names `Foo` exactly, so it counts as an ordinary
|
|
232
255
|
reference and never reaches this section. `"Foo::#{key}".constantize`
|
|
233
|
-
can only bound the namespace, so everything under `Foo` is demoted
|
|
256
|
+
can only bound the namespace, so everything under `Foo` is demoted;
|
|
257
|
+
`"V#{version}"` is the start of a name, so `V1`, `V2_0` and everything
|
|
258
|
+
under them are. `const_get` looks the name up from its receiver, so
|
|
259
|
+
`const_get("V#{version}")` inside `class Migration` demotes
|
|
260
|
+
`Migration::V1` — and the top-level `V*` names as well, unless the call
|
|
261
|
+
passes `false` as its second argument. A name built into a local
|
|
262
|
+
variable first (`name = "V#{version}"; const_get(name)`) is read the
|
|
263
|
+
same way. A
|
|
234
264
|
class name appearing as a string in a `.yml`, `.json` or template file
|
|
235
265
|
demotes the same way — weaker evidence than a constant reference, and
|
|
236
266
|
neither proof of use nor grounds to call it dead.
|
|
@@ -14,7 +14,8 @@ The full catalogue, with a one-line scope for every plugin, is
|
|
|
14
14
|
## Available pages
|
|
15
15
|
|
|
16
16
|
- [rigor-activerecord](rigor-activerecord.md) — ActiveRecord
|
|
17
|
-
finder / relation typing and schema-checked columns
|
|
17
|
+
finder / relation typing and schema-checked columns; roots models reached only through an
|
|
18
|
+
association for `rigor unused`.
|
|
18
19
|
- [rigor-rails-routes](rigor-rails-routes.md) — `*_path` / `*_url`
|
|
19
20
|
helper validation against a parsed `config/routes.rb`.
|
|
20
21
|
- [rigor-rails-i18n](rigor-rails-i18n.md) — `t(...)` / `I18n.t(...)`
|
|
@@ -69,6 +70,10 @@ The full catalogue, with a one-line scope for every plugin, is
|
|
|
69
70
|
(`params`, `namespace`, verb macros, `desc`, `route_setting`) and
|
|
70
71
|
`Grape::Entity` `expose` declarations, including the `instance_eval`'d
|
|
71
72
|
block `self` bindings.
|
|
73
|
+
- [rigor-alba](rigor-alba.md) — types the inline `Alba.serialize { }` block `self`, and
|
|
74
|
+
roots the resources alba infers from `many :articles` for `rigor unused`.
|
|
75
|
+
- [rigor-typelizer](rigor-typelizer.md) — roots the serializer classes typelizer generates TypeScript
|
|
76
|
+
interfaces from, for `rigor unused`.
|
|
72
77
|
- [rigor-rspec-rails](rigor-rspec-rails.md) — `have_http_status`
|
|
73
78
|
argument validation (out-of-range codes, unknown status symbols).
|
|
74
79
|
- [rigor-shoulda-matchers](rigor-shoulda-matchers.md) — shoulda matcher
|
|
@@ -228,6 +228,27 @@ member left out stays lenient rather than reported.
|
|
|
228
228
|
`ActiveRecord::Base` is deliberately **not** declared: closing it would
|
|
229
229
|
close every model in the project.
|
|
230
230
|
|
|
231
|
+
## Roots for `rigor unused`
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
class Recipe < ApplicationRecord
|
|
235
|
+
belongs_to :user # ActiveRecord loads User
|
|
236
|
+
has_many :comments, as: :commentable # ... and Comment
|
|
237
|
+
has_many :tags, through: :taggings # no root of its own, see below
|
|
238
|
+
belongs_to :owner, polymorphic: true # no single target: no root
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`User` and `Comment` are named nowhere in the source, so [`rigor unused`](../02-cli-reference.md#rigor-unused) would list a model reached only this way. The plugin publishes the class each association resolves to as a `:reachability_roots` fact, mirroring ActiveRecord's own lookup:
|
|
243
|
+
|
|
244
|
+
- a literal `class_name:` (String or Symbol, or a rooted `"::Foo"`) wins;
|
|
245
|
+
- otherwise `has_many` / `has_and_belongs_to_many` use the singularized, camelized name and `belongs_to` / `has_one` the camelized name;
|
|
246
|
+
- the name is tried as nested in the owner itself (`Post` looks for `Post::Comment`), then in the owner's enclosing namespaces innermost first (`Admin::Recipe` looks for `Admin::User`), then at top level. If a candidate is not a model but is a namespace of one, nothing is rooted, since ActiveRecord would pick that module.
|
|
247
|
+
|
|
248
|
+
A root is published only when the project declares a model of that name. Nothing is published for a `polymorphic: true` association, a non-literal `class_name:` or `**options`, or a `through:` association without a literal `class_name:` / `source_type:` (a literal `source_type:` names the class; otherwise its target is the source association's, and that association roots it). Associations written inside `class << self`, a `def` or a block other than `with_options` are not seen, and neither is the block-parameter form `with_options(...) do |o| o.belongs_to :x end`. Literal options of a `with_options` group are merged into the associations inside it, the innermost group winning as in Rails; a group with non-literal options (`with_options opts do`) is skipped, as is `anonymous_class:`, an association whose own options hide the class (a `**splat`, an options variable, a non-literal `class_name:`), and a computed name equal to the owner's own name (Rails 7.2+ tries the top-level class first). The merged options also decide the association reader's type: `with_options optional: true` makes `belongs_to` readers nilable, and `class_name:` and `polymorphic:` set the target or make it `Dynamic`. The name is inflected with the real `ActiveSupport::Inflector`; if it cannot be loaded the plugin publishes nothing.
|
|
249
|
+
|
|
250
|
+
The roots are flat: an association on a model `rigor unused` itself reports as dead still roots its target, so a chain of dead models can leave the last one unlisted. Project-custom inflections are not read, so a model whose name relies on one is under-rooted rather than wrongly rooted. The roots are published in reduced mode too, since they do not depend on the schema.
|
|
251
|
+
|
|
231
252
|
## Limitations
|
|
232
253
|
|
|
233
254
|
- **Direct-superclass match only.** `class Admin < User` where
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# rigor-alba
|
|
2
|
+
|
|
3
|
+
Teaches Rigor about the [alba](https://github.com/okuramasafumi/alba) JSON serializer. alba does not ship its
|
|
4
|
+
RBS in the gem, so without this plugin every alba call in your project reads `Dynamic[top]`. The plugin only
|
|
5
|
+
removes false diagnostics and publishes `rigor unused` roots; it adds no diagnostic and no return type.
|
|
6
|
+
|
|
7
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
plugins:
|
|
11
|
+
- rigor-alba
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## What it does
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
# Inline resources: the block runs on an anonymous Alba::Resource class, so `attributes` and friends resolve
|
|
18
|
+
# instead of firing call.unresolved-toplevel.
|
|
19
|
+
json = Alba.serialize(user) { attributes :id, :name }
|
|
20
|
+
hash = Alba.hashify(user) { attributes :id }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The plugin contributes no return types: `Alba.serialize(obj)` runs your own `<Class>Resource#serialize`, which
|
|
24
|
+
you may override to return anything.
|
|
25
|
+
|
|
26
|
+
The block may sit anywhere a value can: `render json: Alba.serialize(x) { ... }` resolves the same as an
|
|
27
|
+
assignment.
|
|
28
|
+
|
|
29
|
+
## Roots for `rigor unused`
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
class UserResource
|
|
33
|
+
include Alba::Resource
|
|
34
|
+
|
|
35
|
+
many :articles # alba loads ArticleResource, then ArticleSerializer
|
|
36
|
+
one :editor, resource: EditorResource # explicit: nothing inferred
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`ArticleResource` appears nowhere in the source, so `rigor unused` would list it. The plugin publishes it as a
|
|
41
|
+
root. It does so only for an association that gives no `resource:` / `serializer:`, no second argument and no
|
|
42
|
+
block, and only when a class of the inferred name exists in the project (nesting-aware: inside `module Admin`
|
|
43
|
+
alba tries `Admin::ArticleResource` first). The name is classified with the real `ActiveSupport::Inflector`;
|
|
44
|
+
if it cannot be loaded the plugin publishes nothing.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
plugins:
|
|
50
|
+
- gem: rigor-alba
|
|
51
|
+
config:
|
|
52
|
+
resource_search_paths: ["app"] # default
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`resource_search_paths` is where the plugin looks for resource classes. Widen it if your resources live
|
|
56
|
+
outside `app/`.
|
|
57
|
+
|
|
58
|
+
## More
|
|
59
|
+
|
|
60
|
+
Internals and the known limits: [plugins/rigor-alba/README.md](../../../plugins/rigor-alba/README.md).
|
|
@@ -44,18 +44,36 @@ their `refute_*` equivalents.
|
|
|
44
44
|
| --- | --- |
|
|
45
45
|
| `assert_kind_of(T, x)` / `assert_instance_of(T, x)` | narrow to `T` |
|
|
46
46
|
| `assert_nil(x)` | narrow to `Constant<nil>` |
|
|
47
|
-
| `assert_equal(literal, x)` |
|
|
47
|
+
| `assert_equal(literal, x)` | meet with `Constant<literal>` (see below) |
|
|
48
48
|
| `assert_match(regex, x)` | narrow to `String` |
|
|
49
49
|
| `refute_kind_of` / `refute_instance_of` (+ `assert_not_*`) | narrow away from `T` |
|
|
50
50
|
| `refute_nil(x)` / `assert_not_nil(x)` | narrow away from nil |
|
|
51
|
-
| `refute_equal(literal, x)` / `assert_not_equal(...)` |
|
|
51
|
+
| `refute_equal(literal, x)` / `assert_not_equal(...)` | unchanged |
|
|
52
52
|
| `_(x).must_be_kind_of(T)` / `must_be_a(T)` / `must_be_instance_of(T)` / `must_be_an_instance_of(T)` | narrow to `T` |
|
|
53
53
|
| `_(x).must_be_nil` | narrow to `Constant<nil>` |
|
|
54
|
-
| `_(x).must_equal(literal)` |
|
|
54
|
+
| `_(x).must_equal(literal)` | meet with `Constant<literal>` (see below) |
|
|
55
55
|
| `_(x).must_match(regex)` | narrow to `String` |
|
|
56
56
|
| `_(x).wont_be_kind_of(T)` / `wont_be_instance_of(T)` | narrow away from `T` |
|
|
57
57
|
| `_(x).wont_be_nil` | narrow away from nil |
|
|
58
|
-
| `_(x).wont_equal(literal)` |
|
|
58
|
+
| `_(x).wont_equal(literal)` | unchanged |
|
|
59
|
+
|
|
60
|
+
### Equality is not identity
|
|
61
|
+
|
|
62
|
+
`assert_equal(literal, x)` and `_(x).must_equal(literal)` pass when
|
|
63
|
+
`literal == x`, and `==` can hold across classes: `1 == 1.0`,
|
|
64
|
+
`1 == 1r`, and a class with its own `==` (a `ModInt` that compares
|
|
65
|
+
`to_i`) passes `assert_equal 9, m`. The assertion therefore never
|
|
66
|
+
replaces `x`'s type:
|
|
67
|
+
|
|
68
|
+
- a `nil`, `true`, `false`, or Symbol literal has identity `==`, so `x`
|
|
69
|
+
narrows to that literal whatever its type was;
|
|
70
|
+
- an Integer, Float, or String literal narrows `x` only when every
|
|
71
|
+
member of `x`'s type is already of the literal's class (or a `nil`,
|
|
72
|
+
boolean, or Symbol the literal cannot equal). After
|
|
73
|
+
`x = rand(10); assert_equal 9, x`, `x` is `9`;
|
|
74
|
+
- otherwise — `x` is `Dynamic`, of a user class, or of another numeric
|
|
75
|
+
class (`assert_equal 9.0, x` with an Integer `x`), or the literal is
|
|
76
|
+
`0.0`, which `-0.0` also equals — `x` keeps its type.
|
|
59
77
|
|
|
60
78
|
## No diagnostics, no config
|
|
61
79
|
|