rigortype 0.2.1 → 0.2.3
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 +41 -14
- 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 +569 -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 +539 -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 +1 -1
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli/triage_command.rb +8 -2
- data/lib/rigor/cli/triage_renderer.rb +4 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +124 -32
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/triage/catalogue.rb +16 -1
- data/lib/rigor/triage.rb +30 -7
- 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 +90 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Appendix — Coming from TypeScript
|
|
2
|
+
|
|
3
|
+
If your reflex when you see a static type checker is "ah, like
|
|
4
|
+
TypeScript," this appendix maps Rigor's vocabulary onto the
|
|
5
|
+
TypeScript concepts you already know. It is the shortest path
|
|
6
|
+
from "I get TypeScript" to "I get Rigor."
|
|
7
|
+
|
|
8
|
+
This page is not a tutorial. It is a translation table plus a
|
|
9
|
+
short discussion of the places where the two systems make
|
|
10
|
+
different choices, which are the places where your
|
|
11
|
+
TypeScript reflexes will mislead you.
|
|
12
|
+
|
|
13
|
+
## The five-second pitch
|
|
14
|
+
|
|
15
|
+
| Question | TypeScript | Rigor |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Where do annotations live? | In source (`x: number`) | In `.rbs` files alongside `.rb` |
|
|
18
|
+
| Who writes them? | The author of the code | The author OR inference |
|
|
19
|
+
| What is the default? | `any` (TypeScript pre-strict) / `unknown` (strict) | Inferred precisely or `Dynamic[top]` |
|
|
20
|
+
| Identity of types | Structural | Nominal + structural facets |
|
|
21
|
+
| Cost of "I do not know yet" | A red squiggle until annotated | Silence — `Dynamic[top]` produces no diagnostic |
|
|
22
|
+
| When do diagnostics fire? | Whenever a type is unsound | Only when Rigor can **prove** the unsoundness |
|
|
23
|
+
|
|
24
|
+
The two systems share their goal — flag bugs before the program
|
|
25
|
+
runs — and disagree on the path to it. TypeScript prefers
|
|
26
|
+
soundness-first authoring (every value gets a checked type, the
|
|
27
|
+
checker complains until it does). Rigor prefers
|
|
28
|
+
no-false-positives inference (the checker stays silent on
|
|
29
|
+
anything it cannot prove and asks for `.rbs` only where
|
|
30
|
+
inference cannot see further).
|
|
31
|
+
|
|
32
|
+
## Type vocabulary mapping
|
|
33
|
+
|
|
34
|
+
| TypeScript form | Rigor form | Notes |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `string` | `String` | Display drops `Nominal[]`. |
|
|
37
|
+
| `number` | `Integer` / `Float` / `Numeric` | TS conflates int and float; Rigor splits per Ruby's runtime. |
|
|
38
|
+
| `boolean` | `bool` (`Constant<true> \| Constant<false>`) | `bool` is structurally a union of two constants. |
|
|
39
|
+
| `null` | `nil` (`Constant<nil>`) | Ruby has only `nil`; TS distinguishes `null` and `undefined`. |
|
|
40
|
+
| `undefined` | (no analogue) | An unset Ruby local raises `NameError`, not "undefined". |
|
|
41
|
+
| `any` | `Dynamic[top]` | The "be silent here" carrier. |
|
|
42
|
+
| `unknown` | `Top` | Both refuse method dispatch until narrowed; `unknown` is closer to `Top` than to `Dynamic[top]`. |
|
|
43
|
+
| `never` | `Bot` | Empty type — no inhabitants. Used for unreachable branches and `T.absurd` (Sorbet) / `raise`-only bodies. |
|
|
44
|
+
| `void` | `void` | Same idea — caller must not consume the value. |
|
|
45
|
+
| `T \| U` | `T \| U` | Same shape; same display. |
|
|
46
|
+
| `T & U` | `Intersection[T, U]` | Less common in Rigor — refinements often replace it. |
|
|
47
|
+
| `"hello"` (literal type) | `Constant<"hello">` | Direct equivalent. Folding is more aggressive in Rigor. |
|
|
48
|
+
| `42` (literal type) | `Constant<42>` | Same. |
|
|
49
|
+
| `42 \| 43 \| 44` | `Constant<42> \| Constant<43> \| Constant<44>` | Same. |
|
|
50
|
+
| `[number, string]` (tuple) | `Tuple[Integer, String]` | Same per-position model. |
|
|
51
|
+
| `{ name: string; age: number }` | `HashShape{name: String, age: Integer}` | Same per-key model; Ruby uses Symbol keys idiomatically. |
|
|
52
|
+
| `Array<T>` / `T[]` | `Array[T]` | Same. |
|
|
53
|
+
| `Record<K, V>` | `Hash[K, V]` | Same. |
|
|
54
|
+
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
|
|
55
|
+
| `Partial<T>` / `Required<T>` | `partial_of[T]` / `required_of[T]` (same plugin) | Flips every entry's required-ness on a `HashShape`. `Partial` does NOT widen value types to `nil` — Rigor's `HashShape` distinguishes "key absent" from "key present with nil value" (ADR-13 WD on required-ness flips). |
|
|
56
|
+
| `Pick<T, K>` / `Omit<T, K>` | `pick_of[T, K]` / `omit_of[T, K]` (same plugin) | Restrict / remove `HashShape` entries by literal-key union; Tuple receivers project by integer index. Non-shape carriers degrade conservatively and surface `dynamic.shape.lossy-projection`. |
|
|
57
|
+
| Conditional types `T extends U ? A : B` | (none in core; plugin contributions) | A plugin can vary return type by argument shape. |
|
|
58
|
+
| `keyof T` | (none) | `HashShape` exposes its key set internally but not as a type operator. |
|
|
59
|
+
| `T['k']` | `T[k]` indexed access | Rigor supports literal indexed access on `HashShape` and `Tuple` (see the type spec). |
|
|
60
|
+
| Template literal types | `literal-string` carrier | "Provably built from literals" — see Chapter 2. |
|
|
61
|
+
|
|
62
|
+
## Narrowing — the part that feels familiar
|
|
63
|
+
|
|
64
|
+
TypeScript's flow-sensitive narrowing has direct analogues in
|
|
65
|
+
Rigor. The vocabulary is different; the behaviour is the same.
|
|
66
|
+
|
|
67
|
+
| TypeScript | Rigor |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `if (x)` | `if x` — strips `false` / `nil` from the truthy edge |
|
|
70
|
+
| `typeof x === "string"` | `x.is_a?(String)` |
|
|
71
|
+
| `x instanceof Foo` | `x.is_a?(Foo)` |
|
|
72
|
+
| `x === null` | `x.nil?` (and `x == nil`) |
|
|
73
|
+
| `if (x !== null && x !== undefined)` | `if x` (Ruby has only `nil`, no `undefined`) |
|
|
74
|
+
| Discriminated union `switch (x.kind)` | `case x; in {kind: :foo}` or `case x.kind; when :foo` |
|
|
75
|
+
| User-defined type guard `function isFoo(x): x is Foo` | `%a{rigor:v1:predicate-if-true x is Foo}` directive |
|
|
76
|
+
| `as` cast | (no equivalent in code) — Rigor has `T.cast` via `rigor-sorbet`, or `param:` directives |
|
|
77
|
+
| `x!` (non-null assertion) | (no equivalent in code) — `T.must` via `rigor-sorbet`, or `unless x.nil?` narrowing |
|
|
78
|
+
| `as const` | Constants fold automatically — no `as const` needed |
|
|
79
|
+
|
|
80
|
+
The biggest practical difference: in TypeScript, you reach for
|
|
81
|
+
`as Foo` whenever the checker disagrees with you. Rigor does
|
|
82
|
+
not have an in-source cast. The equivalents are:
|
|
83
|
+
|
|
84
|
+
1. **Add a guard.** `unless x.nil?; x.upcase; end` is the
|
|
85
|
+
idiomatic move.
|
|
86
|
+
2. **Tighten an `.rbs`.** Often the underlying issue is a
|
|
87
|
+
library sig that is too loose.
|
|
88
|
+
3. **Use the `rigor-sorbet` plugin.** Adopt `T.let` /
|
|
89
|
+
`T.cast` / `T.must` if you want in-source assertions; see
|
|
90
|
+
Chapter 10.
|
|
91
|
+
|
|
92
|
+
## Refinement carriers — the part that does not exist in TypeScript
|
|
93
|
+
|
|
94
|
+
TypeScript can express "string of length ≥ 1" only through
|
|
95
|
+
template literal types or branded types, and neither composes
|
|
96
|
+
well. Rigor has first-class refinement carriers — a string
|
|
97
|
+
that is provably non-empty, an integer that is provably
|
|
98
|
+
positive, an array that is provably non-empty.
|
|
99
|
+
|
|
100
|
+
| Rigor refinement | TypeScript closest | Comment |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `non-empty-string` | `\`${string}${string}\`` (template literal trick) or branded `NonEmptyString` | Awkward in TS; Rigor produces it from `unless s.empty?` automatically. |
|
|
103
|
+
| `positive-int` | branded `PositiveInt` | TS users tend to skip the brand — Rigor narrows from `n > 0`. |
|
|
104
|
+
| `int<1, 9>` | union of literal types `1 \| 2 \| 3 \| ... \| 9` | Rigor's range carrier handles arbitrary bounds without exploding. |
|
|
105
|
+
| `numeric-string` | (none useful) | TS has no equivalent; Rigor narrows from regex matches against numeric patterns. |
|
|
106
|
+
| `non-empty-array[T]` | `[T, ...T[]]` (tuple-with-rest) | TS has the encoding but few APIs use it; Rigor produces it from `unless arr.empty?`. |
|
|
107
|
+
|
|
108
|
+
If you have ever wished TypeScript had `non-empty-string` as a
|
|
109
|
+
keyword instead of a brand, you will appreciate this part of
|
|
110
|
+
Rigor.
|
|
111
|
+
|
|
112
|
+
## "No annotations needed" in practice
|
|
113
|
+
|
|
114
|
+
Take the canonical TypeScript onboarding example:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
function classify(n: number): "zero" | "positive" | "negative" {
|
|
118
|
+
if (n === 0) return "zero";
|
|
119
|
+
if (n > 0) return "positive";
|
|
120
|
+
return "negative";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = classify(7);
|
|
124
|
+
// TypeScript: result: "zero" | "positive" | "negative"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The Rigor equivalent — no annotations:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
def classify(n)
|
|
131
|
+
return :zero if n.zero?
|
|
132
|
+
return :positive if n.positive?
|
|
133
|
+
:negative
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
result = classify(7)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Both checkers infer the same precise union. The TypeScript
|
|
140
|
+
version requires the parameter type and return type as
|
|
141
|
+
authored annotations; the Rigor version requires neither.
|
|
142
|
+
|
|
143
|
+
When you DO need to write a sig — at module boundaries, when
|
|
144
|
+
the body is too dynamic, when you want to enforce parameter
|
|
145
|
+
shapes — that goes into `sig/<file>.rbs`, not into the `.rb`
|
|
146
|
+
source. That separation is deliberate (see ADR-1 and ADR-5).
|
|
147
|
+
|
|
148
|
+
## Generics
|
|
149
|
+
|
|
150
|
+
TypeScript's generics are central to its standard library;
|
|
151
|
+
Rigor's generics are RBS's, which are more conservative. RBS
|
|
152
|
+
supports class-level type parameters and method-level type
|
|
153
|
+
parameters with bounded constraints, but does not yet support
|
|
154
|
+
inferred call-site instantiation as routinely as TypeScript.
|
|
155
|
+
|
|
156
|
+
| TypeScript | Rigor (via RBS) |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `function id<T>(x: T): T` | `def id: [T] (T) -> T` |
|
|
159
|
+
| `Array<T>` | `Array[T]` |
|
|
160
|
+
| `Map<K, V>` | `Hash[K, V]` |
|
|
161
|
+
| `Promise<T>` | (no analogue — Ruby has no built-in Promise) |
|
|
162
|
+
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
|
|
163
|
+
| Conditional types | (no analogue — would need a plugin) |
|
|
164
|
+
|
|
165
|
+
Rigor reads RBS generics through its dispatcher and instantiates
|
|
166
|
+
parameters at the call site when the receiver carries enough
|
|
167
|
+
information. The display is identical to RBS — `Array[Integer]`
|
|
168
|
+
shows as `Array[Integer]`.
|
|
169
|
+
|
|
170
|
+
## Nullability
|
|
171
|
+
|
|
172
|
+
TypeScript's `strictNullChecks` makes `null` and `undefined`
|
|
173
|
+
their own types. You spell nullable as `T | null | undefined`.
|
|
174
|
+
|
|
175
|
+
Ruby has `nil` and only `nil`. The RBS shorthand is `T?`,
|
|
176
|
+
which expands to `T | nil`. Rigor's narrowing handles `nil`
|
|
177
|
+
exactly the way TypeScript handles `null`:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
def length(s) # s: String? (RBS-declared)
|
|
181
|
+
return 0 if s.nil?
|
|
182
|
+
s.length # s: String — nil stripped by .nil? check
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The TypeScript equivalent reads identically:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
function length(s: string | null): number {
|
|
190
|
+
if (s === null) return 0;
|
|
191
|
+
return s.length;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Severity, suppression, and "strict mode"
|
|
196
|
+
|
|
197
|
+
| TypeScript | Rigor |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `tsconfig.json` `strict: true` | `severity_profile: strict` |
|
|
200
|
+
| `tsconfig.json` `noImplicitAny` | (no analogue — Rigor never demands annotations) |
|
|
201
|
+
| `tsconfig.json` `strictNullChecks` | Always-on in Rigor |
|
|
202
|
+
| `// @ts-ignore` | `# rigor:disable <rule>` |
|
|
203
|
+
| `// @ts-expect-error` | (no analogue today) |
|
|
204
|
+
| `// @ts-nocheck` | `# rigor:disable-file all` |
|
|
205
|
+
| `tsc --noEmit` | `rigor check lib` |
|
|
206
|
+
|
|
207
|
+
## What TypeScript has and Rigor does not
|
|
208
|
+
|
|
209
|
+
Be honest about what you give up:
|
|
210
|
+
|
|
211
|
+
- **Conditional types.** `T extends U ? A : B` has no core
|
|
212
|
+
Rigor analogue. A plugin can vary return type by argument
|
|
213
|
+
shape (see Chapter 9), but you write Ruby code for the
|
|
214
|
+
variation, not type-level expressions.
|
|
215
|
+
- **Mapped types.** `Pick`, `Omit`, `Partial`, `Required`, and
|
|
216
|
+
`Readonly` ship as opt-in plugin-supplied vocabulary via
|
|
217
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/),
|
|
218
|
+
which maps them onto the Rigor-canonical `pick_of` / `omit_of`
|
|
219
|
+
/ `partial_of` / `required_of` / `readonly_of` shape-projection
|
|
220
|
+
type functions on `HashShape` (and `pick_of` / `omit_of` on
|
|
221
|
+
`Tuple`). Template literal manipulation and other mapped-type
|
|
222
|
+
variants (`Uppercase<S>` / `Lowercase<S>` / `Capitalize<S>`)
|
|
223
|
+
remain outside Rigor's surface.
|
|
224
|
+
- **Type-level computation.** TypeScript's type system is
|
|
225
|
+
Turing-complete; Rigor's deliberately is not, so the
|
|
226
|
+
analyzer stays fast on real Ruby projects.
|
|
227
|
+
- **Inferred return type from method body in source.** `tsc`
|
|
228
|
+
infers return types from a function's body and exposes them
|
|
229
|
+
to callers. Rigor does the same for in-source `def`, but
|
|
230
|
+
RBS-declared methods bind their callers to the declared
|
|
231
|
+
return — a deliberate boundary-discipline choice (see ADR-5,
|
|
232
|
+
the robustness principle).
|
|
233
|
+
- **Editor IntelliSense parity.** TypeScript's tooling has 20
|
|
234
|
+
years of investment behind it. Rigor's editor integration is
|
|
235
|
+
younger: `rigor lsp` ships an in-process Language Server
|
|
236
|
+
(diagnostics, hover-to-type, outline, type-aware completion;
|
|
237
|
+
see the manual's editor-integration chapter), but its
|
|
238
|
+
tooling depth still trails TypeScript's.
|
|
239
|
+
|
|
240
|
+
## What Rigor has and TypeScript does not
|
|
241
|
+
|
|
242
|
+
The other direction:
|
|
243
|
+
|
|
244
|
+
- **First-class refinements.** `non-empty-string`,
|
|
245
|
+
`positive-int`, `numeric-string`, etc. — values restricted
|
|
246
|
+
by predicate, narrowed automatically.
|
|
247
|
+
- **Constant folding through method calls.** `"foo".upcase` is
|
|
248
|
+
`Constant<"FOO">`, not just `string`. Rigor catalogues which
|
|
249
|
+
built-in methods are pure and folds through them.
|
|
250
|
+
- **No-false-positives stance.** Rigor stays silent on
|
|
251
|
+
`Dynamic[top]` receivers rather than complaining. You will
|
|
252
|
+
never see a Rigor diagnostic where the right answer is "well,
|
|
253
|
+
technically the checker cannot know."
|
|
254
|
+
- **No annotation tax.** You can run `rigor check` on a Ruby
|
|
255
|
+
project that has zero `.rbs` files and get useful diagnostics
|
|
256
|
+
from inference alone. Adding `.rbs` files is incremental;
|
|
257
|
+
every file you skip is `Dynamic[top]` at the boundary, not a
|
|
258
|
+
diagnostic.
|
|
259
|
+
- **Severity-aware adoption.** TypeScript's "all or nothing"
|
|
260
|
+
feel (you flip `strict` and a thousand errors appear) is
|
|
261
|
+
smoothed by Rigor's `lenient` / `balanced` / `strict`
|
|
262
|
+
profiles plus per-rule overrides plus baseline diffing.
|
|
263
|
+
|
|
264
|
+
## A migration vignette
|
|
265
|
+
|
|
266
|
+
You are porting a TypeScript module to Ruby. The original
|
|
267
|
+
function:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
function pick<K extends keyof T, T extends object>(obj: T, keys: K[]): Pick<T, K> {
|
|
271
|
+
const out = {} as Pick<T, K>;
|
|
272
|
+
for (const k of keys) {
|
|
273
|
+
if (k in obj) out[k] = obj[k];
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The Rigor approach:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
# lib/utils.rb
|
|
283
|
+
def pick(obj, keys)
|
|
284
|
+
keys.each_with_object({}) do |k, out|
|
|
285
|
+
out[k] = obj[k] if obj.key?(k)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```rbs
|
|
291
|
+
# sig/utils.rbs
|
|
292
|
+
def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The RBS sig stays generic. If you want `Pick<T, K>`'s exact-
|
|
296
|
+
key-set tracking back, opt into the
|
|
297
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/)
|
|
298
|
+
plugin and annotate the return type with the `Pick` spelling:
|
|
299
|
+
|
|
300
|
+
```rbs
|
|
301
|
+
# sig/utils.rbs
|
|
302
|
+
%a{rigor:v1:return: Pick[T, K]}
|
|
303
|
+
def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The plugin's `TypeNodeResolver` translates `Pick[T, K]` into
|
|
307
|
+
the canonical `pick_of[T, K]` projection. Either way the call
|
|
308
|
+
site stays precise where it matters: a Hash literal at the
|
|
309
|
+
call site is a `HashShape` regardless of the signature, and
|
|
310
|
+
the per-key types survive through `obj.key?(k)` narrowing.
|
|
311
|
+
|
|
312
|
+
## What's next
|
|
313
|
+
|
|
314
|
+
You probably do not need to read the rest of this appendix
|
|
315
|
+
section sequentially. Three useful pointers:
|
|
316
|
+
|
|
317
|
+
- [Chapter 2 — Everyday types](02-everyday-types.md) for the
|
|
318
|
+
carrier zoo if you have not seen the refinements before.
|
|
319
|
+
- [Chapter 7 — RBS and `RBS::Extended`](07-rbs-and-extended.md)
|
|
320
|
+
for the directive grammar (how you teach Rigor about a
|
|
321
|
+
custom type predicate).
|
|
322
|
+
- [Chapter 10 — Coexisting with Sorbet](10-sorbet.md) if your
|
|
323
|
+
project is in fact already using Sorbet — `T.let`, `T.cast`,
|
|
324
|
+
and `T.must` have direct equivalents and the migration is
|
|
325
|
+
smoother than starting from scratch.
|
|
326
|
+
|
|
327
|
+
If you want to compare against another tool, the sibling
|
|
328
|
+
appendix pages cover [PHPStan](appendix-phpstan.md),
|
|
329
|
+
[mypy](appendix-mypy.md), [Steep](appendix-steep.md),
|
|
330
|
+
[TypeProf](appendix-typeprof.md),
|
|
331
|
+
[Java / C#](appendix-java-csharp.md), [Rust](appendix-rust.md),
|
|
332
|
+
[Go](appendix-go.md), and [Elixir](appendix-elixir.md).
|
data/docs/install.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Install Rigor — instructions for an AI agent
|
|
2
|
+
|
|
3
|
+
These instructions are written for an AI coding agent. Follow each
|
|
4
|
+
step in order. Run the shell commands exactly as shown. If a step
|
|
5
|
+
fails, stop and report the error before continuing.
|
|
6
|
+
|
|
7
|
+
The goal is to install Rigor and hand off to `rigor skill describe`,
|
|
8
|
+
which reports the project's state and routes to the right next-step
|
|
9
|
+
skill (`rigor-project-init` for a project that has never run Rigor).
|
|
10
|
+
**Do not add Rigor to the project's `Gemfile`** — Rigor is a
|
|
11
|
+
standalone tool, not a library.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Step 1 — Detect the environment
|
|
16
|
+
|
|
17
|
+
Run these four checks and note which tools are available:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
which mise # preferred — see Step 2A
|
|
21
|
+
which asdf # fallback — see Step 2B
|
|
22
|
+
ruby --version 2>/dev/null | head -1 # is Ruby 4.0 already on PATH?
|
|
23
|
+
which docker # last resort — see Step 2D
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then proceed to the **first** matching case below.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Step 2 — Install Ruby 4.0 and Rigor
|
|
31
|
+
|
|
32
|
+
### Case A — mise is available (recommended)
|
|
33
|
+
|
|
34
|
+
**What is mise?**
|
|
35
|
+
[mise](https://mise.jdx.dev/) is a runtime and tool version manager
|
|
36
|
+
— think `rbenv` + `nvm` combined, plus a task runner. It installs
|
|
37
|
+
and pins language runtimes (Ruby, Node, Python, …) and tool gems
|
|
38
|
+
(like `rigortype`) per project, recording versions in a `mise.toml`
|
|
39
|
+
that can be committed alongside the code. Other contributors — and CI
|
|
40
|
+
— run `mise install` to restore the exact same versions with no
|
|
41
|
+
`Gemfile` involvement.
|
|
42
|
+
|
|
43
|
+
Run in the project root:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
mise use ruby@4.0
|
|
47
|
+
mise use gem:rigortype
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`mise use` installs the tools and writes their versions to `mise.toml`
|
|
51
|
+
in one step. Commit `mise.toml` so the version is shared.
|
|
52
|
+
|
|
53
|
+
Then verify:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
rigor --version
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If `rigor` is not found, mise may not be wired into your shell yet.
|
|
60
|
+
Run one of:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
# Interactive shells (add to ~/.zshrc / ~/.bashrc permanently):
|
|
64
|
+
eval "$(mise activate zsh)" # or bash / fish
|
|
65
|
+
|
|
66
|
+
# Or use the shims directory directly:
|
|
67
|
+
export PATH="$HOME/.local/share/mise/shims:$PATH"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then re-run `rigor --version`. If it still fails, run
|
|
71
|
+
`mise exec gem:rigortype -- rigor --version` as a one-off check.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Case B — asdf is available
|
|
76
|
+
|
|
77
|
+
`asdf` follows the same model as mise but has no gem backend, so the
|
|
78
|
+
gem is installed with `gem install` after setting the Ruby version.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
asdf install ruby latest:4.0
|
|
82
|
+
asdf local ruby latest:4.0
|
|
83
|
+
gem install rigortype
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Verify:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
rigor --version
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Note: unlike mise, `gem install` here does not pin the version in a
|
|
93
|
+
project config file. Consider switching to mise for per-project
|
|
94
|
+
pinning; see <https://mise.jdx.dev/getting-started.html>.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### Case C — Ruby 4.0 is already on PATH
|
|
99
|
+
|
|
100
|
+
If `ruby --version` reports `ruby 4.0.*`, install the gem directly:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
gem install rigortype
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Verify:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
rigor --version
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
No version is pinned per project with this approach; a version manager
|
|
113
|
+
(Case A or B) is better for shared codebases.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Case D — mise is not installed yet (no ruby, no asdf)
|
|
118
|
+
|
|
119
|
+
Install mise first by following the official getting-started guide:
|
|
120
|
+
<https://mise.jdx.dev/getting-started.html>
|
|
121
|
+
|
|
122
|
+
The one-line script from that page is:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
curl https://mise.run | sh
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
After mise is installed and activated in your shell, come back and
|
|
129
|
+
follow **Case A**.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### Last resort — Docker
|
|
134
|
+
|
|
135
|
+
If none of the above is workable in your environment, you can run
|
|
136
|
+
Rigor inside a Docker container:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
docker run --rm -v "$(pwd):/app" -w /app ghcr.io/rigortype/rigor:latest rigor check
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This approach adds overhead from the container filesystem boundary
|
|
143
|
+
and does not integrate well with editor LSP. Use it only when a
|
|
144
|
+
host-side Ruby 4.0 is genuinely unavailable (for example, Windows
|
|
145
|
+
without WSL). For all other environments, prefer Case A–D above.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Step 3 — Verify the installation
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
rigor --version
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A version string like `rigor 0.1.x` confirms a successful install.
|
|
156
|
+
If the command is not found, revisit Step 2 for your case.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Step 4 — Ask Rigor what to do next
|
|
161
|
+
|
|
162
|
+
Now that `rigor` is on your PATH, let it pick the next step for this
|
|
163
|
+
project:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
rigor skill describe
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This reports the project's current state (config / baseline / `sig/` /
|
|
170
|
+
CI), recommends the next skill to run with a reason, and lists every
|
|
171
|
+
skill with its current description. Follow the `## Recommended next
|
|
172
|
+
step` it prints — for a project with no `.rigor.yml` yet that is
|
|
173
|
+
`rigor-project-init`:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
rigor skill rigor-project-init
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`rigor skill <name>` prints the SKILL definition — a header with
|
|
180
|
+
file paths followed by the skill body. Follow it from top to bottom.
|
|
181
|
+
The project-init skill detects your project's stack, proposes plugins,
|
|
182
|
+
writes `.rigor.dist.yml`, and snapshots a baseline if needed; once the
|
|
183
|
+
project is set up, re-run `rigor skill describe` for the step after
|
|
184
|
+
that.
|
|
185
|
+
|
|
186
|
+
If `rigor skill describe` is not recognised, your Rigor version predates
|
|
187
|
+
it. Run `rigor --version` and upgrade with `mise use gem:rigortype` (or
|
|
188
|
+
`gem update rigortype` for Case C/B); on an older version, run
|
|
189
|
+
`rigor skill rigor-project-init` directly.
|
data/docs/llms.txt
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Rigor — offline doc index
|
|
2
|
+
|
|
3
|
+
> Rigor is a Ruby static analyzer that reports real bugs — undefined methods on
|
|
4
|
+
> typed receivers, wrong argument counts, provable nil dereferences — from
|
|
5
|
+
> ordinary Ruby with no type annotations in source. Every type is inferred from
|
|
6
|
+
> the values your code actually produces.
|
|
7
|
+
|
|
8
|
+
This is the **offline** doc index served by an installed Rigor. You do not need
|
|
9
|
+
the network: read any page below with `rigor docs <name>`, and any bundled Agent
|
|
10
|
+
Skill with `rigor skill <name>`. (The canonical web copy is
|
|
11
|
+
<https://rigor.typedduck.fail/llms.txt>.)
|
|
12
|
+
|
|
13
|
+
## Start here (AI coding agents)
|
|
14
|
+
|
|
15
|
+
- **Run the `rigor-next-steps` skill** — the single entry point for "what should
|
|
16
|
+
we do next with Rigor on this project?". It resolves the `rigor` command,
|
|
17
|
+
onboards an unconfigured project, then asks `rigor skill describe` for the next
|
|
18
|
+
step and routes to the matching skill. `rigor skill rigor-next-steps`.
|
|
19
|
+
- `rigor skill describe` (alias `rigor describe`) — probe the project's state and
|
|
20
|
+
recommend the next skill. The driving command behind `rigor-next-steps`.
|
|
21
|
+
- `rigor skill --list` — every bundled skill; `rigor skill <name>` — its body.
|
|
22
|
+
- The full workflow narrative: `rigor docs 17-driving-improvement`.
|
|
23
|
+
|
|
24
|
+
## Manual (read with `rigor docs <name>`)
|
|
25
|
+
|
|
26
|
+
- `install` — install Rigor (mise / asdf / gem). The one doc that precedes a
|
|
27
|
+
local `rigor`, so an agent reads it from the web until Rigor is installed.
|
|
28
|
+
- `01-installation` — installation channels and the tool-not-a-library rule.
|
|
29
|
+
- `02-cli-reference` — every command and flag.
|
|
30
|
+
- `03-configuration` — `.rigor.yml` keys, discovery, `includes:` layering.
|
|
31
|
+
- `04-diagnostics` — the rule-ID catalogue and severity profiles.
|
|
32
|
+
- `05-inspecting-types` — `rigor annotate` / `rigor type-of`, `assert_type`.
|
|
33
|
+
- `06-baseline` — `.rigor-baseline.yml` and the `rigor baseline` subcommands.
|
|
34
|
+
- `07-plugins` — activating framework / gem plugins via `plugins:`.
|
|
35
|
+
- `08-skills` — the bundled Agent Skills (the catalogue this index links to).
|
|
36
|
+
- `09-editor-integration` — wiring `rigor lsp` into Neovim / VS Code / Helix / Emacs.
|
|
37
|
+
- `10-mcp-server` — exposing Rigor to AI agents via `rigor mcp`.
|
|
38
|
+
- `11-ci` — running Rigor in CI with inline PR/MR diagnostics.
|
|
39
|
+
- `12-caching` — where the cache lives and what invalidates it.
|
|
40
|
+
- `13-troubleshooting` — common problems and fixes.
|
|
41
|
+
- `14-rails-quickstart` — the fast path for Rails apps.
|
|
42
|
+
- `15-type-protection-coverage` — `rigor coverage --protection`.
|
|
43
|
+
- `16-rbs-extended-annotations` — the `%a{rigor:v1:…}` annotations.
|
|
44
|
+
- `17-driving-improvement` — the `rigor-next-steps`-driven improvement loop.
|
|
45
|
+
|
|
46
|
+
## Handbook (read with `rigor docs <name>`)
|
|
47
|
+
|
|
48
|
+
The end-user walkthrough of the type model — concepts rather than operations.
|
|
49
|
+
List them with `rigor docs --list handbook`; a chapter whose short name also
|
|
50
|
+
exists in the manual (`plugins`) needs its qualified path (`handbook/09-plugins`).
|
|
51
|
+
|
|
52
|
+
- `01-getting-started` — first run, what Rigor infers with no annotations.
|
|
53
|
+
- `02-everyday-types` — the carrier zoo (literals, unions, nil, Dynamic).
|
|
54
|
+
- `03-narrowing` — control-flow narrowing and certainty.
|
|
55
|
+
- `04-tuples-and-shapes` — tuples, hash shapes, object shapes.
|
|
56
|
+
- `05-methods-and-blocks` — method signatures, blocks, yields.
|
|
57
|
+
- `06-classes` — classes, ivars, inheritance, structural interfaces.
|
|
58
|
+
- `07-rbs-and-extended` — RBS, inline RBS, `%a{rigor:v1:…}` annotations.
|
|
59
|
+
- `08-understanding-errors` — reading diagnostics and why each fires.
|
|
60
|
+
- `handbook/09-plugins` — what plugins add to inference.
|
|
61
|
+
- `10-sorbet` — interoperating with Sorbet signatures.
|
|
62
|
+
- `11-sig-gen` — generating RBS with `rigor sig-gen`.
|
|
63
|
+
- `12-lightweight-hkt` — the `App[F, A]` lightweight higher-kinded encoding.
|
|
64
|
+
- Appendices (`rigor docs --list handbook`) compare Rigor to TypeScript, mypy,
|
|
65
|
+
PHPStan, Steep, TypeProf, Go, Java/C#, Rust, Elixir, and type theory.
|
|
66
|
+
|
|
67
|
+
## More (web only)
|
|
68
|
+
|
|
69
|
+
The contributor-facing corpus is not bundled in the gem; read it on the site:
|
|
70
|
+
the type specification, internal specification, architecture decisions (ADRs),
|
|
71
|
+
design notes, and development notes are linked from
|
|
72
|
+
<https://rigor.typedduck.fail/llms.txt>.
|