rigortype 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +16 -0
- data/docs/manual/02-cli-reference.md +4 -3
- data/docs/manual/03-configuration.md +16 -5
- data/docs/manual/04-diagnostics.md +44 -2
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +32 -3
- data/docs/manual/18-removing-dead-code.md +11 -1
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/rule_ids.rb +57 -0
- data/lib/rigor/analysis/check_rules.rb +46 -36
- data/lib/rigor/analysis/incremental.rb +12 -0
- data/lib/rigor/analysis/incremental_session.rb +121 -21
- data/lib/rigor/analysis/reachability/graph.rb +23 -3
- data/lib/rigor/analysis/reachability/scan.rb +51 -9
- data/lib/rigor/analysis/rule_catalog.rb +6 -1
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +5 -3
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
- data/lib/rigor/analysis/runner/pool_coordinator.rb +110 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
- data/lib/rigor/analysis/runner.rb +35 -9
- data/lib/rigor/analysis/severity_stamp.rb +34 -1
- data/lib/rigor/analysis/worker_session.rb +57 -1
- data/lib/rigor/cache/descriptor.rb +40 -6
- data/lib/rigor/cache/incremental_snapshot.rb +20 -4
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/config_audit.rb +111 -15
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/environment/rbs_loader.rb +46 -10
- data/lib/rigor/inference/block_call_timing.rb +5 -0
- data/lib/rigor/inference/captured_locals.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +16 -7
- data/lib/rigor/inference/expression_typer.rb +59 -4
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/module_function_state.rb +80 -13
- data/lib/rigor/inference/narrowing.rb +184 -68
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +414 -1
- data/lib/rigor/inference/scope_indexer.rb +439 -70
- data/lib/rigor/inference/self_call_ivar_writes.rb +341 -0
- data/lib/rigor/inference/statement_evaluator.rb +84 -29
- data/lib/rigor/lens/anchor.rb +3 -3
- data/lib/rigor/lens/contract_reader.rb +15 -0
- data/lib/rigor/lens/document.rb +2 -2
- data/lib/rigor/lens/file_reader.rb +442 -73
- data/lib/rigor/lens/members.rb +65 -15
- data/lib/rigor/lens/run.rb +44 -2
- data/lib/rigor/lens.rb +2 -1
- data/lib/rigor/mcp/server.rb +61 -20
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/project_environment.rb +6 -3
- data/lib/rigor/scope.rb +11 -6
- data/lib/rigor/sig_gen/layout_index.rb +36 -0
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/writer.rb +47 -34
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +28 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a09068aa2810a8d01758e3ce78f52dc6020f0d197bb83a561684b96969e5eddf
|
|
4
|
+
data.tar.gz: 0105753f063477bea3a85ba3dea3fe660fbe58d846d9388867eafd3b6fed1055
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb111fc503dbb60810cbcdf4bfb0a717f694f771fe16bf41007ef1b9eb2728014aa19d112183e79652e4a1ae03c09d6e227ad13ebc063a9804b7a6ba916ae71b
|
|
7
|
+
data.tar.gz: 4c3d925ef588248fbd84529262632bcfa5e081eedd340c164f0de89b3a0f83abd5cdaad37be922a90571068966ec204f77e075993994db8b106abbaa8d135cd8
|
data/README.md
CHANGED
|
@@ -232,7 +232,7 @@ rigor docs --list # list every bundled page
|
|
|
232
232
|
|
|
233
233
|
## Status
|
|
234
234
|
|
|
235
|
-
Current release: **`v0.4.
|
|
235
|
+
Current release: **`v0.4.4`** (2026-10-16) — on the
|
|
236
236
|
`0.4.x` line, continuing the evaluation era opened by `v0.2.0`, the first
|
|
237
237
|
publicly-announced (general / evaluation) release. The line publishes
|
|
238
238
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Rigor core overlay — supplemental core signatures.
|
|
2
|
+
#
|
|
3
|
+
# --- OpenStruct#method_missing (issue #1456) ---
|
|
4
|
+
#
|
|
5
|
+
# rbs 4.2 ships no `ostruct` signatures (since Ruby 4.0 `ostruct` is a bundled
|
|
6
|
+
# gem, not a default one), and the gem ships no `sig/`. The only `OpenStruct`
|
|
7
|
+
# declaration in the environment is json's add-on reopening in
|
|
8
|
+
# `stdlib/json/0/json.rbs` (`json_create` / `as_json` / `to_json`), so the
|
|
9
|
+
# class read as fully declared and `OpenStruct.new(name: "x").name` reported
|
|
10
|
+
# `call.undefined-method`.
|
|
11
|
+
#
|
|
12
|
+
# Upstream: ostruct 0.6.3 `lib/ostruct.rb` defines each field as a
|
|
13
|
+
# per-instance singleton method (`new_ostruct_member!` through
|
|
14
|
+
# `define_singleton_method!`) and answers the rest through
|
|
15
|
+
# `private def method_missing(mid, *args)`, which this file declares. It
|
|
16
|
+
# defines no `respond_to_missing?` of its own (Kernel's answers, and the
|
|
17
|
+
# fields are real singleton methods), so none is declared here.
|
|
18
|
+
#
|
|
19
|
+
# The per-field accessors cannot be declared on the class at all, so
|
|
20
|
+
# `Rigor::Analysis::CheckRules::CORE_OVERLAY_OPEN_RECEIVERS` names
|
|
21
|
+
# `OpenStruct` as an open receiver: an RBS `method_missing` alone does not
|
|
22
|
+
# stop the undefined-method rule. That list is what quiets `rigor check`;
|
|
23
|
+
# this file supplies the declaration the maintainer decision asked for.
|
|
24
|
+
#
|
|
25
|
+
# The declaration sits on a module the class includes, as `process.rbs`
|
|
26
|
+
# does, so a project `sig/` that declares `OpenStruct#method_missing` itself
|
|
27
|
+
# overrides it instead of raising `DuplicatedMethodDefinitionError`.
|
|
28
|
+
#
|
|
29
|
+
# Maintainer decision on #1456 (2026-10-16, option c): this narrow overlay.
|
|
30
|
+
# The general rule for a class declared only by another library's partial
|
|
31
|
+
# reopening is #1978.
|
|
32
|
+
module RigorCoreOverlay
|
|
33
|
+
module OpenStructMissing
|
|
34
|
+
private def method_missing: (Symbol mid, *untyped args) -> untyped
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class OpenStruct
|
|
39
|
+
include RigorCoreOverlay::OpenStructMissing
|
|
40
|
+
end
|
data/docs/handbook/11-sig-gen.md
CHANGED
|
@@ -751,6 +751,22 @@ by side without coordination.
|
|
|
751
751
|
every other byte of the file verbatim.
|
|
752
752
|
- **Will** append a new `class Foo … end` block when the
|
|
753
753
|
target file does not declare the class yet.
|
|
754
|
+
- **Will** treat a method as already declared when a file
|
|
755
|
+
under the signature tree declares it on the same class
|
|
756
|
+
with a `def` or an `attr_*` of the same kind (instance or
|
|
757
|
+
singleton), in any opening of the class in that file. A
|
|
758
|
+
`def self?.` declares both kinds, and a `def self?.`
|
|
759
|
+
proposal counts as declared when either kind is. The
|
|
760
|
+
method is updated (`--overwrite`) or skipped in the file
|
|
761
|
+
that declares it instead of being added to the class's
|
|
762
|
+
first file. A replacement lands only on a `def` of the
|
|
763
|
+
proposal's own kind in the first opening of the class:
|
|
764
|
+
a `def self?.` proposal over a lone `def self.f` or
|
|
765
|
+
`def f`, a plain proposal over a `def self?.f`, or a
|
|
766
|
+
method declared in a later opening is skipped. A
|
|
767
|
+
`def f: ... | ...` overload is never replaced and never
|
|
768
|
+
counts as the file that declares `f`. An `alias` in
|
|
769
|
+
`sig/` is not yet recognised as declaring its name.
|
|
754
770
|
- **Will not** touch files outside the configured signature
|
|
755
771
|
tree.
|
|
756
772
|
- **Will not** replace an existing method declaration
|
|
@@ -913,10 +913,11 @@ rigor mcp [--transport=stdio] [--config=PATH]
|
|
|
913
913
|
```
|
|
914
914
|
|
|
915
915
|
`stdio` is the only transport. The server is a pure-Ruby
|
|
916
|
-
JSON-RPC 2.0 implementation exposing
|
|
917
|
-
`rigor_check`, `rigor_type_of`, `rigor_triage`,
|
|
916
|
+
JSON-RPC 2.0 implementation exposing eight read-only tools:
|
|
917
|
+
`rigor_check`, `rigor_type_of`, `rigor_triage`, `rigor_lens`,
|
|
918
918
|
`rigor_annotate`, `rigor_sig_gen`, `rigor_explain`,
|
|
919
|
-
`rigor_coverage`.
|
|
919
|
+
`rigor_coverage`. `rigor_lens` writes only the incremental
|
|
920
|
+
cache `rigor check --incremental` writes.
|
|
920
921
|
|
|
921
922
|
## `rigor lsp` vs `rigor mcp`
|
|
922
923
|
|
|
@@ -78,7 +78,7 @@ cache:
|
|
|
78
78
|
| --- | --- | --- | --- |
|
|
79
79
|
| `libraries` | Array | `[]` | Standard-library / gem names whose bundled RBS to load. |
|
|
80
80
|
| `signature_paths` | Array | `nil` | Extra directories of `.rbs` files. Relative entries resolve against the config file's directory. |
|
|
81
|
-
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. The walker does not follow `require` / `require_relative`, so list the files in load order: a method declared in two files takes the last-listed one, and a file that requires a later-listed one is still read in list order. |
|
|
81
|
+
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. The walker does not follow `require` / `require_relative`, so list the files in load order: a method declared in two files takes the last-listed one, and a file that requires a later-listed one is still read in list order. `type-of`, `type-scan`, `trace`, `annotate` and `sig-gen` read the patches too, so they type a patched method as `rigor check` does. |
|
|
82
82
|
| `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
|
|
83
83
|
|
|
84
84
|
### Signature generation
|
|
@@ -103,6 +103,8 @@ rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from
|
|
|
103
103
|
rigor: signature_paths: "/path/to/sig" matched 0 signature files
|
|
104
104
|
rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
|
|
105
105
|
rigor: disable: "call.undefined-methdo" is not a recognized rule id; the suppression has no effect
|
|
106
|
+
rigor: disable: "plugin_loader.load-error" names a run-level row, which `disable:` does not reach; use `severity_overrides:` or a baseline
|
|
107
|
+
rigor: disable: "plugin.shoulda-matchers.unknown-column" names no plugin `plugins:` configures; the suppression has no effect
|
|
106
108
|
rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the override has no effect
|
|
107
109
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
108
110
|
```
|
|
@@ -125,10 +127,19 @@ the JSON schema as you type rather than at check time.
|
|
|
125
127
|
These are warnings, not errors — partial or optional bundles and
|
|
126
128
|
forward-looking config are valid setups. The audit only fires on explicit,
|
|
127
129
|
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
128
|
-
auto-detected bundle) is never warned about, and a
|
|
129
|
-
`severity_overrides:`
|
|
130
|
+
auto-detected bundle) is never warned about, and a
|
|
131
|
+
`severity_overrides:` key under a non-built-in family (`rspec.…`,
|
|
130
132
|
`rbs_extended.…`) is left alone, since its rule id cannot be enumerated
|
|
131
|
-
statically and may resolve at run time.
|
|
133
|
+
statically and may resolve at run time. `disable:` is checked more
|
|
134
|
+
closely, because what it can reach is known: a plugin's rule counts
|
|
135
|
+
only in its qualified `plugin.<id>.<rule>` form, so a bare plugin rule
|
|
136
|
+
(`rspec.let-binding`) is warned about. A qualified token is checked
|
|
137
|
+
against `plugins:` only when every entry's plugin ID is known without
|
|
138
|
+
loading it — an explicit `id:`, or a bundled plugin's gem name minus
|
|
139
|
+
`rigor-`; one third-party gem listed without `id:` turns that check
|
|
140
|
+
off. A token under `rbs_extended.…`, `rbs.…` or `dynamic.…` is left
|
|
141
|
+
alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
142
|
+
`dynamic.dependency-source.…`), which are flagged. The same findings appear in the
|
|
132
143
|
`--format=json` payload under `config_warnings` (each tagged with a
|
|
133
144
|
`kind`), so CI can assert on them.
|
|
134
145
|
|
|
@@ -136,7 +147,7 @@ statically and may resolve at run time. The same findings appear in the
|
|
|
136
147
|
|
|
137
148
|
| Key | Type | Default | Meaning |
|
|
138
149
|
| --- | --- | --- | --- |
|
|
139
|
-
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide. |
|
|
150
|
+
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide; `all` suppresses every built-in check rule. A plugin's diagnostics are named by qualified ID (`plugin.<id>.<rule>`, or `plugin.<id>` for the whole plugin); a bare ID or `all` does not reach them. The run-level `.rigor.yml` rows (`plugin_loader.*`, `dynamic.dependency-source.*`, `rbs.coverage.*`, `plugin_trust.*`, `pool-degraded`) are not reached by any spelling; use `severity_overrides:` for those. See [Diagnostics](04-diagnostics.md#suppressing-a-diagnostic). |
|
|
140
151
|
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
141
152
|
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
142
153
|
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
@@ -253,7 +253,12 @@ config.merge(extra) # rigor:disable call.undefined-method
|
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
It accepts qualified IDs, family wildcards (`call`), a
|
|
256
|
-
comma- or space-separated list, or `all`.
|
|
256
|
+
comma- or space-separated list, or `all`. A plugin's diagnostic
|
|
257
|
+
is reached only by its qualified ID as printed in brackets
|
|
258
|
+
(`plugin.shoulda-matchers.unknown-column`) or by the plugin's
|
|
259
|
+
whole family (`plugin.shoulda-matchers`); its bare rule
|
|
260
|
+
(`unknown-column`) and `all` leave it alone, because several
|
|
261
|
+
plugins share bare rules. The comment must sit
|
|
257
262
|
on the line the diagnostic points at; there is no
|
|
258
263
|
`disable-block` form, so an expression spread over several
|
|
259
264
|
lines needs the comment on each line that fires.
|
|
@@ -283,7 +288,8 @@ suppressible like any other rule.
|
|
|
283
288
|
|
|
284
289
|
**In-source, whole file.** `# rigor:disable-file <rules>`
|
|
285
290
|
anywhere in a file suppresses those rules for every line;
|
|
286
|
-
`# rigor:disable-file all` silences
|
|
291
|
+
`# rigor:disable-file all` silences every built-in rule in the file
|
|
292
|
+
(a plugin's diagnostics need their qualified ID, as above). Convention is to
|
|
287
293
|
put it near the top — typically on a generated file, a fixture,
|
|
288
294
|
or a vendored snippet — but every comment in the file is
|
|
289
295
|
scanned, so any placement works.
|
|
@@ -300,6 +306,42 @@ disable:
|
|
|
300
306
|
- flow.dead-assignment
|
|
301
307
|
```
|
|
302
308
|
|
|
309
|
+
`disable: [all]` turns off every built-in check rule, including
|
|
310
|
+
`effect.annotations-unchecked`, which is reported at a signature
|
|
311
|
+
file.
|
|
312
|
+
|
|
313
|
+
A plugin's own diagnostics are turned off by their qualified ID —
|
|
314
|
+
`plugin.<id>.<rule>` for one rule, `plugin.<id>` for every rule
|
|
315
|
+
of that plugin:
|
|
316
|
+
|
|
317
|
+
```yaml
|
|
318
|
+
disable:
|
|
319
|
+
- plugin.shoulda-matchers.unknown-column
|
|
320
|
+
- plugin.rails-i18n
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
A bare rule (`unknown-column`, `load-error`) and `all` do not
|
|
324
|
+
reach them, so one plugin's suppression never hides another
|
|
325
|
+
plugin's finding of the same name.
|
|
326
|
+
|
|
327
|
+
The list does not reach the run-level rows reported at
|
|
328
|
+
`.rigor.yml` — a plugin that failed to load or raised in
|
|
329
|
+
`prepare` (`plugin_loader.*`), a `dependencies.source_inference:`
|
|
330
|
+
gem that did not resolve (`dynamic.dependency-source.*`), the
|
|
331
|
+
`rbs.coverage.*` notices, `plugin_trust.*`, `pool-degraded` —
|
|
332
|
+
under any spelling, `plugin_loader.load-error` included. Turn
|
|
333
|
+
those off with [`severity_overrides:`](#severity-profiles) set
|
|
334
|
+
to `off`, or record them in a [baseline](06-baseline.md).
|
|
335
|
+
`severity_overrides:` matches the row's rule ID without the
|
|
336
|
+
source prefix, so the plugin loader's rows are `load-error` and
|
|
337
|
+
`runtime-error` there — and that key also re-stamps any plugin
|
|
338
|
+
diagnostic whose bare rule is the same. `rigor check` warns when a
|
|
339
|
+
`disable:` entry names one of these rows, names no rule at all,
|
|
340
|
+
or names a plugin `plugins:` does not configure (see
|
|
341
|
+
[Config validation warnings](03-configuration.md#config-validation-warnings)).
|
|
342
|
+
A path or parse error, which has no rule ID, is always
|
|
343
|
+
reported.
|
|
344
|
+
|
|
303
345
|
For a *known backlog* you want to keep visible but not fail
|
|
304
346
|
on, prefer a [baseline](06-baseline.md) over a blanket
|
|
305
347
|
`disable:` — `disable:` also hides any new occurrences.
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -95,7 +95,7 @@ with the `plugins_isolation:` configuration key or the
|
|
|
95
95
|
| --- | --- |
|
|
96
96
|
| `process` (default) | Run the call in a forked, crash-contained worker, so the target library's monkey-patches and any crash never contaminate Rigor. Falls back to `none` where `fork` is unavailable (Windows / JRuby). |
|
|
97
97
|
| `none` | Load the library into Rigor's own process and call it directly. |
|
|
98
|
-
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. It also needs a Ruby that fixes [Ruby Bug #22260](https://bugs.ruby-lang.org/issues/22260), which no release up to 4.0.7 does. The launcher checks for the fix first, and without it prints a warning and uses the configured strategy instead. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
|
|
98
|
+
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. It also needs a Ruby that fixes [Ruby Bug #22260](https://bugs.ruby-lang.org/issues/22260), which no release up to 4.0.7 does. The launcher checks for the fix first, and without it prints a warning and uses the configured strategy instead. The same check runs when `RUBY_BOX=1` is already in your environment: without the fix, the launcher prints a notice and restarts itself without `RUBY_BOX`, so the run completes but plugins are not boxed. The check result is cached per Ruby build under `$XDG_CACHE_HOME/rigor` (or `~/.cache/rigor`) and never expires, so if you rebuild a Ruby in place with the fix, delete `$XDG_CACHE_HOME/rigor/ruby-box-probe` (or `~/.cache/rigor/ruby-box-probe`). A check that times out is not cached, so it costs up to ten seconds on every run. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
|
|
99
99
|
|
|
100
100
|
The environment variable wins over `plugins_isolation:`, so you can
|
|
101
101
|
override a project's committed choice for one invocation. The legacy
|
|
@@ -33,6 +33,7 @@ There is no conflict in running both simultaneously.
|
|
|
33
33
|
| `rigor_check` | `rigor check --format json` | JSON diagnostic report |
|
|
34
34
|
| `rigor_type_of` | `rigor type-of --format json` | JSON type at `FILE:LINE:COL` |
|
|
35
35
|
| `rigor_triage` | `rigor triage --format json` | JSON distribution + class/method selectors + hotspots + hints |
|
|
36
|
+
| `rigor_lens` | `rigor lens --format json` | JSON `lens/v0` declaration map (experimental) |
|
|
36
37
|
| `rigor_annotate` | `rigor annotate --no-color` | Annotated Ruby source |
|
|
37
38
|
| `rigor_sig_gen` | `rigor sig-gen --print --format json` | JSON RBS skeleton candidates |
|
|
38
39
|
| `rigor_explain` | `rigor explain --format json` | JSON rule catalog entries |
|
|
@@ -320,6 +321,34 @@ by `count` for the highest-leverage class/method, or filter by
|
|
|
320
321
|
|
|
321
322
|
---
|
|
322
323
|
|
|
324
|
+
### rigor_lens
|
|
325
|
+
|
|
326
|
+
Map the classes, modules, methods and attributes the given files declare,
|
|
327
|
+
with the type `rigor check` gives each one and where it came from. The
|
|
328
|
+
document is the experimental `lens/v0` format
|
|
329
|
+
([specification](../internal-spec/lens.md)); see
|
|
330
|
+
[Inspecting inferred types](05-inspecting-types.md) for the command.
|
|
331
|
+
|
|
332
|
+
**Input:**
|
|
333
|
+
|
|
334
|
+
| Argument | Type | Required |
|
|
335
|
+
|---|---|---|
|
|
336
|
+
| `files` | `string[]` | yes |
|
|
337
|
+
| `config` | `string` | no |
|
|
338
|
+
|
|
339
|
+
**Returns:** JSON. Each file carries its `status`, `sites`, `omissions`
|
|
340
|
+
and `diagnostics`; `members` holds the signatures.
|
|
341
|
+
|
|
342
|
+
**Typical agent use:** learn what a file or class offers, and the
|
|
343
|
+
contracts its methods already have, before editing it or writing a type.
|
|
344
|
+
Pass every file in one call: each call runs one analysis, and the first
|
|
345
|
+
call on a project costs a full `rigor check`. Paths, site keys and the cache are
|
|
346
|
+
relative to the directory the server was started in, so start it from the
|
|
347
|
+
project root; `--config` fixes which files belong to the project, not how
|
|
348
|
+
paths are written.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
323
352
|
### rigor_annotate
|
|
324
353
|
|
|
325
354
|
Return the given Ruby source file with each line's last-expression
|
|
@@ -482,7 +511,7 @@ You should get a JSON response. If you get nothing or a shell error,
|
|
|
482
511
|
`rigor` is not on your `PATH` — fix the install path first (see
|
|
483
512
|
[Installing Rigor](01-installation.md)).
|
|
484
513
|
|
|
485
|
-
**`rigor_check` / `rigor_triage` return an empty diagnostics array on a project that has errors
|
|
514
|
+
**`rigor_check` / `rigor_triage` return an empty diagnostics array on a project that has errors, or `rigor_lens` reports every file `outside_project`.**
|
|
486
515
|
|
|
487
516
|
The tool uses `Configuration.discover` from the working directory the
|
|
488
517
|
MCP server was started in. If the client launches `rigor mcp` from a
|
|
@@ -517,8 +546,8 @@ warmed by a prior `rigor check` run reduces it significantly.
|
|
|
517
546
|
|
|
518
547
|
## Status and roadmap
|
|
519
548
|
|
|
520
|
-
`rigor mcp` shipped in v0.1.10 with
|
|
521
|
-
transport. Queued follow-ups are demand-driven:
|
|
549
|
+
`rigor mcp` shipped in v0.1.10 with seven tools and stdio
|
|
550
|
+
transport; `rigor_lens` joined them in v0.4.4. Queued follow-ups are demand-driven:
|
|
522
551
|
|
|
523
552
|
- **HTTP transport** (slice 2) — `--transport=http` for CI / remote
|
|
524
553
|
agent use; a minimal Rack endpoint.
|
|
@@ -265,7 +265,17 @@ under them are. `const_get` looks the name up from its receiver, so
|
|
|
265
265
|
and mixins and at the top level as well, unless the call passes `false`
|
|
266
266
|
as its second argument. A literal `const_get("Handler")` is a reference
|
|
267
267
|
looked up in the same order, so it names `Migration::Handler` when
|
|
268
|
-
that exists rather than a top-level `Handler`.
|
|
268
|
+
that exists rather than a top-level `Handler`. When the receiver is
|
|
269
|
+
implicit, `self` or `self.class` inside a method (or a block in a class
|
|
270
|
+
body, such as a lambda or `define_method`), the call may run on a
|
|
271
|
+
subclass or on an includer, so a `Handler` declared in a descendant is
|
|
272
|
+
kept reachable too. Descendants are the classes written `class D < Base`
|
|
273
|
+
and the declarations whose body has a plain `include`, `prepend` or
|
|
274
|
+
`extend` of the module; `K.include(Mix)`, `K.send(:include, Mix)`,
|
|
275
|
+
`K.extend(Ext)` and `K = Class.new(Base)` are not followed yet. A call
|
|
276
|
+
written directly in a class body is not widened. Inside
|
|
277
|
+
`Other.instance_eval { }` (or `class_eval`, `module_eval` and their
|
|
278
|
+
`_exec` forms) the receiver is `Other`. A name built into a
|
|
269
279
|
local variable first (`name = "V#{version}"; const_get(name)`) is read
|
|
270
280
|
the same way. A
|
|
271
281
|
class name appearing as a string in a `.yml`, `.json` or template file
|
|
@@ -127,8 +127,8 @@ routed. Helper-name recognition for all three is unaffected.
|
|
|
127
127
|
metaprogramming the parser can't unfold (routes built in a loop
|
|
128
128
|
over runtime data, helpers injected by an engine the parser
|
|
129
129
|
doesn't model) may not register, which can surface a false
|
|
130
|
-
`unknown-helper`. Record those in a baseline, or
|
|
131
|
-
`# rigor:disable` the line.
|
|
130
|
+
`unknown-helper`. Record those in a baseline, or add
|
|
131
|
+
`# rigor:disable plugin.rails-routes.unknown-helper` to the line.
|
|
132
132
|
- **The "routes file did not load" warning is run-scoped.** It is a
|
|
133
133
|
fact about your configuration, not about any one source file, so
|
|
134
134
|
it is reported once per run on `.rigor.yml` rather than on the
|
|
@@ -48,7 +48,10 @@ expected kind: `belong_to` / `have_one` (singular), `have_many` /
|
|
|
48
48
|
|
|
49
49
|
Suppress with the qualified rule, e.g.
|
|
50
50
|
`# rigor:disable plugin.shoulda-matchers.unknown-column`, or silence
|
|
51
|
-
the whole family with `# rigor:disable plugin.shoulda-matchers`.
|
|
51
|
+
the whole family with `# rigor:disable plugin.shoulda-matchers`. The
|
|
52
|
+
same spellings work project-wide in `.rigor.yml`'s `disable:`. The
|
|
53
|
+
bare rule (`# rigor:disable unknown-column`) does not reach these
|
|
54
|
+
diagnostics.
|
|
52
55
|
|
|
53
56
|
## No configuration
|
|
54
57
|
|
data/exe/rigor
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
# (`RUBY_BOX=1`, not toggleable at runtime). When a project selects that
|
|
7
7
|
# strategy (`RIGOR_PLUGIN_ISOLATION=ruby_box`, or the legacy `RIGOR_BOX`
|
|
8
8
|
# alias) we re-exec the same command with the flag set before anything
|
|
9
|
-
# else loads. The
|
|
10
|
-
# inherited environment (RUBYOPT / BUNDLE_*) preserves the Bundler
|
|
9
|
+
# else loads. The inherited environment (RUBYOPT / BUNDLE_*) preserves the Bundler
|
|
11
10
|
# context across the exec. The `none` and `process` (default) strategies
|
|
12
11
|
# need no flag, so this is a no-op for them — behaviour is unchanged
|
|
13
12
|
# unless a project opts into `ruby_box`.
|
|
@@ -17,22 +16,18 @@
|
|
|
17
16
|
# Rubies through 4.0.7 segfault on Ruby Bug #22260). When it cannot,
|
|
18
17
|
# the request is dropped with a warning and the run proceeds under the
|
|
19
18
|
# configured strategy (`process` by default) instead of crashing.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
#
|
|
20
|
+
# When `RUBY_BOX` is already set (by hand or by a wrapper), this process
|
|
21
|
+
# booted inside the box before any Rigor code ran, so the same probe
|
|
22
|
+
# runs here too, and a refused Ruby is re-exec'ed with `RUBY_BOX`
|
|
23
|
+
# removed. The probe's verdict is cached per Ruby build, and the
|
|
24
|
+
# re-exec is marked so the child neither probes nor re-execs again.
|
|
25
|
+
# Nothing above `require "rigor/cli"` may define a Ractor-shareable
|
|
26
|
+
# lambda: that is what crashes inside the box.
|
|
27
|
+
if !ENV["RUBY_BOX"].to_s.empty? || ENV["RIGOR_PLUGIN_ISOLATION"].to_s == "ruby_box" ||
|
|
28
|
+
!ENV["RIGOR_BOX"].to_s.empty? || ENV.key?("RIGOR_BOX_PROBED")
|
|
23
29
|
require_relative "../lib/rigor/plugin/box_probe"
|
|
24
|
-
|
|
25
|
-
rigor_box_warning = "rigor: ruby_box plugin isolation is unavailable: #{rigor_box_unsupported}. " \
|
|
26
|
-
"Falling back to the configured plugins_isolation strategy (default: process)."
|
|
27
|
-
# `$stderr.puts`, not `warn`: `-W0` silences `warn`, and this fallback must never be silent.
|
|
28
|
-
$stderr.puts rigor_box_warning # rubocop:disable Style/StderrPuts
|
|
29
|
-
ENV.delete("RIGOR_PLUGIN_ISOLATION") if rigor_isolation == "ruby_box"
|
|
30
|
-
ENV.delete("RIGOR_BOX")
|
|
31
|
-
else
|
|
32
|
-
ENV["RUBY_BOX"] = "1"
|
|
33
|
-
ENV["RIGOR_PLUGIN_ISOLATION"] = "ruby_box" if rigor_isolation.empty?
|
|
34
|
-
exec(RbConfig.ruby, __FILE__, *ARGV)
|
|
35
|
-
end
|
|
30
|
+
exec(RbConfig.ruby, __FILE__, *ARGV) if Rigor::Plugin::BoxProbe.needs_reexec?(ENV)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
lib = File.expand_path("../lib", __dir__)
|
|
@@ -173,6 +173,63 @@ module Rigor
|
|
|
173
173
|
configuration-error load-error pool-degraded runtime-error source-rbs-synthesis-failed
|
|
174
174
|
source-rbs-annotation-not-honoured
|
|
175
175
|
].freeze
|
|
176
|
+
|
|
177
|
+
# Resolves a user-supplied rule token (`undefined-method`, `call.undefined-method`, or the family wildcard
|
|
178
|
+
# `call`) to the set of canonical rule identifiers it disables. Returns `nil` for `"all"` (the existing
|
|
179
|
+
# wildcard meaning "every rule"), or for unknown tokens.
|
|
180
|
+
def self.resolve_rule_token(token)
|
|
181
|
+
return nil if token == "all"
|
|
182
|
+
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
183
|
+
return PRE_EVAL_RULES if token == "pre-eval"
|
|
184
|
+
return ALL_RULES.select { |r| r.start_with?("#{token}.") } if RULE_FAMILIES.include?(token)
|
|
185
|
+
|
|
186
|
+
ALL_RULES.include?(token) ? [token] : []
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Expands a list of user-supplied rule tokens into the canonical-id set per ADR-8 § "Backward
|
|
190
|
+
# compatibility". `disabled_rules` accepts unprefixed legacy names (`undefined-method`), canonical names
|
|
191
|
+
# (`call.undefined-method`), family wildcards (`call`) and the `all` sentinel.
|
|
192
|
+
def self.expand_rule_tokens(tokens)
|
|
193
|
+
Array(tokens).each_with_object(Set.new) do |token, set|
|
|
194
|
+
set.merge(expand_token(token.to_s))
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def self.expand_token(token)
|
|
199
|
+
return ["all"] if token == "all"
|
|
200
|
+
|
|
201
|
+
resolved = resolve_rule_token(token)
|
|
202
|
+
resolved.nil? || resolved.empty? ? [token] : resolved
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Whether the `.rigor.yml` `disable:` list, expanded by {.expand_rule_tokens}, drops a diagnostic whose rule
|
|
206
|
+
# id is `rule`: the `all` sentinel drops every rule-bearing row, any other entry its exact id. A nil rule (a
|
|
207
|
+
# parse, path or internal error) is never dropped.
|
|
208
|
+
def self.config_disabled?(rule, disabled)
|
|
209
|
+
return false if rule.nil?
|
|
210
|
+
|
|
211
|
+
disabled.include?("all") || disabled.include?(rule)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# The source-family prefix the runner stamps on every plugin-emitted row (`plugin.<id>`).
|
|
215
|
+
PLUGIN_FAMILY_PREFIX = "plugin."
|
|
216
|
+
|
|
217
|
+
# Issue #1953 — whether a suppression token set drops a plugin's own row. Such a row answers only to its
|
|
218
|
+
# qualified id: the exact `plugin.<id>.<rule>` (its `qualified_rule`) or the whole plugin's `plugin.<id>`
|
|
219
|
+
# (its `source_family`). Both compare whole strings against the row's own fields, so `plugin.<id>` never
|
|
220
|
+
# reaches a plugin whose id merely starts with `<id>.`. Ids and rules may both hold dots, so plugin `a` with
|
|
221
|
+
# rule `b.c` and plugin `a.b` with rule `c` share `plugin.a.b.c`, and that exact token drops both. A bare rule
|
|
222
|
+
# and `all` do not reach these rows: many plugins share a bare rule (`load-error`, `unknown-column`), and a
|
|
223
|
+
# bare match would hide another plugin's findings. Every other row (built-in, `plugin_loader`, `plugin_trust`)
|
|
224
|
+
# answers false.
|
|
225
|
+
def self.plugin_row_disabled?(diagnostic, tokens)
|
|
226
|
+
return false if diagnostic.rule.nil?
|
|
227
|
+
|
|
228
|
+
family = diagnostic.source_family.to_s
|
|
229
|
+
return false unless family.start_with?(PLUGIN_FAMILY_PREFIX)
|
|
230
|
+
|
|
231
|
+
tokens.include?(family) || tokens.include?(diagnostic.qualified_rule)
|
|
232
|
+
end
|
|
176
233
|
end
|
|
177
234
|
end
|
|
178
235
|
end
|
|
@@ -79,19 +79,8 @@ module Rigor
|
|
|
79
79
|
# runtime one and `def.override-visibility-reduced` cannot prove a reduction.
|
|
80
80
|
IMPLICITLY_PRIVATE_METHODS = (LIFECYCLE_HOOKS + %i[respond_to_missing?]).freeze
|
|
81
81
|
|
|
82
|
-
#
|
|
83
|
-
# `
|
|
84
|
-
# to the set of canonical rule identifiers it disables.
|
|
85
|
-
# Returns `nil` for `"all"` (the existing wildcard meaning
|
|
86
|
-
# "every rule"), or for unknown tokens.
|
|
87
|
-
def self.resolve_rule_token(token)
|
|
88
|
-
return nil if token == "all"
|
|
89
|
-
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
90
|
-
return PRE_EVAL_RULES if token == "pre-eval"
|
|
91
|
-
return ALL_RULES.select { |r| r.start_with?("#{token}.") } if RULE_FAMILIES.include?(token)
|
|
92
|
-
|
|
93
|
-
ALL_RULES.include?(token) ? [token] : []
|
|
94
|
-
end
|
|
82
|
+
# The token expansion (`resolve_rule_token` / `expand_rule_tokens` / `expand_token`) lives in the light
|
|
83
|
+
# `check_rules/rule_ids.rb`, so {SeverityStamp} can apply the `disable:` list on the engine-free hit path.
|
|
95
84
|
|
|
96
85
|
module_function
|
|
97
86
|
|
|
@@ -480,7 +469,7 @@ module Rigor
|
|
|
480
469
|
# - **File-level** (v0.1.2): `# rigor:disable-file <rule1>,
|
|
481
470
|
# <rule2>` anywhere in the file suppresses the matching
|
|
482
471
|
# diagnostic for every line. `# rigor:disable-file all`
|
|
483
|
-
# suppresses every rule across the file. Convention is
|
|
472
|
+
# suppresses every built-in rule across the file. Convention is
|
|
484
473
|
# to put the comment near the top, but Rigor accepts it
|
|
485
474
|
# anywhere — the comment scope is "this file" regardless
|
|
486
475
|
# of position.
|
|
@@ -488,7 +477,8 @@ module Rigor
|
|
|
488
477
|
# on the same line as the offending expression
|
|
489
478
|
# suppresses the matching diagnostic for that line
|
|
490
479
|
# only. `# rigor:disable all` on a line suppresses
|
|
491
|
-
# every rule on that line.
|
|
480
|
+
# every built-in rule on that line. A plugin's rows are
|
|
481
|
+
# not passed here; see {.filter_plugin_suppressed}.
|
|
492
482
|
#
|
|
493
483
|
# Both in-source forms are recognised only when the
|
|
494
484
|
# marker is the FIRST thing in the comment, so a
|
|
@@ -507,7 +497,7 @@ module Rigor
|
|
|
507
497
|
diagnostics.reject do |diagnostic|
|
|
508
498
|
rule = diagnostic.rule
|
|
509
499
|
next false if rule.nil?
|
|
510
|
-
next true if
|
|
500
|
+
next true if config_disabled?(rule, disabled)
|
|
511
501
|
next true if file_suppressions.include?("all") || file_suppressions.include?(rule)
|
|
512
502
|
|
|
513
503
|
line_rules = line_suppressions[diagnostic.line]
|
|
@@ -515,6 +505,26 @@ module Rigor
|
|
|
515
505
|
end
|
|
516
506
|
end
|
|
517
507
|
|
|
508
|
+
# Issue #1953 — the in-source markers over the rows a plugin emitted while `path` was analysed
|
|
509
|
+
# (`#diagnostics_for_file` and node rules). A row answers to a `plugin.<id>.<rule>` or `plugin.<id>` token
|
|
510
|
+
# only ({.plugin_row_disabled?}); a bare id and `all` keep reaching built-in rows alone. `comments` are
|
|
511
|
+
# `path`'s, so a row the plugin positioned in another file is left to the `disable:` list and the baseline.
|
|
512
|
+
# The `disable:` list itself is applied to these rows by {SeverityStamp}, on every run shape.
|
|
513
|
+
def filter_plugin_suppressed(diagnostics, path:, comments:)
|
|
514
|
+
return diagnostics if diagnostics.empty?
|
|
515
|
+
|
|
516
|
+
line_suppressions, file_suppressions = parse_suppression_comments(comments)
|
|
517
|
+
return diagnostics if line_suppressions.empty? && file_suppressions.empty?
|
|
518
|
+
|
|
519
|
+
diagnostics.reject do |diagnostic|
|
|
520
|
+
next false unless diagnostic.path == path
|
|
521
|
+
next true if plugin_row_disabled?(diagnostic, file_suppressions)
|
|
522
|
+
|
|
523
|
+
line_rules = line_suppressions.fetch(diagnostic.line, nil)
|
|
524
|
+
!line_rules.nil? && plugin_row_disabled?(diagnostic, line_rules)
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
518
528
|
# Every suppression-recognition pattern below is anchored with `\A` against the COMMENT SLICE —
|
|
519
529
|
# Prism hands us a slice that starts at the `#`, so `\A#` means "the marker is the first thing in
|
|
520
530
|
# the comment". A real directive always has that shape, in both the whole-line form and the
|
|
@@ -704,24 +714,6 @@ module Rigor
|
|
|
704
714
|
)
|
|
705
715
|
end
|
|
706
716
|
|
|
707
|
-
# Expands a list of user-supplied rule tokens into the
|
|
708
|
-
# canonical-id set per ADR-8 § "Backward compatibility".
|
|
709
|
-
# `disabled_rules` accepts unprefixed legacy names
|
|
710
|
-
# (`undefined-method`), canonical names
|
|
711
|
-
# (`call.undefined-method`), and family wildcards (`call`).
|
|
712
|
-
def expand_rule_tokens(tokens)
|
|
713
|
-
Array(tokens).each_with_object(Set.new) do |token, set|
|
|
714
|
-
set.merge(expand_token(token.to_s))
|
|
715
|
-
end
|
|
716
|
-
end
|
|
717
|
-
|
|
718
|
-
def expand_token(token)
|
|
719
|
-
return ["all"] if token == "all"
|
|
720
|
-
|
|
721
|
-
resolved = resolve_rule_token(token)
|
|
722
|
-
resolved.nil? || resolved.empty? ? [token] : resolved
|
|
723
|
-
end
|
|
724
|
-
|
|
725
717
|
# rubocop:disable-next Metrics/ClassLength
|
|
726
718
|
class << self
|
|
727
719
|
private
|
|
@@ -1393,6 +1385,21 @@ module Rigor
|
|
|
1393
1385
|
GEM_OVERLAY_OPEN_RECEIVERS = Set["ActiveSupport::Duration", "ActiveSupport::Concern"].freeze
|
|
1394
1386
|
private_constant :GEM_OVERLAY_OPEN_RECEIVERS
|
|
1395
1387
|
|
|
1388
|
+
# Issue #1456 — the `data/core_overlay/` twin of {GEM_OVERLAY_OPEN_RECEIVERS}. `OpenStruct` defines each field
|
|
1389
|
+
# as a per-instance singleton method and answers the rest through a private `method_missing` (ostruct 0.6), so
|
|
1390
|
+
# no signature can enumerate its surface. The only other declaration in the environment is json's add-on
|
|
1391
|
+
# reopening (`json_create` / `as_json` / `to_json`), which made the class read as fully declared and every
|
|
1392
|
+
# field access a `call.undefined-method`. `data/core_overlay/ostruct.rbs` declares that `method_missing`, and
|
|
1393
|
+
# an RBS-declared `method_missing` does not by itself open a receiver here, so the class is named. No load
|
|
1394
|
+
# gate is needed: the core overlay loads into every environment that has RBS at all, so the list is active
|
|
1395
|
+
# whenever the declaration it protects is. Both sides of the class are open, as with
|
|
1396
|
+
# {GEM_OVERLAY_OPEN_RECEIVERS}, and an inherited Object / Kernel signature stops deciding `call.wrong-arity`
|
|
1397
|
+
# and `call.argument-type-mismatch` ({#unauthoritative_inherited_signature?}): lost true positives,
|
|
1398
|
+
# accepted. The general rule — a class declared only by another library's partial reopening — is #1978, not
|
|
1399
|
+
# this list.
|
|
1400
|
+
CORE_OVERLAY_OPEN_RECEIVERS = Set["OpenStruct"].freeze
|
|
1401
|
+
private_constant :CORE_OVERLAY_OPEN_RECEIVERS
|
|
1402
|
+
|
|
1396
1403
|
# ADR-26 — whether `class_name` is declared "open" by a
|
|
1397
1404
|
# loaded plugin (manifest `open_receivers:`). An open
|
|
1398
1405
|
# class responds beyond its RBS surface, so the
|
|
@@ -1401,11 +1408,14 @@ module Rigor
|
|
|
1401
1408
|
# RBS method table, so proving a call "undefined" against it is
|
|
1402
1409
|
# unsound: a plugin-declared open receiver, a Rigor-bundled-
|
|
1403
1410
|
# overlay open receiver ({GEM_OVERLAY_OPEN_RECEIVERS}) whose
|
|
1404
|
-
# bundled directory this run actually loaded,
|
|
1411
|
+
# bundled directory this run actually loaded, a core-overlay
|
|
1412
|
+
# open receiver ({CORE_OVERLAY_OPEN_RECEIVERS}), or a
|
|
1405
1413
|
# Rigor-synthesized stub type (see `RbsLoader#synthesized_type_names`).
|
|
1406
1414
|
def unbounded_receiver_surface?(class_name, scope)
|
|
1415
|
+
bare_name = class_name.to_s.sub(/\A::/, "")
|
|
1407
1416
|
open_receiver?(class_name, scope) || synthesized_stub_receiver?(class_name, scope) ||
|
|
1408
|
-
|
|
1417
|
+
CORE_OVERLAY_OPEN_RECEIVERS.include?(bare_name) ||
|
|
1418
|
+
(GEM_OVERLAY_OPEN_RECEIVERS.include?(bare_name) && gem_overlay_loaded?(scope))
|
|
1409
1419
|
end
|
|
1410
1420
|
|
|
1411
1421
|
def open_receiver?(class_name, scope)
|
|
@@ -120,6 +120,18 @@ module Rigor
|
|
|
120
120
|
appeared.freeze
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
# Issue #1459 — the method names some file in `paths` started or stopped defining in this edit, on any class or
|
|
124
|
+
# side. A consumer whose answer asked whether the program defines a name anywhere
|
|
125
|
+
# (`Inference::BlockCallTiming.project_defines_anywhere?`) recorded a `defines:<name>` edge whichever way it
|
|
126
|
+
# answered. `paths` MUST include removed files, whose every name vanished. `before` / `after` map a path to the
|
|
127
|
+
# Set of names it defines (`Inference::ScopeIndexer.defined_method_names`); a path absent from either side
|
|
128
|
+
# defines nothing there.
|
|
129
|
+
def changed_defined_names(paths, before, after)
|
|
130
|
+
moved = Set.new
|
|
131
|
+
paths.each { |path| moved.merge((before[path] || Set.new) ^ (after[path] || Set.new)) }
|
|
132
|
+
moved.freeze
|
|
133
|
+
end
|
|
134
|
+
|
|
123
135
|
# Issue #1120 — the method names whose refinements moved in this edit. A consumer whose answer consulted the
|
|
124
136
|
# refinement table recorded a `refinement:<method>` edge whichever way it answered, because the answer is a
|
|
125
137
|
# function of the whole project's refinements of that name. `paths` MUST include removed files, whose whole
|