rigortype 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/data/builtins/ruby_core/date.yml +6 -3
- data/data/builtins/ruby_core/enumerable.yml +1 -0
- data/data/builtins/ruby_core/exception.yml +2 -1
- data/data/builtins/ruby_core/file.yml +28 -14
- data/data/builtins/ruby_core/hash.yml +5 -2
- data/data/builtins/ruby_core/io.yml +33 -16
- data/data/builtins/ruby_core/random.yml +2 -1
- data/data/builtins/ruby_core/re.yml +6 -3
- data/data/builtins/ruby_core/struct.yml +2 -0
- data/data/builtins/ruby_core/time.yml +2 -1
- data/data/gem_overlay/activesupport/core_ext.rbs +442 -11
- data/docs/handbook/02-everyday-types.md +1 -1
- data/docs/handbook/04-tuples-and-shapes.md +1 -1
- data/docs/handbook/08-understanding-errors.md +1 -1
- data/docs/handbook/09-plugins.md +2 -2
- data/docs/handbook/10-sorbet.md +1 -1
- data/docs/handbook/README.md +2 -2
- data/docs/handbook/appendix-go.md +1 -1
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +1 -1
- data/docs/handbook/appendix-protocols-and-structural-typing.md +2 -2
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +2 -2
- data/docs/handbook/appendix-typescript.md +4 -4
- data/docs/manual/02-cli-reference.md +29 -16
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +12 -0
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/08-skills.md +1 -1
- data/docs/manual/11-ci.md +1 -1
- data/docs/manual/12-caching.md +1 -1
- data/docs/manual/15-type-protection-coverage.md +1 -1
- data/docs/manual/18-removing-dead-code.md +13 -4
- data/docs/manual/19-effect-labels.md +19 -12
- data/docs/manual/README.md +2 -2
- data/docs/manual/plugins/rigor-actionmailer.md +4 -4
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activejob.md +3 -3
- data/docs/manual/plugins/rigor-activerecord.md +78 -5
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- data/docs/manual/plugins/rigor-rails-i18n.md +5 -5
- data/docs/manual/plugins/rigor-rspec.md +6 -2
- data/docs/manual/plugins/rigor-sidekiq.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
- data/lib/rigor/analysis/check_rules.rb +383 -48
- data/lib/rigor/analysis/crash_signature.rb +116 -0
- data/lib/rigor/analysis/dependency_recorder.rb +18 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/diagnostic.rb +17 -6
- data/lib/rigor/analysis/effects_cache_probe.rb +132 -0
- data/lib/rigor/analysis/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +44 -9
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +44 -16
- data/lib/rigor/analysis/reachability/plugin_roots.rb +11 -8
- data/lib/rigor/analysis/reachability/scan.rb +27 -7
- data/lib/rigor/analysis/reachability/scan_cache.rb +130 -0
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +53 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +14 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
- data/lib/rigor/analysis/runner.rb +298 -38
- data/lib/rigor/analysis/worker_session.rb +10 -1
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/file_digest.rb +20 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
- data/lib/rigor/cache/store.rb +10 -1
- data/lib/rigor/cli/check_command.rb +67 -48
- data/lib/rigor/cli/coverage_command.rb +28 -24
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- data/lib/rigor/cli/doc_links.rb +100 -0
- data/lib/rigor/cli/docs_command.rb +32 -2
- data/lib/rigor/cli/effects_command.rb +27 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +82 -12
- data/lib/rigor/cli/effects_explain_renderer.rb +6 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +52 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
- data/lib/rigor/cli/fused_protection_report.rb +23 -2
- data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
- data/lib/rigor/cli/mutation_protection_report.rb +31 -2
- data/lib/rigor/cli/sig_gen_command.rb +18 -0
- data/lib/rigor/cli/type_of_command.rb +188 -41
- data/lib/rigor/cli/type_of_renderer.rb +72 -3
- data/lib/rigor/cli/type_scan_command.rb +10 -1
- data/lib/rigor/cli/unused_command.rb +65 -16
- data/lib/rigor/cli.rb +22 -9
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/definition_lines.rb +100 -0
- data/lib/rigor/effects/envelope_check.rb +18 -1
- data/lib/rigor/effects/liskov_check.rb +17 -8
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/effects/signature_sources.rb +13 -2
- data/lib/rigor/effects/snapshot.rb +53 -21
- data/lib/rigor/effects/snapshot_diff.rb +26 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/lockfile_resolver.rb +15 -5
- data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
- data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
- data/lib/rigor/environment/rbs_loader.rb +525 -46
- data/lib/rigor/environment.rb +38 -15
- data/lib/rigor/inference/acceptance.rb +172 -20
- data/lib/rigor/inference/captured_locals.rb +62 -0
- data/lib/rigor/inference/content_join.rb +347 -0
- data/lib/rigor/inference/def_handle.rb +15 -3
- data/lib/rigor/inference/def_node_resolver.rb +62 -3
- data/lib/rigor/inference/expression_typer.rb +1020 -141
- data/lib/rigor/inference/fork_map.rb +6 -1
- data/lib/rigor/inference/hkt_reducer.rb +12 -1
- data/lib/rigor/inference/hkt_registry.rb +46 -0
- data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
- data/lib/rigor/inference/index_write_widening.rb +48 -0
- data/lib/rigor/inference/indexed_narrowing.rb +14 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
- data/lib/rigor/inference/method_dispatcher.rb +155 -14
- data/lib/rigor/inference/method_parameter_binder.rb +3 -3
- data/lib/rigor/inference/mutation_widening.rb +244 -174
- data/lib/rigor/inference/narrowing.rb +331 -27
- data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
- data/lib/rigor/inference/pre_eval_constants.rb +15 -0
- data/lib/rigor/inference/precision_scanner.rb +5 -2
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +89 -39
- data/lib/rigor/inference/scope_indexer.rb +1493 -140
- data/lib/rigor/inference/statement_evaluator.rb +276 -91
- data/lib/rigor/inference/struct_fold_safety.rb +186 -27
- data/lib/rigor/inference/synthetic_method_scanner.rb +7 -0
- data/lib/rigor/inference/version_guard.rb +229 -0
- data/lib/rigor/language_server/selection_range_provider.rb +1 -1
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/inflector.rb +14 -5
- data/lib/rigor/plugin/io_boundary.rb +107 -4
- data/lib/rigor/plugin/loader.rb +14 -0
- data/lib/rigor/protection/analysis_guard.rb +60 -0
- data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
- data/lib/rigor/protection/discovery_seed.rb +1 -1
- data/lib/rigor/protection/measurement_integrity.rb +36 -0
- data/lib/rigor/protection/mutation_scanner.rb +61 -6
- data/lib/rigor/protection/mutator.rb +121 -18
- data/lib/rigor/rbs_extended/envelope_scanner.rb +9 -0
- data/lib/rigor/reflection.rb +182 -30
- data/lib/rigor/scope/discovery_index.rb +39 -1
- data/lib/rigor/scope.rb +385 -4
- data/lib/rigor/sig_gen/classification.rb +12 -1
- data/lib/rigor/sig_gen/generator.rb +254 -5
- data/lib/rigor/sig_gen/renderer.rb +1 -6
- data/lib/rigor/sig_gen/writer.rb +3 -0
- data/lib/rigor/source/constant_path.rb +79 -2
- data/lib/rigor/triage/catalogue.rb +1 -1
- data/lib/rigor/type/combinator.rb +10 -0
- data/lib/rigor/type/maybe.rb +47 -0
- data/lib/rigor/type/refined.rb +1 -2
- data/lib/rigor/type/result.rb +53 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
- data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
- data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
- data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
- data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
- data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
- data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
- data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
- data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
- data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
- data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
- data/sig/rigor/analysis/baseline.rbs +1 -1
- data/sig/rigor/environment.rbs +3 -0
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/io_boundary.rbs +2 -0
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +25 -1
- data/sig/rigor/type.rbs +95 -1
- data/sig/rigor.rbs +16 -0
- data/skills/rigor-ci-setup/SKILL.md +2 -2
- data/skills/rigor-editor-setup/SKILL.md +2 -2
- data/skills/rigor-mcp-setup/SKILL.md +2 -2
- data/skills/rigor-monkeypatch-resolve/SKILL.md +1 -1
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- metadata +73 -3
|
@@ -91,7 +91,7 @@ The reflex to drop is `unwrap()`. In Rust you reach for
|
|
|
91
91
|
`.unwrap()` / `.expect()` when you *know* it is `Some`. Rigor
|
|
92
92
|
has no in-source assertion that lies to the checker; the
|
|
93
93
|
equivalents are a `nil?` guard (checked, not asserted) or
|
|
94
|
-
`T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
94
|
+
`T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet)
|
|
95
95
|
plugin (see [Chapter 10](10-sorbet.md)).
|
|
96
96
|
|
|
97
97
|
**`Result<T, E>` ↔ exceptions.** Here the models diverge. Ruby
|
|
@@ -709,7 +709,7 @@ Rigor maps onto this as:
|
|
|
709
709
|
| Dynamic type `?` | **`Dynamic[T]`** — a carrier that *wraps* a "best-guess" type `T` while marking the value as not-statically-verified. `Dynamic[top]` is the maximally-dynamic form. |
|
|
710
710
|
| Consistency `~` | The `maybe` arm of the trinary certainty — `Dynamic[T] ~ U` holds whenever `T ~ U` does. |
|
|
711
711
|
| Static/dynamic boundary | Per-method, per-file, per-plugin contribution — Rigor records *why* a value became `Dynamic[T]` in its dynamic-origin algebra. |
|
|
712
|
-
| Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
712
|
+
| Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin reads `T.let` / `T.cast` / `T.must` as cast forms; `RBS::Extended` `assert_type` directives serve the same role from `.rbs`. |
|
|
713
713
|
|
|
714
714
|
Two Rigor-specific extensions matter:
|
|
715
715
|
|
|
@@ -794,7 +794,7 @@ against:
|
|
|
794
794
|
### What Rigor explicitly does NOT do
|
|
795
795
|
|
|
796
796
|
- **Runtime contract insertion at the static / dynamic boundary.**
|
|
797
|
-
The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
797
|
+
The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin
|
|
798
798
|
reads Sorbet's `T.let` / `T.cast` / `T.must` as cast forms, but
|
|
799
799
|
the contract *enforcement* is `sorbet-runtime`'s job, not
|
|
800
800
|
Rigor's. Rigor's static analysis uses the cast as a hint, not
|
|
@@ -51,7 +51,7 @@ inference cannot see further).
|
|
|
51
51
|
| `{ name: string; age: number }` | `HashShape{name: String, age: Integer}` | Same per-key model; Ruby uses Symbol keys idiomatically. |
|
|
52
52
|
| `Array<T>` / `T[]` | `Array[T]` | Same. |
|
|
53
53
|
| `Record<K, V>` | `Hash[K, V]` | Same. |
|
|
54
|
-
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
54
|
+
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
|
|
55
55
|
| `Partial<T>` / `Required<T>` | `partial_of[T]` / `required_of[T]` (same plugin) | Flips every entry's required-ness on a `HashShape`. `Partial` does NOT widen value types to `nil` — Rigor's `HashShape` distinguishes "key absent" from "key present with nil value" (ADR-13 WD on required-ness flips). |
|
|
56
56
|
| `Pick<T, K>` / `Omit<T, K>` | `pick_of[T, K]` / `omit_of[T, K]` (same plugin) | Restrict / remove `HashShape` entries by literal-key union; Tuple receivers project by integer index. Non-shape carriers degrade conservatively and surface `dynamic.shape.lossy-projection`. |
|
|
57
57
|
| Conditional types `T extends U ? A : B` | (none in core; plugin contributions) | A plugin can vary return type by argument shape. |
|
|
@@ -159,7 +159,7 @@ inferred call-site instantiation as routinely as TypeScript.
|
|
|
159
159
|
| `Array<T>` | `Array[T]` |
|
|
160
160
|
| `Map<K, V>` | `Hash[K, V]` |
|
|
161
161
|
| `Promise<T>` | (no analogue — Ruby has no built-in Promise) |
|
|
162
|
-
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
162
|
+
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
|
|
163
163
|
| Conditional types | (no analogue — would need a plugin) |
|
|
164
164
|
|
|
165
165
|
Rigor reads RBS generics through its dispatcher and instantiates
|
|
@@ -214,7 +214,7 @@ Be honest about what you give up:
|
|
|
214
214
|
variation, not type-level expressions.
|
|
215
215
|
- **Mapped types.** `Pick`, `Omit`, `Partial`, `Required`, and
|
|
216
216
|
`Readonly` ship as opt-in plugin-supplied vocabulary via
|
|
217
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
217
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types),
|
|
218
218
|
which maps them onto the Rigor-canonical `pick_of` / `omit_of`
|
|
219
219
|
/ `partial_of` / `required_of` / `readonly_of` shape-projection
|
|
220
220
|
type functions on `HashShape` (and `pick_of` / `omit_of` on
|
|
@@ -294,7 +294,7 @@ def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
|
|
|
294
294
|
|
|
295
295
|
The RBS sig stays generic. If you want `Pick<T, K>`'s exact-
|
|
296
296
|
key-set tracking back, opt into the
|
|
297
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
297
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types)
|
|
298
298
|
plugin and annotate the return type with the `Pick` spelling:
|
|
299
299
|
|
|
300
300
|
```rbs
|
|
@@ -38,7 +38,7 @@ the `paths:` list from the configuration file.
|
|
|
38
38
|
| `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. See [Caching](12-caching.md). |
|
|
39
39
|
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics are byte-identical, then exit (0 on match, 1 with the differing diagnostics on mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
|
|
40
40
|
| `--clear-cache` | Delete the cache directory before running. |
|
|
41
|
-
| `--cache-stats` | Print the on-disk cache inventory when finished. |
|
|
41
|
+
| `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
|
|
42
42
|
| `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
|
|
43
43
|
| `--coverage` | Add a type-precision coverage block to the output (`coverage` object under `--format json`; a one-line summary in text mode). Off by default — it is a second precision pass over the analyzed files, the same scan [`rigor coverage`](#rigor-coverage) runs, so it is opt-in. |
|
|
44
44
|
| `--workers=N` | Dispatch analysis across `N` parallel worker processes (fork-based pool today; ADR-15). Default `0` (sequential). Applies to `--incremental` re-checks as well as full runs. |
|
|
@@ -114,16 +114,25 @@ missing file.
|
|
|
114
114
|
|
|
115
115
|
## `rigor type-of`
|
|
116
116
|
|
|
117
|
-
Print
|
|
117
|
+
Print inferred types at one or more source positions.
|
|
118
118
|
|
|
119
119
|
```sh
|
|
120
|
-
rigor type-of FILE:LINE:COL
|
|
121
|
-
rigor type-of FILE LINE COL
|
|
120
|
+
rigor type-of [options] FILE:LINE[:COL] [FILE:LINE[:COL] ...]
|
|
121
|
+
rigor type-of [options] FILE LINE COL
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
The colon form is repeatable and keeps argument order while
|
|
125
|
+
parsing and scope-indexing each file once. Omit `COL` to print a
|
|
126
|
+
table of up to 40 expressions that start on that line, outermost
|
|
127
|
+
first at each 1-based column; the table marks when further
|
|
128
|
+
expressions were omitted. The legacy three-argument form accepts
|
|
129
|
+
one exact position.
|
|
130
|
+
|
|
131
|
+
`--format=json` keeps one result as the original flat object and
|
|
132
|
+
wraps several results in a `results` array. Line queries add a
|
|
133
|
+
`line_enumerations` array whose `shown` and `total` counts make
|
|
134
|
+
truncation explicit. `--trace` records fail-soft fallbacks,
|
|
135
|
+
after the rows of a line table in text output. The editor-mode
|
|
127
136
|
`--tmp-file` / `--instead-of` pair is accepted as on `check`.
|
|
128
137
|
|
|
129
138
|
## `rigor trace`
|
|
@@ -304,8 +313,8 @@ supposed to fan out — the fan-out is the blast radius.
|
|
|
304
313
|
|
|
305
314
|
```yaml
|
|
306
315
|
# .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
|
|
307
|
-
schema:
|
|
308
|
-
rigor: "0.3.
|
|
316
|
+
schema: 2
|
|
317
|
+
rigor: "0.3.5"
|
|
309
318
|
vocabulary: 1
|
|
310
319
|
config_digest: "9ec82bfc…"
|
|
311
320
|
methods:
|
|
@@ -314,7 +323,7 @@ methods:
|
|
|
314
323
|
"Reports::Nightly#perform":
|
|
315
324
|
effects: ["io.db.read"]
|
|
316
325
|
exhaustive: false
|
|
317
|
-
unresolved:
|
|
326
|
+
unresolved: 1
|
|
318
327
|
reach:
|
|
319
328
|
"OrdersController#create":
|
|
320
329
|
effects: ["io.db.read", "io.net.http", "job.enqueue"]
|
|
@@ -376,14 +385,17 @@ spelled out:
|
|
|
376
385
|
Effect drift against .rigor-effects.yml:
|
|
377
386
|
|
|
378
387
|
methods:
|
|
379
|
-
PaymentGateway#charge + io.net.http
|
|
388
|
+
PaymentGateway#charge + io.net.http (app/services/payment_gateway.rb:18)
|
|
380
389
|
|
|
381
390
|
reach:
|
|
382
|
-
OrdersController#create + io.net.http
|
|
391
|
+
OrdersController#create + io.net.http (app/controllers/orders_controller.rb:7)
|
|
383
392
|
|
|
384
|
-
Run `rigor effects
|
|
393
|
+
Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
|
|
385
394
|
```
|
|
386
395
|
|
|
396
|
+
Each row names where the method is defined, so the reviewer reads the report
|
|
397
|
+
rather than searching for the method.
|
|
398
|
+
|
|
387
399
|
The author runs `rigor effects explain` to see the route —
|
|
388
400
|
|
|
389
401
|
```
|
|
@@ -556,7 +568,7 @@ hand-adjudicated corpus target only **7% of the rows were genuinely
|
|
|
556
568
|
unused**; the rest were reachable by means static analysis cannot
|
|
557
569
|
see. That is why this is a separate command and never a `rigor check`
|
|
558
570
|
diagnostic — see
|
|
559
|
-
[ADR-102](
|
|
571
|
+
[ADR-102](../adr/102-unused-code-reachability-report.md).
|
|
560
572
|
|
|
561
573
|
Reachability is computed from **roots**, not by counting references,
|
|
562
574
|
so a cluster of classes that only reference each other is still
|
|
@@ -998,7 +1010,7 @@ Queued today:
|
|
|
998
1010
|
|
|
999
1011
|
| Feature id | Kind | What it changes |
|
|
1000
1012
|
| --- | --- | --- |
|
|
1001
|
-
| `reject-unparseable-signatures` | severity |
|
|
1013
|
+
| `reject-unparseable-signatures` | severity | A broken RBS set **fails the run** instead of degrading it silently: an unparseable `.rbs` under `signature_paths:` (`rbs.coverage.quarantined-signature` → `error`), a declaration that collides on resolve and collapses the whole environment (`rbs.coverage.environment-build-failed` → `error`), and a duplicate method definition that collapses one class's method surface (`rbs.coverage.definition-build-failed` → `error`). |
|
|
1002
1014
|
| `use-of-void-value` | severity | Using a value recovered from an author-declared `-> void` return in value context is reported as `static.value-use.void` (`warning`). |
|
|
1003
1015
|
| `discovery-seeded-mutation-sites` | behaviour | [`rigor coverage --protection --mutation`](15-type-protection-coverage.md) measures against the same cross-file project discovery Tier 1 already uses — both when picking the sites and when deciding whether a breakage was caught — so a call on a project class declared in a *sibling* file is measured instead of dropped, and a breakage there can actually be caught. **Adds sites to the denominator, so the reported effectiveness ratio moves** — check it against any `--threshold` you pin in CI before adopting. |
|
|
1004
1016
|
| `dependent-closure-kill-oracle` | behaviour | [`rigor coverage --protection --mutation`](15-type-protection-coverage.md) counts a breakage as caught when the diagnostic appears anywhere in the mutated file **or the files that depend on it**, instead of in the mutated file alone — so changing what a method returns counts as caught when the error lands in its callers. Can only **add** kills, so the ratio moves up or not at all; it costs about a third more wall time per mutant, and a ratio measured under it is not comparable with one measured without it. |
|
|
@@ -1092,9 +1104,10 @@ operational knobs read the environment instead.
|
|
|
1092
1104
|
| `RIGOR_CI_DETECT=0` | Turn off CI auto-detection — the same as `--no-ci-detect`. See [Running Rigor in CI § auto-detection](11-ci.md). |
|
|
1093
1105
|
| `RIGOR_RACTOR_WORKERS=N` | Worker count for parallel analysis. Sits between the CLI flag and the config key in precedence: `--workers=N` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > `0` (sequential). |
|
|
1094
1106
|
| `RIGOR_POOL_BACKEND=ractor` | Opt back into the (off-by-default) Ractor worker pool instead of the active fork-based pool ([ADR-15](../adr/15-ractor-concurrency.md)). Only relevant with a non-zero worker count; the fork pool is the supported backend. |
|
|
1107
|
+
| `RIGOR_LSP_POOL_MIN_BATCH=N` | Fewest buffers an [`rigor lsp`](#rigor-lsp) batch must carry before analysis is dispatched across the worker pool rather than run in-process (default `16`). Lower it if your project's per-file analysis is expensive enough that pooling pays off sooner. |
|
|
1095
1108
|
| `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
|
|
1096
1109
|
| `RIGOR_STRICT_VALIDATION=1` | Force full-content cache validation for one run (the same as `cache.validation: digest`, and winning over it) — re-hash every file's content instead of trusting its stat metadata. Use it if a filesystem's timestamps or inode numbers cannot be trusted. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). |
|
|
1097
|
-
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through
|
|
1110
|
+
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through any long run so short runs never pay the JIT warm-up; this variable leaves it off entirely. Diagnostics and allocations are identical either way — the effect is wall-time only. |
|
|
1098
1111
|
| `RIGOR_YJIT_DEADLINE=<seconds>` | Advanced: tune how long a run must last before deferred YJIT enables (default `5.0`). Lower it if your runs are long and you want the JIT sooner; raise it to protect short runs. Ignored when `RIGOR_DISABLE_YJIT=1` is set or YJIT is unavailable. |
|
|
1099
1112
|
|
|
1100
1113
|
Three further variables (`RIGOR_BUDGET_TRACE`,
|
|
@@ -251,7 +251,7 @@ stanza it broke:
|
|
|
251
251
|
```
|
|
252
252
|
app/presenters/user_presenter.rb:14:1: warning: Method Presenters::User#render performs io.fs.read
|
|
253
253
|
(File.read), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds
|
|
254
|
-
the envelope.
|
|
254
|
+
the envelope. [effect.envelope-exceeded]
|
|
255
255
|
```
|
|
256
256
|
|
|
257
257
|
When one method is a deliberate exception, write the narrower envelope on it in RBS — nearest wins,
|
|
@@ -19,6 +19,18 @@ Every rule has a two-segment `family.rule` identifier:
|
|
|
19
19
|
| `assert` | `assert_type` checks. |
|
|
20
20
|
| `dump` | `dump_type` notices. |
|
|
21
21
|
|
|
22
|
+
Every diagnostic carries its identifier in brackets at the end of the
|
|
23
|
+
line, so the ID you need for `# rigor:disable`, for `disable:` and for
|
|
24
|
+
`severity_profile:` is the one you are already looking at:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
app/user.rb:11:3: error: undefined method `lenght' for "hello" [call.undefined-method]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
A diagnostic no rule produced — a parse error, a path error, an
|
|
31
|
+
internal analyzer error — has nothing to suppress and carries no
|
|
32
|
+
bracket.
|
|
33
|
+
|
|
22
34
|
`rigor explain <rule>` prints the full catalogue entry for any
|
|
23
35
|
built-in rule ID; `rigor explain` with no argument lists them all.
|
|
24
36
|
|
|
@@ -63,19 +63,31 @@ syntax-highlighted for a tty — through
|
|
|
63
63
|
colorizer; `--no-color` (and the `NO_COLOR` environment
|
|
64
64
|
variable) disable the colour.
|
|
65
65
|
|
|
66
|
-
## `rigor type-of` —
|
|
66
|
+
## `rigor type-of` — exact positions or a whole line
|
|
67
67
|
|
|
68
|
-
When you
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
When you need a few expression types — typically while chasing
|
|
69
|
+
down why a diagnostic did or did not fire — query the exact
|
|
70
|
+
positions together so Rigor loads the project and each source
|
|
71
|
+
file once:
|
|
71
72
|
|
|
72
73
|
```sh
|
|
73
|
-
rigor type-of lib/example.rb:12:8
|
|
74
|
+
rigor type-of lib/example.rb:12:8 lib/example.rb:12:14
|
|
74
75
|
```
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
Leave off the column to avoid counting it by hand. Rigor prints
|
|
78
|
+
a table of the first 40 expressions starting on the line,
|
|
79
|
+
outermost first at each 1-based column, and marks a truncated
|
|
80
|
+
table:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
rigor type-of lib/example.rb:12
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`--format=json` emits a machine-readable result for tooling: one
|
|
87
|
+
result stays a flat object, while several results use a `results`
|
|
88
|
+
array. Line queries add `line_enumerations` metadata with the
|
|
89
|
+
shown and total expression counts. An exact position is the same
|
|
90
|
+
query the editor integration answers on hover.
|
|
79
91
|
|
|
80
92
|
## `rigor trace` — watch the inference happen
|
|
81
93
|
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -49,6 +49,10 @@ each plugin's options — but the families today are:
|
|
|
49
49
|
`rigor-factorybot`.
|
|
50
50
|
- **dry-rb** — `rigor-dry-types`, `rigor-dry-schema`,
|
|
51
51
|
`rigor-dry-struct`, `rigor-dry-validation`.
|
|
52
|
+
- **FFI** — `rigor-ffi` and sub-plugins (`rigor-ffi-rzmq`,
|
|
53
|
+
`rigor-rbnacl`, `rigor-sassc`, `rigor-ethon`). For plugin authors,
|
|
54
|
+
`rigor-ffi` provides the `ffi_binding_recognizer` class DSL on
|
|
55
|
+
`Rigor::Plugin::Base` to recognize custom binding definitions.
|
|
52
56
|
- **Other ecosystems** — `rigor-sinatra`, `rigor-hanami`,
|
|
53
57
|
`rigor-devise`, `rigor-pundit`, `rigor-sidekiq`,
|
|
54
58
|
`rigor-graphql`, `rigor-statesman`, `rigor-sorbet`,
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Rigor bundles a set of **Agent Skills** — structured workflows an AI
|
|
4
4
|
coding agent (Claude Code and compatible tools) can run on your behalf.
|
|
5
|
-
They live in [`skills/`](../../skills
|
|
5
|
+
They live in [`skills/`](../../skills) and are auto-discovered when an
|
|
6
6
|
agent works inside a project that has Rigor available.
|
|
7
7
|
|
|
8
8
|
Skills are optional. Everything they do, you can do by hand with the
|
data/docs/manual/11-ci.md
CHANGED
|
@@ -73,7 +73,7 @@ platform reads to annotate the diff directly, instead of leaving them in
|
|
|
73
73
|
the job log ([ADR-51](../adr/51-ci-diagnostic-output-formats.md)). These
|
|
74
74
|
layer on the generic `--format json` stream; they add no new diagnostics,
|
|
75
75
|
only a platform-native rendering. Ready-to-copy template files live under
|
|
76
|
-
[`ci-templates/`](ci-templates
|
|
76
|
+
[`ci-templates/`](ci-templates).
|
|
77
77
|
|
|
78
78
|
### GitHub — inline annotations (the default)
|
|
79
79
|
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -137,7 +137,7 @@ CI detection that `auto` relies on.
|
|
|
137
137
|
| --- | --- |
|
|
138
138
|
| `rigor check --no-cache` | Run without reading or writing the persistent cache. |
|
|
139
139
|
| `rigor check --clear-cache` | Delete the cache directory, then run. |
|
|
140
|
-
| `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes. |
|
|
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). |
|
|
142
142
|
|
|
143
143
|
There is no config key to disable caching permanently — the
|
|
@@ -333,7 +333,7 @@ ones a type actually catches.
|
|
|
333
333
|
> [Configuration](03-configuration.md)). A project whose gems live in
|
|
334
334
|
> the active Ruby's default gem home — the common `rbenv` / `mise`
|
|
335
335
|
> case with no `--path` set — is invisible to the isolated analyzer by
|
|
336
|
-
> design ([ADR-27](
|
|
336
|
+
> design ([ADR-27](../adr/27-tool-distribution-model.md)):
|
|
337
337
|
> point Rigor at it with `bundler.bundle_path:`. Until you do, these
|
|
338
338
|
> holes keep the generic `engine_gap` cause instead of `add_rbs` —
|
|
339
339
|
> the label is missing, never wrong.
|
|
@@ -140,7 +140,7 @@ Four sections, needing four different decisions:
|
|
|
140
140
|
|
|
141
141
|
| Section | What it means | What to do |
|
|
142
142
|
| --- | --- | --- |
|
|
143
|
-
| **Reachable only from test code** | Live test, no production caller | Work these first |
|
|
143
|
+
| **Reachable only from test code** | Live test, no production caller Rigor can see or suspect | Work these first |
|
|
144
144
|
| **Candidates** | Nothing reachable names it | Adjudicate — most are still live |
|
|
145
145
|
| **Cannot decide** | Something can name it at runtime | Read the reason; do not delete from here |
|
|
146
146
|
| **Namespace-only** | A module wrapping live code | Excluded from candidates; count only |
|
|
@@ -209,14 +209,23 @@ skip most of a list quickly:
|
|
|
209
209
|
|
|
210
210
|
### Cannot decide: read the reason, do not delete
|
|
211
211
|
|
|
212
|
-
These were demoted
|
|
213
|
-
|
|
212
|
+
These were demoted because something can name the class at runtime —
|
|
213
|
+
out of `candidates`, or out of **reachable only from test code**. Each
|
|
214
|
+
row says what:
|
|
214
215
|
|
|
215
216
|
```
|
|
216
217
|
1 Handlers::Alpha lib/handlers.rb:2
|
|
217
218
|
constantize on an interpolated string (lib/dispatch.rb:14)
|
|
218
219
|
```
|
|
219
220
|
|
|
221
|
+
A row demoted out of the test-only section is the second kind, and it
|
|
222
|
+
is the one worth knowing about: a class your specs reference and a
|
|
223
|
+
data file also names — a job in `config/recurring.yml`, a class named
|
|
224
|
+
from a YAML setting — is not a dead production path, because the
|
|
225
|
+
configuration may well be what drives it. The test-only section makes
|
|
226
|
+
a claim about production, so a row Rigor holds evidence against
|
|
227
|
+
belongs here instead, with the file named.
|
|
228
|
+
|
|
220
229
|
`"Foo".constantize` names `Foo` exactly, so it counts as an ordinary
|
|
221
230
|
reference and never reaches this section. `"Foo::#{key}".constantize`
|
|
222
231
|
can only bound the namespace, so everything under `Foo` is demoted. A
|
|
@@ -322,5 +331,5 @@ Running it as a **reporting** job is fine, and is the intended way to
|
|
|
322
331
|
notice the drift described above: run it on a schedule, publish the
|
|
323
332
|
JSON, and let a person read the trend.
|
|
324
333
|
|
|
325
|
-
[adr-102]:
|
|
334
|
+
[adr-102]: ../adr/102-unused-code-reachability-report.md
|
|
326
335
|
[issue-363]: https://github.com/rigortype/rigor/issues/363
|
|
@@ -313,8 +313,8 @@ digest of your `effects:` block, so an upgrade or a policy edit shows up as a
|
|
|
313
313
|
|
|
314
314
|
```yaml
|
|
315
315
|
# .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
|
|
316
|
-
schema:
|
|
317
|
-
rigor: "0.3.
|
|
316
|
+
schema: 2
|
|
317
|
+
rigor: "0.3.5"
|
|
318
318
|
vocabulary: 1
|
|
319
319
|
config_digest: "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
|
320
320
|
methods:
|
|
@@ -324,10 +324,12 @@ methods:
|
|
|
324
324
|
|
|
325
325
|
Two notes on reading the file:
|
|
326
326
|
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
- **`unresolved:` is a count, not a list.** It says how many calls the analyzer
|
|
328
|
+
could not follow, which is why `exhaustive:` is false. The causes themselves
|
|
329
|
+
are not recorded — they are inference-quality metadata that churns on Rigor
|
|
330
|
+
upgrades and on unrelated edits — so ask for them when you need them:
|
|
331
|
+
`rigor effects explain` names them, including for an `exhaustive → not` row.
|
|
332
|
+
The lines a reviewer reads are `effects:` and `declared:`.
|
|
331
333
|
- Trivial and synthesised entries are left out, as in the report. `--full`
|
|
332
334
|
records everything, and produces a much larger and much noisier file.
|
|
333
335
|
|
|
@@ -398,11 +400,11 @@ CI runs `rigor effects check` and fails, exit 1:
|
|
|
398
400
|
Effect drift against .rigor-effects.yml:
|
|
399
401
|
|
|
400
402
|
methods:
|
|
401
|
-
Change#init_path + io.fs.write
|
|
402
|
-
Change#init_path + nondet.time
|
|
403
|
-
Change#init_path exhaustive → not
|
|
403
|
+
Change#init_path + io.fs.write (app/models/change.rb:41)
|
|
404
|
+
Change#init_path + nondet.time (app/models/change.rb:41)
|
|
405
|
+
Change#init_path exhaustive → not (app/models/change.rb:41)
|
|
404
406
|
|
|
405
|
-
Run `rigor effects
|
|
407
|
+
Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
|
|
406
408
|
```
|
|
407
409
|
|
|
408
410
|
`+ label` and `- label` are the proven lane; `≤+` / `≤-` the declared one;
|
|
@@ -410,6 +412,11 @@ Run `rigor effects update` and commit the result if this change is intended.
|
|
|
410
412
|
someone introduced a call Rigor cannot follow; `+symbol` / `-symbol` are methods
|
|
411
413
|
that appeared or vanished, and a rename is one of each.
|
|
412
414
|
|
|
415
|
+
The parenthetical is where the method is defined — the file, and the `def`'s own
|
|
416
|
+
line when the file has one. A method defined by a reopening spans several files
|
|
417
|
+
and the row names them all; a `-symbol` row carries no position, because a method
|
|
418
|
+
this run no longer sees is one it cannot locate.
|
|
419
|
+
|
|
413
420
|
Before regenerating, ask why:
|
|
414
421
|
|
|
415
422
|
```sh
|
|
@@ -518,7 +525,7 @@ A method that exceeds its bound gets one diagnostic per (method, label) pair, at
|
|
|
518
525
|
its `def`, naming the route:
|
|
519
526
|
|
|
520
527
|
```
|
|
521
|
-
app/helpers/application_helper.rb:59:1: warning: Method ApplicationHelper#link_to_principal performs io.fs.read (Dir.glob via IconsHelper#principal_icon → IconsHelper#sprite_icon → IconsHelper#sprite_source → Redmine::Themes::Helper#current_theme → Redmine::Themes.theme → Redmine::Themes.themes → Redmine::Themes.scan_themes), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds the envelope.
|
|
528
|
+
app/helpers/application_helper.rb:59:1: warning: Method ApplicationHelper#link_to_principal performs io.fs.read (Dir.glob via IconsHelper#principal_icon → IconsHelper#sprite_icon → IconsHelper#sprite_source → Redmine::Themes::Helper#current_theme → Redmine::Themes.theme → Redmine::Themes.themes → Redmine::Themes.scan_themes), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds the envelope. [effect.envelope-exceeded]
|
|
522
529
|
```
|
|
523
530
|
|
|
524
531
|
**Budget for a big first number.** That one stanza, on Redmine, is **343
|
|
@@ -608,7 +615,7 @@ app/serializers/rest/v1/instance_serializer.rb:89:1: warning: Method
|
|
|
608
615
|
REST::V1::InstanceSerializer#invites_enabled performs mutate.self
|
|
609
616
|
(receiver-mutation via UserRole.everyone → UserRole.create! → UserRole#set_position),
|
|
610
617
|
but is declared effect: [] at .rigor.yml effects.envelopes[0], so mutate.self
|
|
611
|
-
exceeds the envelope.
|
|
618
|
+
exceeds the envelope. [effect.envelope-exceeded]
|
|
612
619
|
```
|
|
613
620
|
|
|
614
621
|
Rigor walked *through* the database write and reported the ivar assignment beyond
|
data/docs/manual/README.md
CHANGED
|
@@ -80,7 +80,7 @@ flag, key, or command that *acts* on it.
|
|
|
80
80
|
Cline, …) via `rigor mcp`.
|
|
81
81
|
11. [Running Rigor in CI](11-ci.md) — a clean CI job, inline
|
|
82
82
|
PR/MR diagnostics (SARIF / GitHub Actions / GitLab Code
|
|
83
|
-
Quality), copy-paste [templates](ci-templates
|
|
83
|
+
Quality), copy-paste [templates](ci-templates), and
|
|
84
84
|
version pinning.
|
|
85
85
|
12. [Caching](12-caching.md) — where the cache lives, what
|
|
86
86
|
invalidates it, and how to clear it.
|
|
@@ -94,4 +94,4 @@ flag, key, or command that *acts* on it.
|
|
|
94
94
|
- [`docs/types.md`](../types.md) — one-page type-system guide.
|
|
95
95
|
- [`docs/type-specification/`](../type-specification/README.md)
|
|
96
96
|
— the normative spec corpus.
|
|
97
|
-
- [`docs/adr/`](../adr
|
|
97
|
+
- [`docs/adr/`](../adr) — architecture decision records.
|
|
@@ -17,10 +17,10 @@ plugins:
|
|
|
17
17
|
## What it checks
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
demo.rb:7:1:
|
|
21
|
-
errors_demo.rb:
|
|
22
|
-
errors_demo.rb:
|
|
23
|
-
app/mailers/user_mailer.rb:
|
|
20
|
+
demo.rb:7:1: info: `UserMailer.welcome` matches mailer action (arity 1..2) [plugin.actionmailer.mailer-call]
|
|
21
|
+
errors_demo.rb:10:1: error: `UserMailer.welcome` expects 1..2 argument(s), got 0 [plugin.actionmailer.wrong-arity]
|
|
22
|
+
errors_demo.rb:18:1: error: `UserMailer.does_not_exist` is not a defined mailer action (known actions: digest, reset_password, welcome) [plugin.actionmailer.unknown-action]
|
|
23
|
+
app/mailers/user_mailer.rb:26:7: warning: `UserMailer#digest` has no view template under `app/views/user_mailer/` [plugin.actionmailer.missing-view]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
1. **Action existence** — `Mailer.unknown_action(...)` →
|
|
@@ -56,6 +56,43 @@ plugins:
|
|
|
56
56
|
view_search_paths: ["app/views"] # default
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## What it types
|
|
60
|
+
|
|
61
|
+
Inside a controller, `params`, `request`, `session`, `flash` and
|
|
62
|
+
`cookies` type as their Action Pack classes, and so do the chains
|
|
63
|
+
built on them:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
request.post? # bool — and so do get? / put? / patch? / delete? /
|
|
67
|
+
# head? / options? / trace? / link? / unlink? /
|
|
68
|
+
# xhr? / xml_http_request? / ssl? / local? / form_data?
|
|
69
|
+
flash.now # ActionDispatch::Flash::FlashNow
|
|
70
|
+
flash.keep # ActionDispatch::Flash::FlashHash
|
|
71
|
+
flash[:notice] = "hi" # "hi" — an assignment is its right-hand side
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Rigor ships **no signature** for these Action Pack classes, on
|
|
75
|
+
purpose: the receiver becomes concrete (so `rigor coverage
|
|
76
|
+
--protection` counts the site) while the method surface stays
|
|
77
|
+
lenient, so `request.headers`, `flash.now[:alert] = x` and anything
|
|
78
|
+
else the framework adds resolve without a diagnostic. A partial
|
|
79
|
+
signature would be worse than none — every member it omitted would
|
|
80
|
+
become a false `call.undefined-method`.
|
|
81
|
+
|
|
82
|
+
The predicates are typed `bool` — the union of `true` and `false` —
|
|
83
|
+
which is both the real contract (every one of them is an `==`,
|
|
84
|
+
`match?` or `include?` in Rails or Rack) and the reason they are safe
|
|
85
|
+
to type at all: a condition that folds needs to prove *one* constant,
|
|
86
|
+
and a union of both never does. `return unless request.post?` and
|
|
87
|
+
`mode = request.get? ? :a : :b` read exactly as they did before.
|
|
88
|
+
|
|
89
|
+
`request.format` is **not** typed. That inertness argument is
|
|
90
|
+
narrower than it looks — it holds for a union of the two boolean
|
|
91
|
+
constants, not for a union of ordinary classes, which is nil-free and
|
|
92
|
+
so *can* fold a condition — and `Mime::NullType`, the value `format`
|
|
93
|
+
returns when there is no format, answers `nil?` with `true` while
|
|
94
|
+
being a real object. Typing it needs a nil-aware answer.
|
|
95
|
+
|
|
59
96
|
## Limitations
|
|
60
97
|
|
|
61
98
|
- **Implicit-self helpers only.** `*_path` / `*_url` calls with an
|
|
@@ -69,6 +106,30 @@ plugins:
|
|
|
69
106
|
knows what `rigor-rails-routes` published, and `permit`
|
|
70
107
|
validation only what `rigor-activerecord` published — enabling
|
|
71
108
|
those producers widens what this plugin can check.
|
|
109
|
+
- **`params[:key]` stays untyped.** Inside a controller, `params`
|
|
110
|
+
types as `ActionController::Parameters`, and so does the result
|
|
111
|
+
of every builder method that always returns one — `require`,
|
|
112
|
+
`permit`, `permit!`, `expect`, `slice`, `slice!`, `except`,
|
|
113
|
+
`without`, `extract!`, `merge`, `merge!`, `reverse_merge`,
|
|
114
|
+
`reverse_merge!`, `with_defaults`, `with_defaults!`, `compact`,
|
|
115
|
+
`compact_blank`, `deep_dup` — so a chain built from them keeps a
|
|
116
|
+
concrete receiver throughout. A subscript read is deliberately
|
|
117
|
+
left untyped: `params[:missing]` is `nil` at runtime, and a type
|
|
118
|
+
that says otherwise would let the flow rules fold live
|
|
119
|
+
conditions (`if params[:q]`, `url.nil?`) to a constant and report
|
|
120
|
+
working code. Methods whose result depends on the call — `dig`,
|
|
121
|
+
`fetch`, `compact!`, and the block-less `select` / `reject` /
|
|
122
|
+
`transform_keys` / `transform_values` — are untyped for the same
|
|
123
|
+
reason.
|
|
124
|
+
- **`flash[:key]` and `session[:key]` stay untyped too**, for that
|
|
125
|
+
same reason and measured the same way. Both are leaf reads that
|
|
126
|
+
return whatever was stored — or `nil` for a key that is not set.
|
|
127
|
+
A non-nil type folds `mode = flash[:notice] ? … : …` to one arm and
|
|
128
|
+
reports the live guard after it; a nullable one puts
|
|
129
|
+
`call.possible-nil-receiver` on `note = flash[:notice];
|
|
130
|
+
note.upcase`. Writing through them is unaffected: `flash[:k] = v`
|
|
131
|
+
is `v` because that is what an assignment expression means in Ruby,
|
|
132
|
+
with no rule needed.
|
|
72
133
|
|
|
73
134
|
## Plugin internals
|
|
74
135
|
|
|
@@ -18,9 +18,9 @@ Given a job whose `#perform` takes one required and one optional
|
|
|
18
18
|
argument (arity `1..2`):
|
|
19
19
|
|
|
20
20
|
```text
|
|
21
|
-
demo.rb:
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
demo.rb:8:1: info: `WelcomeEmailJob.perform_later` matches `#perform` (arity 1..2) [plugin.activejob.job-call]
|
|
22
|
+
errors_demo.rb:10:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 0 [plugin.activejob.wrong-arity]
|
|
23
|
+
errors_demo.rb:14:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 3 [plugin.activejob.wrong-arity]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
A `*rest` parameter yields an unbounded upper bound (`arity 0+`).
|