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,226 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Rigor-side supplement to the rbs gem's
|
|
3
|
+
# `core/rubygems/*.rbs` declarations.
|
|
4
|
+
#
|
|
5
|
+
# Re-opens the rubygems classes / Gem module to ADD the
|
|
6
|
+
# declarations the upstream RBS omits. Every block below adds
|
|
7
|
+
# new method signatures only — it does NOT redeclare anything
|
|
8
|
+
# the upstream RBS already covers, so the loader does not raise
|
|
9
|
+
# `RBS::DuplicatedDeclarationError`.
|
|
10
|
+
#
|
|
11
|
+
# Driven by the `references/ruby/lib` survey
|
|
12
|
+
# (`docs/CURRENT_WORK.md` § "Queued engine items") which
|
|
13
|
+
# clusters `undefined-method` on Gem-side selectors most-touched
|
|
14
|
+
# by rubygems' own source tree and by bundler. Returns are
|
|
15
|
+
# `untyped` where Rigor cannot infer a precise shape — the goal
|
|
16
|
+
# is to silence false positives, not to author precise sigs.
|
|
17
|
+
#
|
|
18
|
+
module Gem
|
|
19
|
+
# Singleton additions surfacing in the survey but absent from
|
|
20
|
+
# the upstream `core/rubygems/rubygems.rbs`. All are real
|
|
21
|
+
# methods defined in `lib/rubygems/defaults.rb`,
|
|
22
|
+
# `lib/rubygems.rb`, or the platform-specific override files.
|
|
23
|
+
def self.target_rbconfig: () -> untyped
|
|
24
|
+
def self.set_target_rbconfig: (untyped) -> untyped
|
|
25
|
+
def self.load_safe_marshal: () -> untyped
|
|
26
|
+
def self.safe_load_marshal: (untyped) -> untyped
|
|
27
|
+
def self.verbose: () -> bool
|
|
28
|
+
def self.really_verbose: () -> bool
|
|
29
|
+
def self.discover_gems_on_require: () -> bool
|
|
30
|
+
def self.discover_gems_on_require=: (bool) -> bool
|
|
31
|
+
def self.default_user_install: () -> bool
|
|
32
|
+
def self.dynamic_library_suffixes: () -> Array[String]
|
|
33
|
+
def self.extension_api_version: () -> String
|
|
34
|
+
def self.find_default_spec: (untyped) -> untyped?
|
|
35
|
+
def self.install_extension_in_lib: () -> bool
|
|
36
|
+
def self.load_bundler_extensions: () -> untyped
|
|
37
|
+
def self.load_plugin_files: (?untyped plugins) -> untyped
|
|
38
|
+
def self.open_file: (String path, String mode) { (IO) -> untyped } -> untyped
|
|
39
|
+
def self.open_file_with_lock: (String path) { (IO) -> untyped } -> untyped
|
|
40
|
+
def self.state_file: () -> String
|
|
41
|
+
def self.vendor_dir: () -> String
|
|
42
|
+
def self.activate_bin_path: (String name, ?String exec_name, ?untyped requirements) -> String
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Gem::Platform
|
|
46
|
+
# `Gem::Platform.new(arg)` is the canonical constructor; the
|
|
47
|
+
# upstream `core/rubygems/platform.rbs` declares the class
|
|
48
|
+
# but no `initialize`, which forces RBS to default to
|
|
49
|
+
# `(): void` and the wrong-arity rule fires for every call
|
|
50
|
+
# site. The accessor / predicate set covers the most-used
|
|
51
|
+
# surface bundler and rubygems internals touch.
|
|
52
|
+
def initialize: (*untyped) -> void
|
|
53
|
+
attr_accessor cpu: String?
|
|
54
|
+
attr_accessor os: String?
|
|
55
|
+
attr_accessor version: String?
|
|
56
|
+
def to_s: () -> String
|
|
57
|
+
def ==: (untyped) -> bool
|
|
58
|
+
def ===: (untyped) -> bool
|
|
59
|
+
def =~: (untyped) -> bool?
|
|
60
|
+
def eql?: (untyped) -> bool
|
|
61
|
+
def hash: () -> Integer
|
|
62
|
+
def match_spec?: (untyped) -> bool
|
|
63
|
+
def match_gem?: (untyped, untyped) -> bool
|
|
64
|
+
|
|
65
|
+
def self.local: () -> Platform
|
|
66
|
+
def self.match_spec?: (untyped) -> bool
|
|
67
|
+
def self.match_gem?: (untyped, untyped) -> bool
|
|
68
|
+
def self.installable?: (untyped) -> bool
|
|
69
|
+
def self.sort_priority: (untyped) -> Integer
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Gem::Dependency
|
|
73
|
+
# `Gem::Dependency.new(name, *requirements, type: :runtime)`.
|
|
74
|
+
# Upstream RBS declares the class shell but no constructor; a
|
|
75
|
+
# permissive splat keeps the canonical and the
|
|
76
|
+
# `(name, requirements, type)` invocation forms both silent.
|
|
77
|
+
def initialize: (*untyped) -> void
|
|
78
|
+
attr_accessor name: String
|
|
79
|
+
attr_accessor requirement: Gem::Requirement
|
|
80
|
+
attr_accessor type: Symbol
|
|
81
|
+
attr_accessor prerelease: bool
|
|
82
|
+
def matches_spec?: (untyped) -> bool
|
|
83
|
+
def match?: (*untyped) -> bool
|
|
84
|
+
def to_s: () -> String
|
|
85
|
+
def ==: (untyped) -> bool
|
|
86
|
+
def eql?: (untyped) -> bool
|
|
87
|
+
def hash: () -> Integer
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class Gem::Specification
|
|
91
|
+
def self.find_all_by_name: (String name, *untyped requirements) -> Array[Gem::Specification]
|
|
92
|
+
def self.stubs: () -> Array[untyped]
|
|
93
|
+
def self.default_stubs: (?String pattern) -> Array[untyped]
|
|
94
|
+
def self.reset: () -> void
|
|
95
|
+
def self.from_yaml: (untyped) -> Gem::Specification
|
|
96
|
+
def self.load: (String path) -> Gem::Specification?
|
|
97
|
+
def self._all: () -> Array[Gem::Specification]
|
|
98
|
+
def self.each: () { (Gem::Specification) -> untyped } -> void
|
|
99
|
+
|
|
100
|
+
# Instance-side surface widely used by bundler / rubygems
|
|
101
|
+
# internals. `attr_accessor` mirrors the writer + reader pair
|
|
102
|
+
# so the survey's `.name=` / `.version=` / `.full_gem_path=` /
|
|
103
|
+
# `.loaded_from=` / `.post_install_message=` writes resolve too.
|
|
104
|
+
def activate: () -> untyped
|
|
105
|
+
def specification_version: () -> Integer
|
|
106
|
+
def files: () -> Array[String]
|
|
107
|
+
def default_gem?: () -> bool
|
|
108
|
+
def spec_file: () -> String
|
|
109
|
+
def runtime_dependencies: () -> Array[Gem::Dependency]
|
|
110
|
+
def required_rubygems_version: () -> Gem::Requirement
|
|
111
|
+
def required_ruby_version: () -> Gem::Requirement
|
|
112
|
+
def require_paths: () -> Array[String]
|
|
113
|
+
attr_accessor name: String
|
|
114
|
+
attr_accessor version: Gem::Version
|
|
115
|
+
attr_accessor full_gem_path: String
|
|
116
|
+
attr_accessor loaded_from: String?
|
|
117
|
+
attr_accessor post_install_message: String?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class Gem::BasicSpecification
|
|
121
|
+
def version: () -> Gem::Version
|
|
122
|
+
def executables: () -> Array[String]
|
|
123
|
+
def name: () -> String
|
|
124
|
+
def full_name: () -> String
|
|
125
|
+
def gem_dir: () -> String
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class Gem::Version
|
|
129
|
+
def segments: () -> Array[untyped]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class Gem::Requirement
|
|
133
|
+
def requirements: () -> Array[untyped]
|
|
134
|
+
def self.create: (*untyped) -> Gem::Requirement
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class Gem::ConfigFile
|
|
138
|
+
def []: (untyped) -> untyped
|
|
139
|
+
def []=: (untyped, untyped) -> untyped
|
|
140
|
+
def write: () -> void
|
|
141
|
+
def verbose: () -> bool
|
|
142
|
+
def verbose=: (bool) -> bool
|
|
143
|
+
def really_verbose: () -> bool
|
|
144
|
+
def ssl_ca_cert: () -> String?
|
|
145
|
+
def ssl_client_cert: () -> String?
|
|
146
|
+
def ssl_verify_mode: () -> Integer?
|
|
147
|
+
def credentials_path: () -> String
|
|
148
|
+
def api_keys: () -> Hash[String, String]
|
|
149
|
+
def cert_expiration_length_days: () -> Integer
|
|
150
|
+
def sources: () -> Array[String]
|
|
151
|
+
def update_sources=: (bool) -> bool
|
|
152
|
+
def unset_api_key!: () -> bool
|
|
153
|
+
def state_file_writable?: () -> bool
|
|
154
|
+
def set_api_key: (untyped host, untyped key) -> untyped
|
|
155
|
+
def rubygems_api_key: () -> String?
|
|
156
|
+
def rubygems_api_key=: (String?) -> String?
|
|
157
|
+
def last_update_check: () -> Integer
|
|
158
|
+
def last_update_check=: (Integer) -> Integer
|
|
159
|
+
def ipv4_fallback_enabled: () -> bool
|
|
160
|
+
def install_extension_in_lib: () -> bool
|
|
161
|
+
def each: () { ([String, untyped]) -> untyped } -> void
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class Gem::LoadError < LoadError
|
|
165
|
+
attr_accessor name: String
|
|
166
|
+
attr_accessor requirement: Gem::Requirement
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
class Gem::SourceList
|
|
170
|
+
include Enumerable[untyped]
|
|
171
|
+
def initialize: () -> void
|
|
172
|
+
def include?: (untyped) -> bool
|
|
173
|
+
def each: () { (untyped) -> untyped } -> void
|
|
174
|
+
def to_a: () -> Array[untyped]
|
|
175
|
+
def from: (untyped) -> SourceList
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class Gem::RequestSet
|
|
179
|
+
def initialize: (*untyped) -> void
|
|
180
|
+
def import: (untyped) -> untyped
|
|
181
|
+
def source_set: () -> untyped
|
|
182
|
+
def installed_gems: () -> Hash[String, untyped]
|
|
183
|
+
def install: (?untyped options) { (?untyped) -> untyped } -> untyped
|
|
184
|
+
def install_from_gemdeps: (untyped options) { (?untyped) -> untyped } -> untyped
|
|
185
|
+
def resolve: (?untyped) -> untyped
|
|
186
|
+
def resolve_current: () -> untyped
|
|
187
|
+
def resolve_dependencies: () -> untyped
|
|
188
|
+
def errors: () -> Array[untyped]
|
|
189
|
+
attr_accessor remote: bool
|
|
190
|
+
attr_accessor development: bool
|
|
191
|
+
attr_accessor development_shallow: bool
|
|
192
|
+
attr_accessor ignore_dependencies: bool
|
|
193
|
+
attr_accessor prerelease: bool
|
|
194
|
+
attr_accessor soft_missing: bool
|
|
195
|
+
attr_accessor without_groups: Array[Symbol]
|
|
196
|
+
attr_accessor always_install: untyped
|
|
197
|
+
attr_accessor installing: bool
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
class Gem::DependencyInstaller
|
|
201
|
+
def initialize: (*untyped) -> void
|
|
202
|
+
def install: (untyped, ?untyped) -> Array[untyped]
|
|
203
|
+
def installed_gems: () -> Array[untyped]
|
|
204
|
+
def errors: () -> Array[untyped]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class Gem::Uninstaller
|
|
208
|
+
def initialize: (*untyped) -> void
|
|
209
|
+
def uninstall: () -> void
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
class Gem::PathSupport
|
|
213
|
+
def initialize: (*untyped) -> void
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
class Gem::Installer
|
|
217
|
+
def initialize: (*untyped) -> void
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class Gem::DependencyInstaller
|
|
221
|
+
def initialize: (*untyped) -> void
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class Gem::MissingSpecError < StandardError
|
|
225
|
+
def initialize: (*untyped) -> void
|
|
226
|
+
end
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
By the end of this chapter you will be able to:
|
|
4
|
+
|
|
5
|
+
- get `rigor` onto your `PATH` (the fast AI-assisted way, or
|
|
6
|
+
by hand);
|
|
7
|
+
- run `rigor check` and read the diagnostics it prints;
|
|
8
|
+
- understand the "no annotations needed" stance that sets
|
|
9
|
+
Rigor apart from most checkers, and the escape hatches for
|
|
10
|
+
when inference falls short.
|
|
11
|
+
|
|
12
|
+
It is the only chapter you must read top to bottom. The rest
|
|
13
|
+
of the handbook is reference you can dip into later.
|
|
14
|
+
|
|
15
|
+
## Installing Rigor
|
|
16
|
+
|
|
17
|
+
Rigor is a tool, not a library — like a linter or a compiler, it
|
|
18
|
+
analyses your project but is not part of its runtime. **Do not add
|
|
19
|
+
it to your application's `Gemfile`.** Install it on its own and
|
|
20
|
+
point it at your project.
|
|
21
|
+
|
|
22
|
+
Rigor itself runs on Ruby 4.0, independently of the Ruby your own
|
|
23
|
+
code targets. The [`target_ruby:` config key](#the-config-file)
|
|
24
|
+
tells Rigor which Ruby *your* project runs.
|
|
25
|
+
|
|
26
|
+
### The fast path: let an AI agent set it up
|
|
27
|
+
|
|
28
|
+
If you work with an AI coding agent (Claude Code or any assistant
|
|
29
|
+
that supports [Agent Skills](https://agentskills.io/)), hand it
|
|
30
|
+
this prompt:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Install Rigor in this project by following the instructions at
|
|
34
|
+
https://raw.githubusercontent.com/rigortype/rigor/refs/heads/master/docs/install.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The agent detects your environment, installs Rigor, then runs the
|
|
38
|
+
**`rigor-project-init` skill** — which walks your `Gemfile`,
|
|
39
|
+
proposes a plugin set matched to your framework, picks an adoption
|
|
40
|
+
mode, and writes `.rigor.dist.yml` for you. No manual YAML editing.
|
|
41
|
+
This is the recommended path; the [config file](#the-config-file)
|
|
42
|
+
section below shows what the skill produces so you can read and
|
|
43
|
+
tweak it afterwards.
|
|
44
|
+
|
|
45
|
+
The same prompt is available in sixteen languages in the
|
|
46
|
+
[Rails quickstart](../manual/14-rails-quickstart.md#step-1--install-ruby-40-and-rigor-common-to-both-paths).
|
|
47
|
+
|
|
48
|
+
### The manual path: mise
|
|
49
|
+
|
|
50
|
+
If you prefer to drive the setup yourself, the recommended runtime
|
|
51
|
+
version manager is [`mise`](https://mise.jdx.dev/), which
|
|
52
|
+
provisions both Ruby 4.0 and Rigor:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
mise use ruby@4.0
|
|
56
|
+
mise use gem:rigortype
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With mise activated in your shell, `rigor` is on your `PATH`. The
|
|
60
|
+
gem is named `rigortype` (the name `rigor` was taken on RubyGems);
|
|
61
|
+
the executable it installs is `rigor`. If you already have Ruby
|
|
62
|
+
4.0, `gem install rigortype` works too.
|
|
63
|
+
|
|
64
|
+
For shell activation and shims, `asdf`, and developing inside a
|
|
65
|
+
container, see [Installing Rigor](../manual/01-installation.md);
|
|
66
|
+
for continuous integration, see
|
|
67
|
+
[Running Rigor in CI](../manual/11-ci.md).
|
|
68
|
+
|
|
69
|
+
## What does `rigor check` look at?
|
|
70
|
+
|
|
71
|
+
Rigor reads your `.rb` files, runs a flow-sensitive type
|
|
72
|
+
inference engine over each one, consults any `sig/*.rbs`
|
|
73
|
+
declarations available to your project, and reports a small
|
|
74
|
+
catalogue of bugs:
|
|
75
|
+
|
|
76
|
+
- methods called on the wrong receiver class;
|
|
77
|
+
- methods called with the wrong number of arguments;
|
|
78
|
+
- arithmetic that can be proved to raise (`5 / 0`);
|
|
79
|
+
- arguments whose type does not satisfy a refined parameter
|
|
80
|
+
contract;
|
|
81
|
+
- a few more, all listed in
|
|
82
|
+
[Chapter 8 — Understanding errors](08-understanding-errors.md).
|
|
83
|
+
|
|
84
|
+
Rigor does **not** ask you to write type annotations in
|
|
85
|
+
your Ruby source. It infers as much as it can, and stays
|
|
86
|
+
silent everywhere it cannot prove a narrower type.
|
|
87
|
+
A diagnostic only fires when Rigor has enough static
|
|
88
|
+
information to be confident.
|
|
89
|
+
|
|
90
|
+
## The smallest working session
|
|
91
|
+
|
|
92
|
+
Drop into your project root and run:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
rigor check lib
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
That walks every `.rb` under `lib/`. When the analyzer finds
|
|
99
|
+
nothing to complain about, it prints `No diagnostics` and
|
|
100
|
+
exits `0`:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
No diagnostics
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
When it does find something, each diagnostic is one line. Given
|
|
107
|
+
this file:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# lib/demo.rb
|
|
111
|
+
"hello".no_such_method # typo'd method name
|
|
112
|
+
[1, 2, 3].rotate(1, 2) # too many arguments
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`rigor check lib/demo.rb` prints:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
lib/demo.rb:1:9: error: undefined method `no_such_method' for "hello" [call.undefined-method]
|
|
119
|
+
lib/demo.rb:2:11: error: wrong number of arguments to `rotate' on Array (given 2, expected 0..1) [call.wrong-arity]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
To run on a single file, pass the file instead of a directory:
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
rigor check path/to/file.rb
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
And to ask what Rigor inferred at one precise position:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
rigor type-of lib/foo.rb:10:5
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
That prints both the rich Rigor type and the conservative
|
|
135
|
+
RBS erasure (the type a non-Rigor RBS tool would see). It is
|
|
136
|
+
the fastest way to ask "what does Rigor think this expression
|
|
137
|
+
produces?"
|
|
138
|
+
|
|
139
|
+
## Reading a diagnostic
|
|
140
|
+
|
|
141
|
+
Take the first line from the run above:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
lib/demo.rb:1:9: error: undefined method `no_such_method' for "hello" [call.undefined-method]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Slice | Meaning |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `lib/demo.rb:1:9` | File, 1-indexed line, 1-indexed column |
|
|
150
|
+
| `error` | Severity (`error` / `warning` / `info`) |
|
|
151
|
+
| `undefined method ...` | Human-readable message |
|
|
152
|
+
| `[call.undefined-method]` | The qualified rule identifier |
|
|
153
|
+
|
|
154
|
+
The qualified rule identifier is the handle you use to silence,
|
|
155
|
+
demote, or look up a rule. The quickest is an in-source comment
|
|
156
|
+
on the offending line:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
"hello".no_such_method # rigor:disable call.undefined-method
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The same identifier also drives the `disable:` and
|
|
163
|
+
`severity_overrides:` config keys, and family wildcards work
|
|
164
|
+
(`# rigor:disable call` suppresses every `call.*` rule on that
|
|
165
|
+
line). The full list of families and rules, and when to reach
|
|
166
|
+
for each suppression mechanism, is in
|
|
167
|
+
[Chapter 8 — Understanding errors](08-understanding-errors.md).
|
|
168
|
+
|
|
169
|
+
## The "no annotations" stance
|
|
170
|
+
|
|
171
|
+
Most static checkers ask the user to annotate types. Rigor
|
|
172
|
+
does the opposite: it looks at what your Ruby code does and
|
|
173
|
+
**proves** types from the values themselves. Three quick
|
|
174
|
+
examples:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
n = 100
|
|
178
|
+
m = n + 1
|
|
179
|
+
assert_type("101", m) # arithmetic folds
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
def kind(x)
|
|
184
|
+
case x
|
|
185
|
+
when Integer then :int
|
|
186
|
+
when String then :str
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
assert_type(":int | :str | nil", kind(7)) # union of all case branches
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
greeting = "Hello, " # Constant<"Hello, ">
|
|
194
|
+
name = ARGV.first # String? (RBS-declared)
|
|
195
|
+
hello = "#{greeting}#{name}!" # literal-string carrier:
|
|
196
|
+
# every interpolated part
|
|
197
|
+
# is itself literal-string-
|
|
198
|
+
# compatible, so the result
|
|
199
|
+
# is "provably source-derived"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
You did not write a single annotation. Rigor reasons about the
|
|
203
|
+
values directly.
|
|
204
|
+
|
|
205
|
+
> The `assert_type(...)` line is Rigor's introspection helper,
|
|
206
|
+
> not a runtime check — it pins the inferred type at that point
|
|
207
|
+
> so you can compare the prose to the analyzer's actual output.
|
|
208
|
+
> See [How to read this handbook](README.md#how-to-read-this-handbook)
|
|
209
|
+
> for the full snippet convention.
|
|
210
|
+
|
|
211
|
+
When inference cannot prove a narrower type, the engine
|
|
212
|
+
returns `Dynamic[top]` (the gradual carrier — "could be any
|
|
213
|
+
Ruby value") and stays silent. Rigor never invents
|
|
214
|
+
diagnostics it cannot prove.
|
|
215
|
+
|
|
216
|
+
## When inference is not enough
|
|
217
|
+
|
|
218
|
+
*First read? Skip this section.* Out of the box, inference plus
|
|
219
|
+
any RBS your gems already ship covers most code, and the next
|
|
220
|
+
chapters teach you to read what it produces. Come back here when
|
|
221
|
+
Rigor resolves something to `Dynamic[top]` that you wish it knew
|
|
222
|
+
more about. For most projects only escape hatches (1) and (2)
|
|
223
|
+
ever come up.
|
|
224
|
+
|
|
225
|
+
There are five escape hatches, in rough order of how often you
|
|
226
|
+
will need them:
|
|
227
|
+
|
|
228
|
+
1. **Add an `.rbs` file.** Drop a signature into `sig/` and
|
|
229
|
+
Rigor picks it up automatically. This is the most common
|
|
230
|
+
reason inference does not see further than the local
|
|
231
|
+
`def` — by default the analyzer treats every external gem
|
|
232
|
+
as `Dynamic[top]` unless the gem ships RBS or you
|
|
233
|
+
opt in to gem-source inference per (4) below.
|
|
234
|
+
2. **Tighten an existing RBS sig with `RBS::Extended`.** Add
|
|
235
|
+
a `%a{rigor:v1:return: non-empty-string}` annotation
|
|
236
|
+
above the method's `def ... -> ::String` line. Rigor
|
|
237
|
+
sees the refinement; ordinary RBS tools see a comment.
|
|
238
|
+
3. **Write a plugin.** When your project has a domain DSL
|
|
239
|
+
(`Lisp.eval`, `100.kilometers`, `transition_to(:foo)`)
|
|
240
|
+
that no general-purpose analyzer can know about, a
|
|
241
|
+
plugin teaches Rigor about it.
|
|
242
|
+
4. **Opt in to gem-source inference.** When a no-RBS gem's
|
|
243
|
+
methods would otherwise resolve to `Dynamic[top]`, list
|
|
244
|
+
the gem under `dependencies.source_inference:` in
|
|
245
|
+
`.rigor.dist.yml` and Rigor will walk its `lib/` the same way
|
|
246
|
+
it walks project source. Returns are wrapped in
|
|
247
|
+
`Dynamic[T]` so the call site retains the provenance.
|
|
248
|
+
See [ADR-10](../adr/10-dependency-source-inference.md)
|
|
249
|
+
for the trade-offs (per-gem opt-in by design — broad
|
|
250
|
+
defaults would inflate budgets and make `bundle update`
|
|
251
|
+
noisy).
|
|
252
|
+
5. **Use the `rigor-sorbet` adapter.** If your project
|
|
253
|
+
already uses [Sorbet](https://sorbet.org/), Rigor can
|
|
254
|
+
read your existing `sig { ... }` blocks, RBI files, and
|
|
255
|
+
`T.let` / `T.cast` / `T.must` / `T.unsafe` assertions
|
|
256
|
+
as type sources without rewriting anything. See
|
|
257
|
+
[Chapter 10](10-sorbet.md) for the migration patterns
|
|
258
|
+
and the translation table.
|
|
259
|
+
|
|
260
|
+
Chapters 7 and 9 cover (1)–(3) in detail; chapter 10 covers
|
|
261
|
+
(5).
|
|
262
|
+
|
|
263
|
+
## The config file
|
|
264
|
+
|
|
265
|
+
The minimum useful run needs no config file at all —
|
|
266
|
+
`rigor check lib` works out of the box. A config file is for
|
|
267
|
+
non-default behaviours: extra `paths`, an alternative
|
|
268
|
+
`severity_profile`, project-wide rule disables, plugins.
|
|
269
|
+
|
|
270
|
+
If you used the [AI-assisted setup](#the-fast-path-let-an-ai-agent-set-it-up),
|
|
271
|
+
the `rigor-project-init` skill already wrote one for you. To
|
|
272
|
+
write a starter by hand, `rigor init` emits `.rigor.dist.yml`
|
|
273
|
+
— the project default that gets committed:
|
|
274
|
+
|
|
275
|
+
```yaml
|
|
276
|
+
target_ruby: "3.4" # your project's Ruby — not Rigor's own 4.0
|
|
277
|
+
|
|
278
|
+
paths:
|
|
279
|
+
- lib
|
|
280
|
+
|
|
281
|
+
# signature_paths: [sig] # auto-detected when omitted
|
|
282
|
+
|
|
283
|
+
severity_profile: balanced
|
|
284
|
+
|
|
285
|
+
# severity_overrides:
|
|
286
|
+
# call.argument-type-mismatch: warning
|
|
287
|
+
|
|
288
|
+
# disable: []
|
|
289
|
+
|
|
290
|
+
# plugins: []
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
That is all most projects need. The remaining mechanics —
|
|
294
|
+
editor autocomplete from the bundled JSON schema, the
|
|
295
|
+
`.rigor.yml` vs `.rigor.dist.yml` precedence rule, `includes:`
|
|
296
|
+
composition, and how path-bearing keys resolve relative to the
|
|
297
|
+
declaring file — are covered in
|
|
298
|
+
[Configuration](../manual/03-configuration.md). The one rule
|
|
299
|
+
worth knowing up front: when a developer keeps a local
|
|
300
|
+
`.rigor.yml`, it is the *sole* source of config for their runs
|
|
301
|
+
(the two files are never merged automatically), so to extend
|
|
302
|
+
the shared default it must list it under `includes:`.
|
|
303
|
+
|
|
304
|
+
## What's next
|
|
305
|
+
|
|
306
|
+
Chapter 2 introduces the carriers Rigor uses to represent
|
|
307
|
+
types — the part of the model that distinguishes Rigor from
|
|
308
|
+
ordinary RBS. After that, Chapter 3 (narrowing) makes the
|
|
309
|
+
carriers come alive: the carriers describe values, and
|
|
310
|
+
narrowing describes how those carriers change as control
|
|
311
|
+
flow passes through `if` / `case` / predicate methods.
|