rigortype 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +82 -20
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/docs/handbook/01-getting-started.md +311 -0
- data/docs/handbook/02-everyday-types.md +337 -0
- data/docs/handbook/03-narrowing.md +359 -0
- data/docs/handbook/04-tuples-and-shapes.md +321 -0
- data/docs/handbook/05-methods-and-blocks.md +339 -0
- data/docs/handbook/06-classes.md +305 -0
- data/docs/handbook/07-rbs-and-extended.md +427 -0
- data/docs/handbook/08-understanding-errors.md +373 -0
- data/docs/handbook/09-plugins.md +241 -0
- data/docs/handbook/10-sorbet.md +347 -0
- data/docs/handbook/11-sig-gen.md +312 -0
- data/docs/handbook/12-lightweight-hkt.md +333 -0
- data/docs/handbook/README.md +275 -0
- data/docs/handbook/appendix-elixir.md +370 -0
- data/docs/handbook/appendix-go.md +399 -0
- data/docs/handbook/appendix-java-csharp.md +470 -0
- data/docs/handbook/appendix-liskov.md +580 -0
- data/docs/handbook/appendix-mypy.md +370 -0
- data/docs/handbook/appendix-phpstan.md +338 -0
- data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
- data/docs/handbook/appendix-rust.md +446 -0
- data/docs/handbook/appendix-steep.md +336 -0
- data/docs/handbook/appendix-type-theory.md +1662 -0
- data/docs/handbook/appendix-typeprof.md +416 -0
- data/docs/handbook/appendix-typescript.md +332 -0
- data/docs/install.md +189 -0
- data/docs/llms.txt +72 -0
- data/docs/manual/01-installation.md +342 -0
- data/docs/manual/02-cli-reference.md +557 -0
- data/docs/manual/03-configuration.md +152 -0
- data/docs/manual/04-diagnostics.md +206 -0
- data/docs/manual/05-inspecting-types.md +109 -0
- data/docs/manual/06-baseline.md +104 -0
- data/docs/manual/07-plugins.md +92 -0
- data/docs/manual/08-skills.md +143 -0
- data/docs/manual/09-editor-integration.md +245 -0
- data/docs/manual/10-mcp-server.md +532 -0
- data/docs/manual/11-ci.md +274 -0
- data/docs/manual/12-caching.md +116 -0
- data/docs/manual/13-troubleshooting.md +120 -0
- data/docs/manual/14-rails-quickstart.md +332 -0
- data/docs/manual/15-type-protection-coverage.md +204 -0
- data/docs/manual/16-rbs-extended-annotations.md +190 -0
- data/docs/manual/17-driving-improvement.md +160 -0
- data/docs/manual/README.md +87 -0
- data/docs/manual/ci-templates/README.md +58 -0
- data/docs/manual/plugins/README.md +86 -0
- data/docs/manual/plugins/rigor-actioncable.md +78 -0
- data/docs/manual/plugins/rigor-actionmailer.md +74 -0
- data/docs/manual/plugins/rigor-actionpack.md +80 -0
- data/docs/manual/plugins/rigor-activejob.md +58 -0
- data/docs/manual/plugins/rigor-activerecord.md +102 -0
- data/docs/manual/plugins/rigor-activestorage.md +74 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
- data/docs/manual/plugins/rigor-devise.md +70 -0
- data/docs/manual/plugins/rigor-dry-schema.md +56 -0
- data/docs/manual/plugins/rigor-dry-struct.md +60 -0
- data/docs/manual/plugins/rigor-dry-types.md +59 -0
- data/docs/manual/plugins/rigor-dry-validation.md +62 -0
- data/docs/manual/plugins/rigor-factorybot.md +76 -0
- data/docs/manual/plugins/rigor-graphql.md +89 -0
- data/docs/manual/plugins/rigor-hanami.md +83 -0
- data/docs/manual/plugins/rigor-mangrove.md +73 -0
- data/docs/manual/plugins/rigor-minitest.md +86 -0
- data/docs/manual/plugins/rigor-pundit.md +72 -0
- data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
- data/docs/manual/plugins/rigor-rails-routes.md +94 -0
- data/docs/manual/plugins/rigor-rails.md +44 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
- data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
- data/docs/manual/plugins/rigor-rspec.md +86 -0
- data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
- data/docs/manual/plugins/rigor-sidekiq.md +78 -0
- data/docs/manual/plugins/rigor-sinatra.md +61 -0
- data/docs/manual/plugins/rigor-sorbet.md +63 -0
- data/docs/manual/plugins/rigor-statesman.md +75 -0
- data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
- data/exe/rigor +1 -1
- data/lib/rigor/analysis/incremental_session.rb +4 -2
- data/lib/rigor/analysis/run_stats.rb +13 -1
- data/lib/rigor/analysis/runner.rb +54 -12
- data/lib/rigor/cli/check_command.rb +26 -3
- data/lib/rigor/cli/coverage_command.rb +67 -92
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration.rb +12 -0
- data/lib/rigor/environment/rbs_loader.rb +27 -0
- data/lib/rigor/environment.rb +49 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +140 -38
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/inference/statement_evaluator.rb +27 -0
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +98 -38
- data/lib/rigor/protection/mutator.rb +21 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/version.rb +1 -1
- data/skills/rigor-ask/SKILL.md +172 -0
- data/skills/rigor-doctor/SKILL.md +87 -0
- data/skills/rigor-editor-setup/SKILL.md +114 -0
- data/skills/rigor-mcp-setup/SKILL.md +117 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
- data/skills/rigor-next-steps/SKILL.md +113 -0
- data/skills/rigor-plugin-tune/SKILL.md +79 -0
- data/skills/rigor-protection-uplift/SKILL.md +133 -0
- data/skills/rigor-rbs-setup/SKILL.md +128 -0
- data/skills/rigor-upgrade/SKILL.md +79 -0
- metadata +120 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# rigor-sinatra
|
|
2
|
+
|
|
3
|
+
Recognises Sinatra's class-level route DSL (`get` / `post` / `put`
|
|
4
|
+
/ `delete` / `head` / `options` / `patch` / `link` / `unlink`) on
|
|
5
|
+
`Sinatra::Base` subclasses and narrows the route block's `self` so
|
|
6
|
+
its bare helpers — `params`, `redirect`, `halt`, `session`,
|
|
7
|
+
`headers`, `content_type`, `erb`, `status`, `body`, … — resolve
|
|
8
|
+
through `Sinatra::Base`'s RBS instead of going unresolved.
|
|
9
|
+
|
|
10
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`:
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
plugins:
|
|
14
|
+
- rigor-sinatra
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
class MyApp < Sinatra::Base
|
|
21
|
+
get "/users/:id" do
|
|
22
|
+
halt 404 unless params["id"]
|
|
23
|
+
redirect "/users/#{params['id']}/profile"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Inside the route block, `self` is narrowed to `MyApp` (which
|
|
29
|
+
inherits from `Sinatra::Base`), so `params` / `redirect` / `halt`
|
|
30
|
+
resolve through the normal RBS chain. Without the plugin the block
|
|
31
|
+
body is typed as `Singleton[MyApp]` and that per-block resolution is
|
|
32
|
+
lost.
|
|
33
|
+
|
|
34
|
+
Sinatra's own RBS needs to be available for the helpers to resolve
|
|
35
|
+
— through Rigor's Bundler-awareness, a vendored sig, or (in the
|
|
36
|
+
demo) a local stub.
|
|
37
|
+
|
|
38
|
+
## No diagnostics, no config
|
|
39
|
+
|
|
40
|
+
The plugin only recognises the route shape and narrows `self`; it
|
|
41
|
+
emits no diagnostics and has no config keys (the verb match table
|
|
42
|
+
is fixed in the manifest).
|
|
43
|
+
|
|
44
|
+
## Limitations
|
|
45
|
+
|
|
46
|
+
- **No routing diagnostics** — path-pattern uniqueness, conflict
|
|
47
|
+
detection, and named-route reverse lookup are out of scope.
|
|
48
|
+
- **`helpers do … end`** blocks (injecting instance methods) are
|
|
49
|
+
not handled (that's Tier B / C work, not this Tier A shape).
|
|
50
|
+
- **`configure` / `set` settings DSL** is not handled.
|
|
51
|
+
- **Classic-style top-level routes** — a bare `get '/x' do … end`
|
|
52
|
+
with no enclosing `class < Sinatra::Base` — are deferred; the
|
|
53
|
+
recognition needs the receiver's class visible at the call site.
|
|
54
|
+
|
|
55
|
+
## Plugin internals
|
|
56
|
+
|
|
57
|
+
The declarative `BlockAsMethod` manifest and the macro-substrate
|
|
58
|
+
`self`-narrowing it rides on are documented in the
|
|
59
|
+
[plugin's README](../../../plugins/rigor-sinatra/README.md). To
|
|
60
|
+
write a plugin, see [`examples/`](../../../examples/README.md) and
|
|
61
|
+
the [`rigor-plugin-author`](../08-skills.md) skill.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# rigor-sorbet
|
|
2
|
+
|
|
3
|
+
Lets Rigor read an existing [Sorbet](https://sorbet.org/) codebase
|
|
4
|
+
as a type source: inline `sig { ... }` blocks, RBI files, and the
|
|
5
|
+
`T.let` / `T.cast` / `T.must` / `T.unsafe` / `T.bind` /
|
|
6
|
+
`T.assert_type!` / `T.absurd` assertion forms are translated into
|
|
7
|
+
Rigor's own carriers, so you can run `rigor check` alongside
|
|
8
|
+
`srb tc` without rewriting anything in RBS. It reads source only —
|
|
9
|
+
it does not load `sorbet-runtime`.
|
|
10
|
+
|
|
11
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
plugins:
|
|
15
|
+
- rigor-sorbet
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **Full guide.** This page is the operational quick reference.
|
|
19
|
+
> The complete walkthrough — the Sorbet→Rigor type-vocabulary
|
|
20
|
+
> table, every assertion form, RBI / Tapioca-DSL handling, sigil
|
|
21
|
+
> semantics, `T.absurd` exhaustiveness, and the migration
|
|
22
|
+
> patterns — is
|
|
23
|
+
> [handbook chapter 10 — Coexisting with Sorbet](../../handbook/10-sorbet.md).
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
plugins:
|
|
29
|
+
- gem: rigor-sorbet
|
|
30
|
+
config:
|
|
31
|
+
enforce_sigil: true # default; honour `# typed:` sigils
|
|
32
|
+
rbi_paths: ["sorbet/rbi"] # default; set [] to disable RBI loading
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **`enforce_sigil`** (default `true`) — mirror Sorbet's own
|
|
36
|
+
contract: only record sigs from files at `# typed: true` or
|
|
37
|
+
stricter. Set `false` to record sigs from every parseable file
|
|
38
|
+
regardless of sigil. The inline assertion recognisers (`T.let`,
|
|
39
|
+
`T.cast`, …) always fire, since the user wrote them deliberately.
|
|
40
|
+
- **`rbi_paths`** (default `["sorbet/rbi"]`) — directories of
|
|
41
|
+
`.rbi` files to load (the standard Tapioca subdirectories
|
|
42
|
+
`gems/` / `annotations/` / `dsl/` / `shims/` participate by
|
|
43
|
+
recursion). Set `[]` to opt out, or add a vendored tree.
|
|
44
|
+
|
|
45
|
+
## Scope and limits
|
|
46
|
+
|
|
47
|
+
The plugin is **input-side only**: it translates Sorbet's syntax
|
|
48
|
+
into Rigor's type model. It does **not** run Sorbet's checker,
|
|
49
|
+
ship `sorbet-runtime`, or enforce Sorbet's runtime guarantees.
|
|
50
|
+
When an RBS sig and a Sorbet sig disagree, RBS wins (the Sorbet
|
|
51
|
+
sig may refine but not contradict it). Forms outside the
|
|
52
|
+
translation table (`T.proc`, `T.self_type`, `T::Struct` /
|
|
53
|
+
`T::Enum` subclasses, …) degrade to `Dynamic[top]`. Chapter 10
|
|
54
|
+
documents the full vocabulary and these edges.
|
|
55
|
+
|
|
56
|
+
## Plugin internals
|
|
57
|
+
|
|
58
|
+
The slice-by-slice implementation (sig parsing, assertion
|
|
59
|
+
lifting, the RBI tree walker, mixin-chain resolution, the
|
|
60
|
+
dispatcher tier ordering), the source layout, and the demo are in
|
|
61
|
+
the [plugin's README](../../../plugins/rigor-sorbet/README.md).
|
|
62
|
+
The design rationale is
|
|
63
|
+
[ADR-11](../../adr/11-sorbet-input-adapter.md).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# rigor-statesman
|
|
2
|
+
|
|
3
|
+
Validates `transition_to(:state)` calls against the states declared
|
|
4
|
+
in a `state_machine do … end` block, within the same file. A
|
|
5
|
+
transition to an undeclared state is flagged (with a did-you-mean
|
|
6
|
+
suggestion). It reads source only — no Statesman runtime
|
|
7
|
+
dependency. (The DSL method names are configurable, so it also fits
|
|
8
|
+
AASM-shaped state machines.)
|
|
9
|
+
|
|
10
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`:
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
plugins:
|
|
14
|
+
- rigor-statesman
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it checks
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
class Order
|
|
21
|
+
state_machine do
|
|
22
|
+
state :draft, initial: true
|
|
23
|
+
state :submitted
|
|
24
|
+
state :approved
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
order.transition_to(:submitted) # info: known state
|
|
29
|
+
order.transition_to(:approval) # error: unknown state :approval (did you mean :approved?)
|
|
30
|
+
order.transition_to(:purgatory) # error: unknown state :purgatory
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Rule | Severity | Fires when |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `plugin.statesman.known-state` | info | `transition_to(:sym)` where `:sym` is declared in an in-file `state_machine` block |
|
|
36
|
+
| `plugin.statesman.unknown-state` | error | `transition_to(:sym)` where `:sym` is not declared (with a `Base.suggest` did-you-mean) |
|
|
37
|
+
|
|
38
|
+
Multiple `state_machine` blocks in one file union their states.
|
|
39
|
+
Non-literal-symbol arguments and files with no state machine are
|
|
40
|
+
silently passed through.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
plugins:
|
|
46
|
+
- gem: rigor-statesman
|
|
47
|
+
config:
|
|
48
|
+
dsl_method: "state_machine" # default; the block-opening method
|
|
49
|
+
state_method: "state" # default; the state-declaring method
|
|
50
|
+
transition_method: "transition_to" # default; the call to validate
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Renaming these adapts the plugin to a different state-machine DSL
|
|
54
|
+
(e.g. AASM's `aasm do … state … end`).
|
|
55
|
+
|
|
56
|
+
## Limitations
|
|
57
|
+
|
|
58
|
+
- **File-scoped.** States declared in `models/order.rb` aren't
|
|
59
|
+
visible from another file — each file validates independently.
|
|
60
|
+
- **Literal symbols only.** A variable or method-call argument is
|
|
61
|
+
not checked.
|
|
62
|
+
- **Receiver-agnostic.** The check fires on any receiver as long as
|
|
63
|
+
*some* state machine in the file declares the symbol; it does not
|
|
64
|
+
tie a `transition_to` to a specific machine.
|
|
65
|
+
|
|
66
|
+
## Plugin internals
|
|
67
|
+
|
|
68
|
+
`rigor-statesman` is the reference example for the two-pass
|
|
69
|
+
(collect-then-validate) pattern: a `node_file_context` pass
|
|
70
|
+
collects the declared states once, and a `node_rule` validates each
|
|
71
|
+
`transition_to` over the engine-owned walk. The layout, demo, and
|
|
72
|
+
contract surfaces are in the
|
|
73
|
+
[plugin's README](../../../plugins/rigor-statesman/README.md). To
|
|
74
|
+
write a plugin, see [`examples/`](../../../examples/README.md) and
|
|
75
|
+
the [`rigor-plugin-author`](../08-skills.md) skill.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# rigor-typescript-utility-types
|
|
2
|
+
|
|
3
|
+
Maps the TypeScript-canonical utility-type spellings onto Rigor's own
|
|
4
|
+
shape-projection type functions, so a codebase migrating from
|
|
5
|
+
TypeScript / Sorbet RBI can write the familiar names inside
|
|
6
|
+
`RBS::Extended` annotations. It registers five
|
|
7
|
+
[ADR-13](../../adr/13-typenode-resolver-plugin.md) `TypeNodeResolver`s
|
|
8
|
+
as a pure translation layer; the shape *semantics* live in core, where
|
|
9
|
+
they have one spec-owned definition shared by every consumer.
|
|
10
|
+
|
|
11
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
plugins:
|
|
15
|
+
- rigor-typescript-utility-types
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **Full guide.** The shape projections themselves — what each does,
|
|
19
|
+
> the lossy-projection rule, and the TypeScript→Rigor vocabulary table
|
|
20
|
+
> — are covered in the handbook:
|
|
21
|
+
> [chapter 4 — Tuples and hash shapes](../../handbook/04-tuples-and-shapes.md)
|
|
22
|
+
> § "Deriving new shapes" and the
|
|
23
|
+
> [TypeScript appendix](../../handbook/appendix-typescript.md). This page
|
|
24
|
+
> is the operational quick reference.
|
|
25
|
+
|
|
26
|
+
## What it resolves
|
|
27
|
+
|
|
28
|
+
| TypeScript spelling | Rigor core projection |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `Pick<T, K>` | `pick_of[T, K]` |
|
|
31
|
+
| `Omit<T, K>` | `omit_of[T, K]` |
|
|
32
|
+
| `Partial<T>` | `partial_of[T]` |
|
|
33
|
+
| `Required<T>` | `required_of[T]` |
|
|
34
|
+
| `Readonly<T>` | `readonly_of[T]` |
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
class Address
|
|
38
|
+
# @rbs!
|
|
39
|
+
# %a{rigor:v1:return: Pick[Address::Shape, :name | :email]}
|
|
40
|
+
def public_fields; end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Once the plugin is active, the `Pick[…]` head resolves through the full
|
|
45
|
+
resolution pass (built-ins → plugin chain → RBS Nominal fallback), so
|
|
46
|
+
the sub-arguments may use any of Rigor's existing type vocabulary.
|
|
47
|
+
|
|
48
|
+
## No configuration
|
|
49
|
+
|
|
50
|
+
The plugin has no configuration knobs — the resolver chain is
|
|
51
|
+
registered at class load.
|
|
52
|
+
|
|
53
|
+
## Limitations
|
|
54
|
+
|
|
55
|
+
- **Unmapped TS names degrade to `Nominal`.** `Parameters<F>`,
|
|
56
|
+
`ReturnType<F>`, `InstanceType<C>`, `Awaited<P>`, the string-casing
|
|
57
|
+
utilities (`Uppercase`/`Lowercase`/…), `ThisParameterType`, and
|
|
58
|
+
`NoInfer` are not mapped — they have no Rigor analogue yet (or need a
|
|
59
|
+
core operator that hasn't landed) and resolve as `Nominal[Name, […]]`.
|
|
60
|
+
- **Lossy on non-shape carriers.** A projection applied to a bare
|
|
61
|
+
`Nominal[Hash, [K, V]]` (rather than a `HashShape` / `Tuple`) returns
|
|
62
|
+
the input unchanged and records a `dynamic.shape.lossy-projection`
|
|
63
|
+
`:info` diagnostic so you can audit the call site.
|
|
64
|
+
|
|
65
|
+
## Plugin internals
|
|
66
|
+
|
|
67
|
+
The five resolver classes, the recursive resolution mechanism, and the
|
|
68
|
+
ADR-13 `TypeNodeResolver` contract are in the
|
|
69
|
+
[plugin's README](../../../plugins/rigor-typescript-utility-types/README.md).
|
|
70
|
+
To write a plugin, see [`examples/`](../../../examples/README.md) and the
|
|
71
|
+
[`rigor-plugin-author`](../08-skills.md) skill.
|
data/exe/rigor
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# alias) we re-exec the same command with the flag set before anything
|
|
9
9
|
# else loads. The `RUBY_BOX` guard prevents an infinite re-exec, and the
|
|
10
10
|
# inherited environment (RUBYOPT / BUNDLE_*) preserves the Bundler
|
|
11
|
-
# context across the exec. The `none`
|
|
11
|
+
# context across the exec. The `none` and `process` (default) strategies
|
|
12
12
|
# need no flag, so this is a no-op for them — behaviour is unchanged
|
|
13
13
|
# unless a project opts into `ruby_box`.
|
|
14
14
|
rigor_isolation = ENV["RIGOR_PLUGIN_ISOLATION"].to_s
|
|
@@ -182,8 +182,10 @@ module Rigor
|
|
|
182
182
|
@symbol_fingerprints = payload.symbol_fingerprints || {}
|
|
183
183
|
# ADR-46 slice 3 — restore negative edges if present (absent in
|
|
184
184
|
# pre-slice-3 snapshots → empty, which only loses the appeared-symbol
|
|
185
|
-
# re-check refinement;
|
|
186
|
-
#
|
|
185
|
+
# re-check refinement; such a snapshot is never loaded by a slice-3+
|
|
186
|
+
# engine because the engine version + schema is part of the snapshot
|
|
187
|
+
# fingerprint, and `--verify-incremental` backstops any residual
|
|
188
|
+
# under-capture, so it is never unsound).
|
|
187
189
|
@missing = payload.missing || {}
|
|
188
190
|
@class_decls = payload.class_decls || {}
|
|
189
191
|
@symbol_dependents = Incremental.invert_symbols(@symbol_sources)
|
|
@@ -144,7 +144,19 @@ module Rigor
|
|
|
144
144
|
out.puts("#{prefix} Ruby source files: #{@target_files}")
|
|
145
145
|
out.puts("#{prefix}Type universe (symbol discovery; not analyzed for diagnostics)")
|
|
146
146
|
out.puts("#{prefix} RBS classes available: #{@rbs_classes_total}")
|
|
147
|
-
if @
|
|
147
|
+
if @rbs_classes_total.zero?
|
|
148
|
+
# A normal run always loads the bundled core+stdlib RBS (~1300+
|
|
149
|
+
# classes), so zero means the environment failed to build (most
|
|
150
|
+
# often a duplicate declaration in `signature_paths:`) and fell
|
|
151
|
+
# back to empty — type coverage is then near-useless but the run
|
|
152
|
+
# still "succeeds". Surface it loudly so a broken setup is not
|
|
153
|
+
# read as a clean analysis (the 20260620 field trial: redmine
|
|
154
|
+
# would otherwise wire a 0-coverage check into CI).
|
|
155
|
+
out.puts("#{prefix} WARNING: the RBS environment is empty — it failed to build or loaded no")
|
|
156
|
+
out.puts("#{prefix} signatures, so type coverage is severely limited (most diagnostics")
|
|
157
|
+
out.puts("#{prefix} and coverage cannot fire). Usually a duplicate declaration in")
|
|
158
|
+
out.puts("#{prefix} `signature_paths:` — fix it and re-run; the rigor-doctor skill helps.")
|
|
159
|
+
elsif @rbs_attribution_available
|
|
148
160
|
out.puts("#{prefix} project sig/: #{@rbs_classes_project_sig}")
|
|
149
161
|
out.puts("#{prefix} bundled (core+stdlib+gems): #{@rbs_classes_bundled}")
|
|
150
162
|
elsif @rbs_classes_total.positive?
|
|
@@ -483,19 +483,48 @@ module Rigor
|
|
|
483
483
|
end
|
|
484
484
|
private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result
|
|
485
485
|
|
|
486
|
+
# Ruby versions probed (ascending) to discover the lowest one this
|
|
487
|
+
# Prism build accepts for `version:`. Prism exposes no version
|
|
488
|
+
# list, so the floor is found empirically — only when a
|
|
489
|
+
# misconfigured `target_ruby` is rejected — so the diagnostic can
|
|
490
|
+
# name it instead of leaving the user to guess (the dogfood field
|
|
491
|
+
# trial, 20260620-skill-driven-onboarding-dogfood.md, burned cycles
|
|
492
|
+
# on exactly this).
|
|
493
|
+
PRISM_VERSION_LADDER = %w[
|
|
494
|
+
3.0.0 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 4.0.0 4.1.0 4.2.0
|
|
495
|
+
].freeze
|
|
496
|
+
|
|
497
|
+
# @return [String, nil] the lowest `target_ruby` this Prism build
|
|
498
|
+
# accepts, or nil if none of the ladder parses. Memoised per
|
|
499
|
+
# process (the value is constant for a given Prism).
|
|
500
|
+
def self.prism_supported_floor
|
|
501
|
+
@prism_supported_floor ||= PRISM_VERSION_LADDER.find do |candidate|
|
|
502
|
+
Prism.parse("nil", version: candidate)
|
|
503
|
+
true
|
|
504
|
+
rescue ArgumentError
|
|
505
|
+
false
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
486
509
|
# `target_ruby` flows through to Prism's `version:` option.
|
|
487
|
-
# Prism enforces the supported range and raises
|
|
488
|
-
#
|
|
489
|
-
#
|
|
490
|
-
#
|
|
491
|
-
#
|
|
510
|
+
# Prism enforces the supported range and raises `ArgumentError`
|
|
511
|
+
# for versions it does not recognise. Run a one-time smoke parse
|
|
512
|
+
# here so a misconfigured target_ruby surfaces as a single
|
|
513
|
+
# project-level diagnostic instead of crashing the whole run on
|
|
514
|
+
# the first file — and name the supported floor + where to read
|
|
515
|
+
# the right value, so the fix is obvious without a guess-and-retry
|
|
516
|
+
# loop.
|
|
492
517
|
def validate_target_ruby
|
|
493
518
|
Prism.parse("nil", version: @configuration.target_ruby)
|
|
494
519
|
nil
|
|
495
520
|
rescue ArgumentError => e
|
|
521
|
+
floor = self.class.prism_supported_floor
|
|
496
522
|
Diagnostic.new(
|
|
497
523
|
path: ".rigor.yml", line: 1, column: 1,
|
|
498
|
-
message: "target_ruby #{@configuration.target_ruby.inspect} is not
|
|
524
|
+
message: "target_ruby #{@configuration.target_ruby.inspect} is not supported by this Rigor build " \
|
|
525
|
+
"(Prism accepts #{floor} and newer). Set target_ruby to your project's Ruby version " \
|
|
526
|
+
"(>= #{floor}) — read it from Gemfile.lock's `RUBY VERSION` or .ruby-version. " \
|
|
527
|
+
"(Prism: #{e.message})",
|
|
499
528
|
severity: :error,
|
|
500
529
|
rule: "configuration-error",
|
|
501
530
|
source_family: :builtin
|
|
@@ -734,7 +763,7 @@ module Rigor
|
|
|
734
763
|
# cleanly with no output, which silently masked typos.
|
|
735
764
|
def expand_paths(paths)
|
|
736
765
|
files = []
|
|
737
|
-
|
|
766
|
+
bad = []
|
|
738
767
|
Array(paths).each do |path|
|
|
739
768
|
if File.directory?(path)
|
|
740
769
|
files.concat(reject_excluded(Dir.glob(File.join(path, RUBY_GLOB))))
|
|
@@ -745,12 +774,25 @@ module Rigor
|
|
|
745
774
|
elsif accept_as_ruby_file?(path)
|
|
746
775
|
files << path
|
|
747
776
|
elsif File.exist?(path)
|
|
748
|
-
|
|
777
|
+
bad << [path, "not a Ruby file (expected `.rb` or a directory)"]
|
|
749
778
|
else
|
|
750
|
-
|
|
779
|
+
bad << [path, "no such file or directory"]
|
|
751
780
|
end
|
|
752
781
|
end
|
|
753
|
-
{ files: files, errors:
|
|
782
|
+
{ files: files, errors: path_expansion_errors(bad, any_files: files.any?) }
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
# A bad path *among valid ones* is a warn-and-skip — the run still
|
|
786
|
+
# does useful work, so `rigor check app lib` with no `lib/` (the
|
|
787
|
+
# 20260620 field trial's strap case) analyses `app` instead of
|
|
788
|
+
# aborting on exit 1. A bad path that leaves NOTHING to analyse
|
|
789
|
+
# stays an error so a lone typo (`rigor check typo.rb`) is not
|
|
790
|
+
# silently masked (the regression the path-error check was added
|
|
791
|
+
# to catch in the first place).
|
|
792
|
+
def path_expansion_errors(bad, any_files:)
|
|
793
|
+
severity = any_files ? :warning : :error
|
|
794
|
+
suffix = any_files ? " (skipped)" : ""
|
|
795
|
+
bad.map { |path, message| path_error(path, "#{message}#{suffix}", severity: severity) }
|
|
754
796
|
end
|
|
755
797
|
|
|
756
798
|
def accept_as_ruby_file?(path)
|
|
@@ -778,13 +820,13 @@ module Rigor
|
|
|
778
820
|
@configuration.exclude_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
|
|
779
821
|
end
|
|
780
822
|
|
|
781
|
-
def path_error(path, message)
|
|
823
|
+
def path_error(path, message, severity: :error)
|
|
782
824
|
Diagnostic.new(
|
|
783
825
|
path: path,
|
|
784
826
|
line: 1,
|
|
785
827
|
column: 1,
|
|
786
828
|
message: message,
|
|
787
|
-
severity:
|
|
829
|
+
severity: severity
|
|
788
830
|
)
|
|
789
831
|
end
|
|
790
832
|
|
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require "optionparser"
|
|
6
6
|
|
|
7
7
|
require_relative "../configuration"
|
|
8
|
+
require_relative "../config_audit"
|
|
8
9
|
require_relative "../analysis/result"
|
|
9
10
|
require_relative "../analysis/rule_catalog"
|
|
10
11
|
require_relative "coverage_scan"
|
|
@@ -38,6 +39,7 @@ module Rigor
|
|
|
38
39
|
|
|
39
40
|
configuration = load_check_configuration(options)
|
|
40
41
|
configuration = apply_bleeding_edge_override(configuration, options)
|
|
42
|
+
config_warnings = warn_unresolved_config(configuration)
|
|
41
43
|
cache_root = configuration.cache_path
|
|
42
44
|
handle_clear_cache(cache_root) if options.fetch(:clear_cache)
|
|
43
45
|
|
|
@@ -52,7 +54,7 @@ module Rigor
|
|
|
52
54
|
result = apply_baseline_filter(raw_result, configuration, options)
|
|
53
55
|
|
|
54
56
|
coverage = compute_coverage(runner, configuration, options)
|
|
55
|
-
write_result(result, options.fetch(:format), coverage: coverage)
|
|
57
|
+
write_result(result, options.fetch(:format), coverage: coverage, config_warnings: config_warnings)
|
|
56
58
|
emit_ci_detected_output(result, options)
|
|
57
59
|
write_run_stats(result.stats) if result.stats
|
|
58
60
|
write_trace_appendices
|
|
@@ -412,6 +414,26 @@ module Rigor
|
|
|
412
414
|
options
|
|
413
415
|
end
|
|
414
416
|
|
|
417
|
+
# Surfaces the class of mistake where a configured value resolves
|
|
418
|
+
# to nothing — a typo'd or moved `signature_paths:` / bundler /
|
|
419
|
+
# collection path, an unknown `libraries:` name, an inert
|
|
420
|
+
# `disable:` / `severity_overrides:` rule id ({ConfigAudit}). The
|
|
421
|
+
# loader filters each one silently, and the downstream symptom is
|
|
422
|
+
# confusing: missing signatures turn calls into high-confidence
|
|
423
|
+
# `call.undefined-method` firings, and an unrecognised suppression
|
|
424
|
+
# token leaves the rule firing as if the line were never written —
|
|
425
|
+
# so a one-character mistake can read as hundreds of real errors.
|
|
426
|
+
# Each finding is emitted to STDERR (a warning, not a hard error —
|
|
427
|
+
# partial / optional bundles are a valid setup) and the returned
|
|
428
|
+
# list rides into the `--format=json` payload under `config_warnings`
|
|
429
|
+
# so CI and framework consumers can assert on it. The audits only
|
|
430
|
+
# fire on explicit, working-setup-safe signals (see {ConfigAudit}).
|
|
431
|
+
def warn_unresolved_config(configuration)
|
|
432
|
+
warnings = ConfigAudit.warnings(configuration)
|
|
433
|
+
warnings.each { |warning| @err.puts("rigor: #{warning.message}") }
|
|
434
|
+
warnings
|
|
435
|
+
end
|
|
436
|
+
|
|
415
437
|
# ADR-32 WD10 carry-over — wraps `Configuration.load` so the
|
|
416
438
|
# CLI's `--treat-all-as-inline-rbs` flag can inject a
|
|
417
439
|
# `rigor-rbs-inline` plugin entry with
|
|
@@ -671,11 +693,12 @@ module Rigor
|
|
|
671
693
|
format("%.1f MiB", bytes / (1024.0 * 1024.0))
|
|
672
694
|
end
|
|
673
695
|
|
|
674
|
-
def write_result(result, format, coverage: nil)
|
|
696
|
+
def write_result(result, format, coverage: nil, config_warnings: [])
|
|
675
697
|
case format
|
|
676
698
|
when "json"
|
|
677
699
|
payload = enrich_json(result.to_h)
|
|
678
700
|
payload["coverage"] = coverage_payload(coverage) if coverage
|
|
701
|
+
payload["config_warnings"] = config_warnings.map(&:to_h) unless config_warnings.empty?
|
|
679
702
|
@out.puts(JSON.pretty_generate(payload))
|
|
680
703
|
when "text"
|
|
681
704
|
write_text_result(result)
|
|
@@ -776,7 +799,7 @@ module Rigor
|
|
|
776
799
|
end
|
|
777
800
|
|
|
778
801
|
def ci_detected_hint(platform)
|
|
779
|
-
tail = "see `rigor skill
|
|
802
|
+
tail = "see `rigor skill rigor-ci-setup`"
|
|
780
803
|
if platform.native_artifact?
|
|
781
804
|
"rigor: #{platform.name} detected — for the inline report run " \
|
|
782
805
|
"`rigor check --format #{platform.format}` and publish it as the platform's report artifact (#{tail})."
|