rigortype 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +4 -3
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +99 -4
- data/docs/manual/02-cli-reference.md +65 -19
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +5 -5
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +21 -0
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +207 -60
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +56 -30
- data/lib/rigor/cli/diagnostic_formats.rb +4 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/protection_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_report.rb +7 -1
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +71 -43
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +379 -113
- data/lib/rigor/inference/fork_map.rb +87 -0
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +245 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +70 -10
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +460 -49
- data/lib/rigor/inference/statement_evaluator.rb +111 -17
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +23 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +21 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +13 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +65 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +20 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +22 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +191 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +19 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +48 -15
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +42 -12
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/environment.rbs +1 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +49 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -22,6 +22,24 @@ discovery entirely.
|
|
|
22
22
|
All relative paths in a config file resolve against that
|
|
23
23
|
file's own directory.
|
|
24
24
|
|
|
25
|
+
## Editor validation
|
|
26
|
+
|
|
27
|
+
Rigor ships a JSON Schema for this file. Editors that
|
|
28
|
+
understand the [`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server)
|
|
29
|
+
magic comment — VS Code's YAML extension, the IntelliJ
|
|
30
|
+
family, Helix, Neovim with `yaml-ls` — give you
|
|
31
|
+
autocomplete, hover docs, and structural validation as you
|
|
32
|
+
type:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
# yaml-language-server: $schema=https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`rigor init` writes that line for you. The schema is not a
|
|
39
|
+
copy of this page — it is a source of truth in its own
|
|
40
|
+
right, kept in step with the loader by a spec, so a key it
|
|
41
|
+
rejects is a key Rigor does not accept.
|
|
42
|
+
|
|
25
43
|
## A minimal config
|
|
26
44
|
|
|
27
45
|
```yaml
|
|
@@ -44,6 +62,7 @@ cache:
|
|
|
44
62
|
| `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
45
63
|
| `includes` | Array | `[]` | Other config files to layer underneath this one. |
|
|
46
64
|
| `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
|
|
65
|
+
| `parameter_inference` | Boolean | `false` | Opt-in call-site parameter type inference on the `check` walk ([ADR-67](../adr/67-parameter-type-inference.md) WD6). When `true`, an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved call-site argument types, sharpening downstream ivar reads, folds, and protection coverage. Precision-additive only — the negative rules never fire against an inferred parameter. Cannot be combined with `--incremental`. |
|
|
47
66
|
|
|
48
67
|
### Type sources
|
|
49
68
|
|
|
@@ -65,6 +84,7 @@ types it was meant to describe into a high-confidence
|
|
|
65
84
|
of real type errors. The audit covers:
|
|
66
85
|
|
|
67
86
|
```
|
|
87
|
+
rigor: `excludee` is not a recognized configuration key; it has no effect. Did you mean `exclude`?
|
|
68
88
|
rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from it)
|
|
69
89
|
rigor: signature_paths: "/path/to/sig" matched 0 signature files
|
|
70
90
|
rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
|
|
@@ -73,6 +93,11 @@ rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the overrid
|
|
|
73
93
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
74
94
|
```
|
|
75
95
|
|
|
96
|
+
The unrecognised-key check covers **top-level** keys, and skips
|
|
97
|
+
the namespaces reserved for other implementations (see below).
|
|
98
|
+
A typo *inside* a group — `cache: { pth: … }` — is caught by
|
|
99
|
+
the JSON schema as you type rather than at check time.
|
|
100
|
+
|
|
76
101
|
These are warnings, not errors — partial or optional bundles and
|
|
77
102
|
forward-looking config are valid setups. The audit only fires on explicit,
|
|
78
103
|
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
@@ -91,7 +116,7 @@ statically and may resolve at run time. The same findings appear in the
|
|
|
91
116
|
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
92
117
|
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
93
118
|
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
94
|
-
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge).
|
|
119
|
+
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge). |
|
|
95
120
|
|
|
96
121
|
### Dependency RBS discovery
|
|
97
122
|
|
|
@@ -128,11 +153,18 @@ explicitly with `bundler.bundle_path:`, or supply signatures another way:
|
|
|
128
153
|
| --- | --- | --- | --- |
|
|
129
154
|
| `cache.path` | String | `.rigor/cache` | Persistent cache directory. See [Caching](12-caching.md). |
|
|
130
155
|
| `cache.max_bytes` | Integer or `null` | `268435456` (256 MB) | LRU eviction cap for the cache directory; `null` disables eviction. See [Caching § Size and eviction](12-caching.md#size-and-eviction). |
|
|
131
|
-
| `
|
|
156
|
+
| `cache.validation` | String | `"auto"` | How the cache checks whether a file is unchanged: `auto` behaves as `digest` when a CI environment is detected and as `stat` otherwise; `stat` compares size + nanosecond timestamps + inode and only re-hashes a file whose stat moved; `digest` re-hashes every file's content every run. Both keep the content hash as the sole change authority — `stat` just skips the hash when the stat proves a file untouched. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). The `RIGOR_STRICT_VALIDATION=1` environment variable forces `digest` for one run and wins over this key; `RIGOR_CI_DETECT=0` disables the CI detection. |
|
|
157
|
+
| `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. Applies to `--incremental` re-checks as well as full runs. |
|
|
132
158
|
| `plugins_io.network` | String | `"disabled"` | Plugin network policy — `disabled` or `allowlist`. |
|
|
133
159
|
| `plugins_io.allowed_paths` | Array | `[]` | Filesystem paths plugins may read. |
|
|
134
160
|
| `plugins_io.allowed_url_hosts` | Array | `[]` | URL hosts plugins may fetch from when `network: allowlist`. |
|
|
135
161
|
|
|
162
|
+
### Reserved for other implementations
|
|
163
|
+
|
|
164
|
+
| Key | Type | Default | Meaning |
|
|
165
|
+
| --- | --- | --- | --- |
|
|
166
|
+
| `rigor_rs` | Hash | — | **Reserved; this implementation skips it.** Another Rigor implementation reads keys under this namespace, so one `.rigor.yml` can serve both. Rigor validates its shape at the schema level only — it never reads the value, and an invalid one is never a runtime error here. Leave it alone unless the tool that reads it tells you otherwise. |
|
|
167
|
+
|
|
136
168
|
## A worked example
|
|
137
169
|
|
|
138
170
|
```yaml
|
|
@@ -41,18 +41,26 @@ carries no `documentation_url`.
|
|
|
41
41
|
| <a id="rule-call-wrong-arity"></a>`call.wrong-arity` | The positional-argument count matches no signature. | high |
|
|
42
42
|
| <a id="rule-call-argument-type-mismatch"></a>`call.argument-type-mismatch` | An argument's type provably violates the parameter contract. | high |
|
|
43
43
|
| <a id="rule-call-possible-nil-receiver"></a>`call.possible-nil-receiver` | The receiver is `T \| nil` and the method is not defined on `NilClass`. | high |
|
|
44
|
+
| <a id="rule-call-raise-non-exception"></a>`call.raise-non-exception` | A `raise` / `fail` argument's concrete type is provably not an Exception class, an Exception instance, a String, or an object defining `#exception` — a runtime `TypeError`. | high |
|
|
44
45
|
| <a id="rule-call-unresolved-toplevel"></a>`call.unresolved-toplevel` | A top-level implicit-self call resolves against no same-file `def`, `pre_eval:` patch, or `Kernel` / `Object` method. | low |
|
|
45
46
|
| <a id="rule-flow-always-raises"></a>`flow.always-raises` | The expression provably raises on every reachable path. | high |
|
|
46
47
|
| <a id="rule-flow-unreachable-branch"></a>`flow.unreachable-branch` | An `if` / `unless` / ternary branch is statically dead. | high |
|
|
47
48
|
| <a id="rule-flow-always-truthy-condition"></a>`flow.always-truthy-condition` | A condition is provably always truthy or always falsey. | medium |
|
|
48
49
|
| <a id="rule-flow-dead-assignment"></a>`flow.dead-assignment` | A local is written but never read in the same method. | medium |
|
|
49
50
|
| <a id="rule-flow-unreachable-clause"></a>`flow.unreachable-clause` | A `case`/`when` or `case`/`in` clause is statically dead — its subject type is disjoint with the pattern, or a prior clause already exhausted the subject. | medium |
|
|
51
|
+
| <a id="rule-flow-duplicate-hash-key"></a>`flow.duplicate-hash-key` | A Hash literal repeats a literal key (symbol, plain string, integer, float, `true`/`false`/`nil`) — the last entry silently overwrites the earlier one at runtime. Literal keys only; symbol vs string and `1` vs `1.0` never collide, and interpolated / constant / computed keys are never compared. A `**splat` between two identical literal keys does not rescue the pair. | high |
|
|
52
|
+
| <a id="rule-flow-return-in-ensure"></a>`flow.return-in-ensure` | An explicit `return` inside an `ensure` clause — it overrides the method's in-flight return value and silently swallows any in-flight exception. A `return` in a nested `def`, lambda, or `define_method` block inside the `ensure` does not fire (it exits that inner frame). | high |
|
|
53
|
+
| <a id="rule-flow-shadowed-rescue-clause"></a>`flow.shadowed-rescue-clause` | A `rescue` clause can never run because an earlier clause of the same chain already catches a superclass (or the same class) of every exception class it names. | high |
|
|
50
54
|
| <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
|
|
51
55
|
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write. | high |
|
|
52
56
|
| <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
|
|
53
57
|
| <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
|
|
54
58
|
| <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
|
|
55
59
|
| <a id="rule-def-override-param-narrowed"></a>`def.override-param-narrowed` | An override narrows an inherited parameter type (contravariance). | high |
|
|
60
|
+
| <a id="rule-static-value-use-void"></a>`static.value-use.void` | A value recovered from an author-declared `-> void` return is used in value context (an assignment right-hand side, a call receiver, or a call argument). Off by default; reaches a run only through the `use-of-void-value` bleeding-edge feature (ADR-100). A bare-statement `void` call and a legitimate `top` value both stay silent. | high |
|
|
61
|
+
| <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
|
|
62
|
+
| <a id="rule-suppression-empty"></a>`suppression.empty` | A `# rigor:disable[-file]` comment lists no rules, so it suppresses nothing. | high |
|
|
63
|
+
| <a id="rule-suppression-unknown-marker"></a>`suppression.unknown-marker` | A comment uses a suppression marker Rigor does not recognise — typically the RuboCop reflex `# rigor:disable-next-line <rule>` or `# rigor:enable <rule>`. Rigor's only markers are `# rigor:disable <rules>` (suppresses on its own line) and `# rigor:disable-file <rules>`, so the comment suppresses nothing. | high |
|
|
56
64
|
| <a id="rule-rbs_extended-unsatisfied-conformance"></a>`rbs_extended.unsatisfied-conformance` | A class declares `%a{rigor:v1:conforms-to _Interface}` in its RBS but is missing a method the interface requires. Presence-based: only definitively-absent required methods fire. | — |
|
|
57
65
|
| <a id="rule-assert-type-mismatch"></a>`assert.type-mismatch` | An `assert_type` expectation does not match the inferred type. | high |
|
|
58
66
|
| <a id="rule-dump-type"></a>`dump.type` | A `dump_type` call — informational, prints the inferred type. | — |
|
|
@@ -192,6 +200,21 @@ config.merge(extra) # rigor:disable call.undefined-method
|
|
|
192
200
|
It accepts qualified IDs, family wildcards (`call`), a
|
|
193
201
|
comma- or space-separated list, or `all`.
|
|
194
202
|
|
|
203
|
+
A marker that cannot work is flagged rather than silently
|
|
204
|
+
ignored: a token that names no known rule (a typo like
|
|
205
|
+
`call.undefined-metod`) fires
|
|
206
|
+
[`suppression.unknown-rule`](#rule-suppression-unknown-rule),
|
|
207
|
+
a bare marker with no rules at all fires
|
|
208
|
+
[`suppression.empty`](#rule-suppression-empty), and a marker
|
|
209
|
+
word outside Rigor's grammar (the RuboCop reflex
|
|
210
|
+
`# rigor:disable-next-line <rule>`, or `# rigor:enable`)
|
|
211
|
+
fires
|
|
212
|
+
[`suppression.unknown-marker`](#rule-suppression-unknown-marker)
|
|
213
|
+
— all `:warning` in every profile. Tokens under the `plugin.`
|
|
214
|
+
prefix are never flagged (plugin rule vocabularies load
|
|
215
|
+
dynamically), and the surveillance diagnostics are themselves
|
|
216
|
+
suppressible like any other rule.
|
|
217
|
+
|
|
195
218
|
**In-source, whole file.** `# rigor:disable-file <rules>`
|
|
196
219
|
anywhere in a file suppresses those rules for every line;
|
|
197
220
|
`# rigor:disable-file all` silences the file.
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -27,7 +27,7 @@ configuration takes the object form:
|
|
|
27
27
|
plugins:
|
|
28
28
|
- gem: rigor-activerecord
|
|
29
29
|
config:
|
|
30
|
-
|
|
30
|
+
schema_file: db/schema.rb
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Available plugins
|
|
@@ -40,10 +40,10 @@ each plugin's options — but the families today are:
|
|
|
40
40
|
- **Rails** — `rigor-activerecord`, `rigor-actionpack`,
|
|
41
41
|
`rigor-rails-routes`, `rigor-rails-i18n`,
|
|
42
42
|
`rigor-actionmailer`, `rigor-activejob`,
|
|
43
|
-
`rigor-activestorage`, `rigor-actioncable`.
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
`rigor-activestorage`, `rigor-actioncable`. Enumerate the ones
|
|
44
|
+
you want under `plugins:` — there is no umbrella entry that
|
|
45
|
+
turns on the Rails set as a group
|
|
46
|
+
([ADR-96](../adr/96-plugin-target-gems.md) WD3 proposes one).
|
|
47
47
|
- **Testing** — `rigor-rspec`, `rigor-rspec-rails`,
|
|
48
48
|
`rigor-minitest`, `rigor-shoulda-matchers`,
|
|
49
49
|
`rigor-factorybot`.
|
|
@@ -201,8 +201,8 @@ For a legacy bundler-based install, swap the connection list to
|
|
|
201
201
|
analyzer assigns the receiver.
|
|
202
202
|
- Mid-edit buffer support is best-effort. If parse fails AND the
|
|
203
203
|
cursor isn't right after `.` / `::`, the v1 LSP returns no
|
|
204
|
-
completions; deeper recovery is queued (see
|
|
205
|
-
|
|
204
|
+
completions; deeper recovery is queued (see the open
|
|
205
|
+
[`area:editor` issues](https://github.com/rigortype/rigor/issues?q=is%3Aissue+is%3Aopen+label%3Aarea%3Aeditor)).
|
|
206
206
|
|
|
207
207
|
**Hover shows `untyped` everywhere.**
|
|
208
208
|
|
|
@@ -236,8 +236,8 @@ LSP v1 + v2 landed in v0.1.6 and ship in the `0.1.x` line. Queued
|
|
|
236
236
|
follow-ups (`textDocument/signatureHelp`, hash-key completion,
|
|
237
237
|
`textDocument/definition`, incremental `didChange` sync, Ractor
|
|
238
238
|
pool dispatch, codeAction / rename / semanticTokens / inlayHint)
|
|
239
|
-
are demand-driven;
|
|
240
|
-
|
|
239
|
+
are demand-driven; the current queue is the open
|
|
240
|
+
[`area:editor` issues](https://github.com/rigortype/rigor/issues?q=is%3Aissue+is%3Aopen+label%3Aarea%3Aeditor).
|
|
241
241
|
|
|
242
242
|
To request a queued feature or report an LSP issue, open a GitHub
|
|
243
243
|
issue with: the editor + version, the Rigor version
|
data/docs/manual/11-ci.md
CHANGED
|
@@ -246,6 +246,57 @@ updates:
|
|
|
246
246
|
extra files — but Dependabot does not see a version inside a `run:`
|
|
247
247
|
step, so updates to the pin are manual.
|
|
248
248
|
|
|
249
|
+
## Persisting the analysis cache across runs
|
|
250
|
+
|
|
251
|
+
By default each CI run starts cold and re-analyses everything. The
|
|
252
|
+
templates leave it that way on purpose — cold runs are simple,
|
|
253
|
+
deterministic, and fast enough for most projects. When the Rigor
|
|
254
|
+
job's runtime starts to matter (a large codebase, many plugins),
|
|
255
|
+
persist [the analysis cache](12-caching.md) between runs.
|
|
256
|
+
|
|
257
|
+
**Restore and save the cache directory** with your CI's cache
|
|
258
|
+
facility, keyed on anything that changes the analysis wholesale
|
|
259
|
+
(the Rigor version / lockfile). That is the whole setup: Rigor
|
|
260
|
+
detects CI and switches its cache freshness check from stat
|
|
261
|
+
metadata (which a fresh checkout regenerates, and which would
|
|
262
|
+
otherwise force parts of the restored cache to recompute every
|
|
263
|
+
run) to content hashes, which are identical across checkouts.
|
|
264
|
+
Details: [How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes).
|
|
265
|
+
|
|
266
|
+
GitHub Actions:
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
- uses: actions/cache@v4
|
|
270
|
+
with:
|
|
271
|
+
path: .rigor/cache
|
|
272
|
+
key: rigor-cache-${{ runner.os }}-${{ hashFiles('.github/rigor/Gemfile.lock') }}-${{ github.sha }}
|
|
273
|
+
restore-keys: rigor-cache-${{ runner.os }}-${{ hashFiles('.github/rigor/Gemfile.lock') }}-
|
|
274
|
+
- run: bundle exec rigor check
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
GitLab CI:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
rigor:
|
|
281
|
+
cache:
|
|
282
|
+
key: rigor-cache
|
|
283
|
+
paths:
|
|
284
|
+
- .rigor/cache
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Two refinements:
|
|
288
|
+
|
|
289
|
+
- A **self-hosted runner that reuses its workspace** keeps stable
|
|
290
|
+
stat metadata, so the faster stat check works there — opt back
|
|
291
|
+
into it with `cache.validation: stat`.
|
|
292
|
+
- On Rigor versions before the `auto` default, set
|
|
293
|
+
`RIGOR_STRICT_VALIDATION: "1"` on the job explicitly.
|
|
294
|
+
|
|
295
|
+
The cache is content-keyed and self-invalidating — a stale or
|
|
296
|
+
corrupt restore degrades to a recompute, never a wrong result. If
|
|
297
|
+
you would rather guarantee every run is independent, pass
|
|
298
|
+
`--no-cache` instead and skip this section.
|
|
299
|
+
|
|
249
300
|
## Container image
|
|
250
301
|
|
|
251
302
|
A standalone image is published to GHCR with Ruby 4.0 and Rigor
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -38,6 +38,49 @@ The cache is also schema-versioned: after a Rigor upgrade that
|
|
|
38
38
|
changes the cache format, the stale cache is purged on the
|
|
39
39
|
first writable run.
|
|
40
40
|
|
|
41
|
+
## How a file is checked for changes
|
|
42
|
+
|
|
43
|
+
To decide whether a cached entry is still valid, Rigor needs to
|
|
44
|
+
know which of your files changed since the entry was written. By
|
|
45
|
+
default it checks each file's **stat metadata** first — size,
|
|
46
|
+
nanosecond modification and change timestamps, and inode — and
|
|
47
|
+
only re-hashes a file's content when that metadata moved. On a
|
|
48
|
+
large project an unchanged run then reads *zero* content bytes to
|
|
49
|
+
validate the cache, instead of re-hashing every file.
|
|
50
|
+
|
|
51
|
+
The content hash stays the sole authority on whether a file
|
|
52
|
+
actually changed: the stat check only decides whether the hash
|
|
53
|
+
needs recomputing. A file that was merely `touch`ed (new
|
|
54
|
+
timestamp, identical content) is re-hashed once and correctly
|
|
55
|
+
found unchanged. Editing a file always moves its timestamps, so
|
|
56
|
+
an edit is never missed.
|
|
57
|
+
|
|
58
|
+
The stat check is skipped where it cannot work: the default
|
|
59
|
+
setting is `validation: auto`, which behaves as `stat` on your
|
|
60
|
+
machine and switches to hashing every file (`digest`) when a CI
|
|
61
|
+
environment is detected. CI is the common case of untrustworthy
|
|
62
|
+
stat metadata — a fresh checkout regenerates every timestamp and
|
|
63
|
+
inode, so a cache restored across CI runs (for example with
|
|
64
|
+
`actions/cache`) never passes the stat check, and parts of it
|
|
65
|
+
(plugin watch-glob entries, which are stat-signature only) would
|
|
66
|
+
be recomputed on every run. Content hashes are identical across
|
|
67
|
+
checkouts, so under `digest` the restored cache simply hits.
|
|
68
|
+
|
|
69
|
+
The setting can be forced either way:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
cache:
|
|
73
|
+
validation: digest # hash always — for any filesystem whose
|
|
74
|
+
# timestamps or inodes cannot be trusted
|
|
75
|
+
# validation: stat # stat always — e.g. a self-hosted CI
|
|
76
|
+
# runner that reuses its workspace, where
|
|
77
|
+
# stat metadata IS stable across runs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For a single run, `RIGOR_STRICT_VALIDATION=1` forces `digest`
|
|
81
|
+
and wins over the config key; `RIGOR_CI_DETECT=0` turns off the
|
|
82
|
+
CI detection that `auto` relies on.
|
|
83
|
+
|
|
41
84
|
## Controlling the cache
|
|
42
85
|
|
|
43
86
|
| Flag | Effect |
|
|
@@ -90,6 +133,28 @@ gate, `rigor check --verify-incremental`, asserts this on every
|
|
|
90
133
|
build: it runs the incremental analyzer and a full analysis and
|
|
91
134
|
fails if they disagree on a single diagnostic.
|
|
92
135
|
|
|
136
|
+
Rigor is also precise about *what* an edit changed. Re-checking a
|
|
137
|
+
file that many others depend on — a base class, a widely-used
|
|
138
|
+
concern, a shared utility — used to re-check every dependent.
|
|
139
|
+
Now, if your edit did not change the file's declaration shape
|
|
140
|
+
(the signatures of its methods, its superclass and includes) or
|
|
141
|
+
the types its methods return, the files that only depend on those
|
|
142
|
+
things are left untouched: a comment, a formatting change, or an
|
|
143
|
+
internal refactor that preserves every method's return type stops
|
|
144
|
+
at the edited file. An edit that *does* change a return type or a
|
|
145
|
+
signature still propagates to the dependents that consume it, so
|
|
146
|
+
the result stays identical to a full run.
|
|
147
|
+
|
|
148
|
+
Types contributed by plugins (a Sorbet signature, an
|
|
149
|
+
ActiveRecord column type, a dry-types alias) are validated too:
|
|
150
|
+
if a plugin's cross-file contributions change between runs, the
|
|
151
|
+
snapshot is dropped and the next run is a full one, so a plugin
|
|
152
|
+
edit can never leave a dependent stale.
|
|
153
|
+
|
|
154
|
+
An `--incremental` re-check honours `--workers=N` (and
|
|
155
|
+
`RIGOR_RACTOR_WORKERS` / `parallel.workers:`), analysing the
|
|
156
|
+
affected files in parallel just as a full run does.
|
|
157
|
+
|
|
93
158
|
The snapshot lives under the cache directory (`.rigor/cache`)
|
|
94
159
|
and is keyed by a fingerprint of your configuration, your locked
|
|
95
160
|
gems, your project's own `sig/` RBS, and the Rigor version.
|
|
@@ -27,7 +27,7 @@ You need:
|
|
|
27
27
|
- **`mise` wired into your shell** — add
|
|
28
28
|
`eval "$(mise activate zsh)"` (or the equivalent for your
|
|
29
29
|
shell) to your shell rc so that `rigor` reaches your `PATH`.
|
|
30
|
-
See [Installing Rigor § Putting rigor on your PATH](01-installation.md)
|
|
30
|
+
See [Installing Rigor § Putting rigor on your PATH](01-installation.md#putting-rigor-on-your-path)
|
|
31
31
|
for detail.
|
|
32
32
|
- **An existing Rails project** at a known path.
|
|
33
33
|
|
|
@@ -156,7 +156,9 @@ source-of-truth copy is
|
|
|
156
156
|
|
|
157
157
|
If your first `rigor check` reports more than ~100 diagnostics,
|
|
158
158
|
acknowledge mode is the natural starting point. You can tighten
|
|
159
|
-
it later.
|
|
159
|
+
it later. In `.rigor.dist.yml` (Step 3) the mode maps to a
|
|
160
|
+
`severity_profile:` — acknowledge → `lenient`, strict → `strict`
|
|
161
|
+
(omit the key for the default `balanced`).
|
|
160
162
|
|
|
161
163
|
### Step 3 — Write .rigor.dist.yml
|
|
162
164
|
|
|
@@ -48,6 +48,14 @@ power. `--threshold=RATIO` turns it into a CI gate (exit `1`
|
|
|
48
48
|
below the ratio) and `--format=json` carries the structured
|
|
49
49
|
fields.
|
|
50
50
|
|
|
51
|
+
On a large project, `--workers=N` fork-parallelizes the scan
|
|
52
|
+
(both the parameter-inference pre-pass and the per-file scan),
|
|
53
|
+
with output byte-identical to a sequential run. The worker count
|
|
54
|
+
resolves the same way `rigor check` does — `--workers` ›
|
|
55
|
+
`RIGOR_RACTOR_WORKERS` › [`parallel.workers:`](03-configuration.md)
|
|
56
|
+
› `0` (sequential default) — so a project already configured for
|
|
57
|
+
parallel `check` gets parallel coverage for free.
|
|
58
|
+
|
|
51
59
|
This is the everyday number. When you want the truth behind it,
|
|
52
60
|
move to Tier 2.
|
|
53
61
|
|
|
@@ -200,6 +208,19 @@ the "Add a type here" header, and the JSON carries the same totals as
|
|
|
200
208
|
`tractability_summary`. Start with the `add_rbs` holes — they are the
|
|
201
209
|
ones a type actually catches.
|
|
202
210
|
|
|
211
|
+
> **`external_gem_without_rbs` needs your gems installed where Rigor
|
|
212
|
+
> can read them.** To tell whether an unresolved constant belongs to
|
|
213
|
+
> a gem, Rigor reads that gem's source — so it must find the gem on
|
|
214
|
+
> disk. It looks in the project's Bundler install tree (a
|
|
215
|
+
> `vendor/bundle`, or a path set with `bundler.bundle_path:`; see
|
|
216
|
+
> [Configuration](03-configuration.md)). A project whose gems live in
|
|
217
|
+
> the active Ruby's default gem home — the common `rbenv` / `mise`
|
|
218
|
+
> case with no `--path` set — is invisible to the isolated analyzer by
|
|
219
|
+
> design ([ADR-27](https://github.com/rigortype/rigor/blob/master/docs/adr/27-tool-distribution-model.md)):
|
|
220
|
+
> point Rigor at it with `bundler.bundle_path:`. Until you do, these
|
|
221
|
+
> holes keep the generic `engine_gap` cause instead of `add_rbs` —
|
|
222
|
+
> the label is missing, never wrong.
|
|
223
|
+
|
|
203
224
|
Provenance is precision-additive only: it never changes a type, fires
|
|
204
225
|
no diagnostic, and never affects severity or the protection ratio.
|
|
205
226
|
|
|
@@ -39,7 +39,7 @@ no-op rather than erroring.
|
|
|
39
39
|
| `plugin.actionpack.render-target` | info | an explicit `render :symbol` / `"string"` / `partial:` resolved to a view template |
|
|
40
40
|
| `plugin.actionpack.missing-template` | error | an explicit `render` resolved to a view path that doesn't exist under any `view_search_paths` |
|
|
41
41
|
| `plugin.actionpack.permit-call` | info | a `params.require(:m).permit(:key, …)` chain resolved to a known model; keys matched against its columns |
|
|
42
|
-
| `plugin.actionpack.unknown-permit-key` | error | a literal `permit(:key)`
|
|
42
|
+
| `plugin.actionpack.unknown-permit-key` | error | a literal `permit(:key)` is a near-miss (edit distance ≤ 2) of a real column but not one — a likely typo (with a did-you-mean). A key nothing like any column (a legitimate virtual attribute) does not fire |
|
|
43
43
|
|
|
44
44
|
Filter and render resolution honours nested-module controller
|
|
45
45
|
qualification (`module Admin; class WidgetsController` resolves
|
|
@@ -30,9 +30,9 @@ errors_demo.rb:25:1: error: `User.find` expects at least 1 argument, got 0 [plug
|
|
|
30
30
|
| Recognised `Model.find` / `Model.find_by` / `Model.where` call | `:info` | `plugin.activerecord.model-call` |
|
|
31
31
|
| `Model.find_by(unknown: ...)` / `Model.where(unknown: ...)` | `:error` | `plugin.activerecord.unknown-column` |
|
|
32
32
|
| `Model.find` with 0 args | `:error` | `plugin.activerecord.wrong-arity` |
|
|
33
|
-
| `db/schema.rb`
|
|
33
|
+
| No schema source (`db/schema.rb` or `db/structure.sql`) readable | `:warning` | `plugin.activerecord.load-error` |
|
|
34
34
|
|
|
35
|
-
Did-you-mean suggestions use
|
|
35
|
+
Did-you-mean suggestions use `DidYouMean` fuzzy matching against
|
|
36
36
|
the resolved table's column names.
|
|
37
37
|
|
|
38
38
|
## Configuration
|
|
@@ -42,13 +42,16 @@ plugins:
|
|
|
42
42
|
- gem: rigor-activerecord
|
|
43
43
|
config:
|
|
44
44
|
schema_file: "db/schema.rb" # default
|
|
45
|
+
structure_sql_file: "db/structure.sql" # default (fallback when schema_file is absent)
|
|
45
46
|
model_search_paths: ["app/models"] # default
|
|
46
47
|
model_base_classes: ["ApplicationRecord", "ActiveRecord::Base"] # default
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
All
|
|
50
|
+
All keys are optional. Tweak them when:
|
|
50
51
|
|
|
51
52
|
- the schema lives elsewhere (`schema_file: "shared/db/schema.rb"`);
|
|
53
|
+
- the project uses `schema_format = :sql` and its dump is not at the
|
|
54
|
+
default path (`structure_sql_file: "db/structure.sql"`);
|
|
52
55
|
- models are in a non-standard directory
|
|
53
56
|
(`model_search_paths: ["domain/models", "engines/billing/app/models"]`);
|
|
54
57
|
- the base class is custom
|
|
@@ -78,8 +81,12 @@ never surfaces a false `call.undefined-method`.
|
|
|
78
81
|
`User < ApplicationRecord` is not discovered. Either add `User`
|
|
79
82
|
to `model_base_classes`, or list every concrete model
|
|
80
83
|
explicitly.
|
|
81
|
-
-
|
|
82
|
-
|
|
84
|
+
- **PostgreSQL `db/structure.sql` fallback.** When `db/schema.rb` is
|
|
85
|
+
absent, the plugin parses `db/structure.sql` (the `schema_format =
|
|
86
|
+
:sql` dump) for the same column/type table. It reads PostgreSQL DDL
|
|
87
|
+
only; a column whose SQL type has no Ruby mapping (a custom enum,
|
|
88
|
+
`tsvector`, `ltree`) degrades to `Object` (never dropped), and
|
|
89
|
+
non-`public`-schema partition tables are skipped.
|
|
83
90
|
- **Column reads, not setters.** The plugin types instance-side
|
|
84
91
|
column *reads* (`user.name`, `user.admin?`) and singular
|
|
85
92
|
associations, but not the `name=` setter or the dirty-tracking
|
|
@@ -59,6 +59,8 @@ plugins:
|
|
|
59
59
|
routes_file: "config/routes.rb" # default
|
|
60
60
|
helper_paths: ["app"] # default; dirs scanned for
|
|
61
61
|
# project-defined *_path / *_url methods
|
|
62
|
+
grape_api_paths: ["lib/api", "app/api"]
|
|
63
|
+
# default; dirs scanned for Grape API classes
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
`helper_paths` lets the plugin also register URL builders you
|
|
@@ -66,6 +68,15 @@ define yourself (e.g. a private `def callback_url` under
|
|
|
66
68
|
`app/controllers` or `app/lib`), so calls to them are not flagged
|
|
67
69
|
as unknown helpers.
|
|
68
70
|
|
|
71
|
+
`grape_api_paths` is where the plugin looks for Grape API classes.
|
|
72
|
+
If you mount one, the `grape-path-helpers` gem generates helpers
|
|
73
|
+
named after each route's path (`api_v4_groups_badges_path`) from
|
|
74
|
+
grape's *runtime* route table, which no static parser can
|
|
75
|
+
enumerate. The plugin reads your API's `prefix` and `version`
|
|
76
|
+
declarations instead and treats the namespace they open as
|
|
77
|
+
unknowable-but-valid, so those calls are never flagged. The `_url`
|
|
78
|
+
form still is: `grape-path-helpers` defines only `_path` helpers.
|
|
79
|
+
|
|
69
80
|
## What it provides
|
|
70
81
|
|
|
71
82
|
The parsed helper table is published as the `:helper_table`
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "../../source/node_children"
|
|
6
|
+
require_relative "inferred_param_guard"
|
|
7
|
+
|
|
5
8
|
module Rigor
|
|
6
9
|
module Analysis
|
|
7
10
|
module CheckRules
|
|
@@ -81,7 +84,7 @@ module Rigor
|
|
|
81
84
|
visit(node) if conditional_node?(node) && !in_loop_or_block
|
|
82
85
|
|
|
83
86
|
child_in_loop_or_block = in_loop_or_block || enters_loop_or_block?(node)
|
|
84
|
-
node.
|
|
87
|
+
node.rigor_each_child { |child| walk(child, in_loop_or_block: child_in_loop_or_block) }
|
|
85
88
|
end
|
|
86
89
|
|
|
87
90
|
def conditional_node?(node)
|
|
@@ -100,6 +103,11 @@ module Rigor
|
|
|
100
103
|
scope = @scope_index[node]
|
|
101
104
|
return if scope.nil?
|
|
102
105
|
|
|
106
|
+
# ADR-67 WD6b — a predicate rooted at an inferred parameter (`opts.key?(:x)`) folds to a constant only
|
|
107
|
+
# because the seed's lower-bound type pinned it; a wider real caller would not. Declining preserves
|
|
108
|
+
# the precision-additive contract.
|
|
109
|
+
return if InferredParamGuard.rooted?(predicate, scope)
|
|
110
|
+
|
|
103
111
|
predicate_type = scope.type_of(predicate)
|
|
104
112
|
return unless predicate_type.is_a?(Type::Constant)
|
|
105
113
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "../../source/node_children"
|
|
6
|
+
|
|
5
7
|
module Rigor
|
|
6
8
|
module Analysis
|
|
7
9
|
module CheckRules
|
|
@@ -65,7 +67,7 @@ module Rigor
|
|
|
65
67
|
return unless node.is_a?(Prism::Node)
|
|
66
68
|
|
|
67
69
|
collect_def_assignments(node) if node.is_a?(Prism::DefNode)
|
|
68
|
-
node.
|
|
70
|
+
node.rigor_each_child { |child| walk_for_def_nodes(child) }
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
def collect_def_assignments(def_node)
|
|
@@ -92,7 +94,7 @@ module Rigor
|
|
|
92
94
|
# / similar shapes don't trip the rule.
|
|
93
95
|
accumulator << node.name if reading_assignment?(node)
|
|
94
96
|
|
|
95
|
-
node.
|
|
97
|
+
node.rigor_each_child { |child| gather_read_names(child, accumulator) }
|
|
96
98
|
accumulator
|
|
97
99
|
end
|
|
98
100
|
|
|
@@ -110,7 +112,7 @@ module Rigor
|
|
|
110
112
|
# outer walker visits them separately.
|
|
111
113
|
return accumulator if node.is_a?(Prism::DefNode) && !accumulator.last.equal?(node)
|
|
112
114
|
|
|
113
|
-
node.
|
|
115
|
+
node.rigor_each_child { |child| gather_write_nodes(child, accumulator) }
|
|
114
116
|
accumulator
|
|
115
117
|
end
|
|
116
118
|
|