rigortype 0.2.4 → 0.2.6
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/docs/handbook/09-plugins.md +5 -2
- data/docs/handbook/appendix-liskov.md +5 -3
- data/docs/handbook/appendix-phpstan.md +2 -2
- data/docs/install.md +1 -1
- data/docs/manual/02-cli-reference.md +58 -1
- data/docs/manual/06-baseline.md +12 -0
- data/docs/manual/11-ci.md +6 -6
- data/docs/manual/15-type-protection-coverage.md +29 -0
- data/docs/manual/plugins/rigor-minitest.md +1 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +22 -3
- data/lib/rigor/analysis/incremental_session.rb +7 -2
- data/lib/rigor/cli/check_command.rb +4 -33
- data/lib/rigor/cli/check_runner_factory.rb +63 -0
- data/lib/rigor/cli/doctor_command.rb +295 -0
- data/lib/rigor/cli/plugins_command.rb +2 -2
- data/lib/rigor/cli/plugins_renderer.rb +1 -1
- data/lib/rigor/cli/protection_renderer.rb +32 -2
- data/lib/rigor/cli/protection_report.rb +32 -6
- data/lib/rigor/cli/upgrade_command.rb +25 -0
- data/lib/rigor/cli.rb +17 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/inference/dynamic_origin.rb +67 -0
- data/lib/rigor/inference/expression_typer.rb +22 -10
- data/lib/rigor/inference/fallback.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +16 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +41 -2
- data/lib/rigor/inference/method_dispatcher.rb +19 -4
- data/lib/rigor/inference/mutation_widening.rb +18 -0
- data/lib/rigor/inference/protection_scanner.rb +6 -3
- data/lib/rigor/inference/statement_evaluator.rb +5 -4
- data/lib/rigor/plugin/base.rb +34 -7
- data/lib/rigor/plugin/registry.rb +1 -1
- data/lib/rigor/scope.rb +16 -5
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +1 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +52 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +123 -8
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +3 -3
- data/sig/rigor/plugin/base.rbs +2 -0
- data/sig/rigor/scope.rbs +3 -1
- data/skills/rigor-plugin-author/SKILL.md +8 -5
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +8 -4
- metadata +27 -3
|
@@ -26,6 +26,7 @@ module Rigor
|
|
|
26
26
|
# config:
|
|
27
27
|
# locale_search_paths: ["config/locales"] # default; optional
|
|
28
28
|
# configured_locales: ["en"] # default; optional — locales the project ships
|
|
29
|
+
# view_search_paths: ["app/views"] # default; optional — view template search roots
|
|
29
30
|
#
|
|
30
31
|
# ## What it checks
|
|
31
32
|
#
|
|
@@ -39,6 +40,11 @@ module Rigor
|
|
|
39
40
|
# `%{var}` placeholders must match the call's keyword
|
|
40
41
|
# arguments. Missing placeholders are errors; extra
|
|
41
42
|
# arguments are warnings.
|
|
43
|
+
# 4. **View template lazy keys** — `t('.title')` inside
|
|
44
|
+
# `app/views/setting/index.html.erb` expands to
|
|
45
|
+
# `setting.index.title` and is validated against the
|
|
46
|
+
# locale index. ERB, Haml, and Slim templates are
|
|
47
|
+
# scanned under `view_search_paths`.
|
|
42
48
|
#
|
|
43
49
|
# ## Limitations
|
|
44
50
|
#
|
|
@@ -51,6 +57,16 @@ module Rigor
|
|
|
51
57
|
# Lazy keys in non-controller `.rb` files (models, helpers,
|
|
52
58
|
# mailers, …) are silently skipped — the controller/action
|
|
53
59
|
# scope cannot be statically determined there.
|
|
60
|
+
# - View template lazy keys (`t('.key')` inside ERB / Haml /
|
|
61
|
+
# Slim) are validated for key existence and per-locale
|
|
62
|
+
# coverage. Interpolation variable validation is skipped
|
|
63
|
+
# for view templates (the hash may come from controller
|
|
64
|
+
# instance variables not visible in the template).
|
|
65
|
+
# The view scan is a project-wide pass surfaced through the
|
|
66
|
+
# per-file diagnostic hook, so under `--workers` each
|
|
67
|
+
# fork-pool worker re-emits the full set (the same
|
|
68
|
+
# once-per-run limitation the `load-error` path carries);
|
|
69
|
+
# sequential `rigor check` is unaffected.
|
|
54
70
|
# - Pluralization (`t('errors.messages.too_short',
|
|
55
71
|
# count: n)`) is recognised at the call site but the
|
|
56
72
|
# `count` key is not used to validate the locale's
|
|
@@ -61,14 +77,15 @@ module Rigor
|
|
|
61
77
|
class RailsI18n < Rigor::Plugin::Base
|
|
62
78
|
manifest(
|
|
63
79
|
id: "rails-i18n",
|
|
64
|
-
# Bumped 2026-
|
|
65
|
-
#
|
|
66
|
-
# `
|
|
67
|
-
version: "0.
|
|
80
|
+
# Bumped 2026-06-23 — view template lazy-key scanning
|
|
81
|
+
# (`t('.key')` inside ERB / Haml / Slim under
|
|
82
|
+
# `view_search_paths`).
|
|
83
|
+
version: "0.3.0",
|
|
68
84
|
description: "Validates I18n `t(key)` calls against `config/locales/*.yml`.",
|
|
69
85
|
config_schema: {
|
|
70
86
|
"locale_search_paths" => { kind: :array, default: ["config/locales"] },
|
|
71
|
-
"configured_locales" => { kind: :array, default: ["en"] }
|
|
87
|
+
"configured_locales" => { kind: :array, default: ["en"] },
|
|
88
|
+
"view_search_paths" => { kind: :array, default: ["app/views"] }
|
|
72
89
|
}
|
|
73
90
|
)
|
|
74
91
|
|
|
@@ -88,22 +105,49 @@ module Rigor
|
|
|
88
105
|
index
|
|
89
106
|
end
|
|
90
107
|
|
|
108
|
+
# Scans view templates under `view_search_paths` for lazy
|
|
109
|
+
# `t('.key')` / `I18n.translate('.key')` calls and validates
|
|
110
|
+
# each expanded key against the locale index. Interpolation
|
|
111
|
+
# validation is skipped — the hash may come from controller
|
|
112
|
+
# instance variables not visible in the template source.
|
|
113
|
+
#
|
|
114
|
+
# Watches `**/*.{erb,haml,slim}` under each search root so
|
|
115
|
+
# the cache invalidates when templates are edited.
|
|
116
|
+
producer :view_diagnostics, watch: -> { [[@view_search_paths, "**/*.erb", "**/*.haml", "**/*.slim"]] } do |_params|
|
|
117
|
+
index = producer_value(:locale_index)
|
|
118
|
+
next [] if index.nil? || index.empty?
|
|
119
|
+
|
|
120
|
+
scan_view_files(index)
|
|
121
|
+
end
|
|
122
|
+
|
|
91
123
|
def init(_services)
|
|
92
124
|
@locale_search_paths = Array(config.fetch("locale_search_paths")).map(&:to_s)
|
|
125
|
+
@view_search_paths = Array(config.fetch("view_search_paths")).map(&:to_s)
|
|
93
126
|
@configured_locales = Array(config.fetch("configured_locales")).map(&:to_s)
|
|
94
127
|
@load_errors = []
|
|
95
128
|
@load_errors_emitted = false
|
|
129
|
+
@view_diagnostics_emitted = false
|
|
96
130
|
end
|
|
97
131
|
|
|
98
132
|
# File-level only: the once-per-run YAML load errors + the
|
|
99
|
-
# runtime (cache-load) error. Per-call
|
|
100
|
-
# over the engine-owned walk via the
|
|
101
|
-
#
|
|
133
|
+
# runtime (cache-load) error + the view-template scan. Per-call
|
|
134
|
+
# `t('key')` validation runs over the engine-owned walk via the
|
|
135
|
+
# node_rule below (ADR-37). The locale index and view diagnostics
|
|
136
|
+
# are lazily loaded + memoised by `producer_value`.
|
|
102
137
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
103
138
|
index = producer_value(:locale_index)
|
|
104
139
|
diagnostics = []
|
|
105
140
|
diagnostics.concat(consume_load_error_diagnostics(path)) unless @load_errors.empty?
|
|
106
141
|
diagnostics << runtime_error_diagnostic(path) if index.nil? && producer_error(:locale_index)
|
|
142
|
+
unless @view_diagnostics_emitted
|
|
143
|
+
view_diags = producer_value(:view_diagnostics) || []
|
|
144
|
+
if (view_err = producer_error(:view_diagnostics))
|
|
145
|
+
diagnostics << view_runtime_error_diagnostic(path, view_err)
|
|
146
|
+
else
|
|
147
|
+
diagnostics.concat(view_diags)
|
|
148
|
+
end
|
|
149
|
+
@view_diagnostics_emitted = true
|
|
150
|
+
end
|
|
107
151
|
diagnostics
|
|
108
152
|
end
|
|
109
153
|
|
|
@@ -126,6 +170,68 @@ module Rigor
|
|
|
126
170
|
|
|
127
171
|
private
|
|
128
172
|
|
|
173
|
+
def scan_view_files(index)
|
|
174
|
+
view_files.flat_map do |view_path|
|
|
175
|
+
scan_view_file(view_path, index)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def scan_view_file(view_path, index)
|
|
180
|
+
content = read_view_template(view_path) or return []
|
|
181
|
+
scope = Analyzer.view_scope_from_path(view_path) or return []
|
|
182
|
+
|
|
183
|
+
display_path = relative_view_path(view_path)
|
|
184
|
+
Analyzer.extract_lazy_keys_from_erb(content).flat_map do |key|
|
|
185
|
+
full_key = "#{scope}.#{key}"
|
|
186
|
+
Analyzer.validate_view_key(
|
|
187
|
+
full_key, locale_index: index, configured_locales: @configured_locales
|
|
188
|
+
).map { |v| view_diagnostic(display_path, v) }
|
|
189
|
+
end
|
|
190
|
+
rescue StandardError => e
|
|
191
|
+
[Rigor::Analysis::Diagnostic.new(
|
|
192
|
+
path: relative_view_path(view_path), line: 1, column: 1,
|
|
193
|
+
message: "rigor-rails-i18n: failed to scan view template: #{e.class}: #{e.message}",
|
|
194
|
+
severity: :warning,
|
|
195
|
+
rule: "load-error"
|
|
196
|
+
)]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def view_files
|
|
200
|
+
@view_search_paths.flat_map do |root|
|
|
201
|
+
absolute = File.expand_path(root)
|
|
202
|
+
next [] unless File.directory?(absolute)
|
|
203
|
+
|
|
204
|
+
Dir.glob(File.join(absolute, "**", "*.{erb,haml,slim}"))
|
|
205
|
+
end.sort
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Diagnostics anchor on a path relative to the working
|
|
209
|
+
# directory — the same base `File.expand_path` globbed the
|
|
210
|
+
# view files against — so view diagnostics render and match
|
|
211
|
+
# baselines like every other diagnostic (which carry
|
|
212
|
+
# project-relative paths). Falls back to the absolute path
|
|
213
|
+
# for a view outside the working tree.
|
|
214
|
+
def relative_view_path(absolute_path)
|
|
215
|
+
Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
|
|
216
|
+
rescue ArgumentError
|
|
217
|
+
absolute_path
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def read_view_template(path)
|
|
221
|
+
io_boundary.read_file(path)
|
|
222
|
+
rescue Plugin::AccessDeniedError
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def view_diagnostic(view_path, violation)
|
|
227
|
+
Rigor::Analysis::Diagnostic.new(
|
|
228
|
+
path: view_path, line: 1, column: 1,
|
|
229
|
+
message: violation.message,
|
|
230
|
+
severity: violation.severity,
|
|
231
|
+
rule: violation.rule
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
|
|
129
235
|
# The runner only invokes `diagnostics_for_file` for
|
|
130
236
|
# Ruby files (`paths:` is filtered to `.rb`). YAML
|
|
131
237
|
# parse errors therefore can't be anchored on the
|
|
@@ -155,6 +261,15 @@ module Rigor
|
|
|
155
261
|
rule: "load-error"
|
|
156
262
|
)
|
|
157
263
|
end
|
|
264
|
+
|
|
265
|
+
def view_runtime_error_diagnostic(path, error)
|
|
266
|
+
Rigor::Analysis::Diagnostic.new(
|
|
267
|
+
path: path, line: 1, column: 1,
|
|
268
|
+
message: "rigor-rails-i18n: failed to scan view templates: #{error.class}: #{error.message}",
|
|
269
|
+
severity: :warning,
|
|
270
|
+
rule: "load-error"
|
|
271
|
+
)
|
|
272
|
+
end
|
|
158
273
|
end
|
|
159
274
|
|
|
160
275
|
Rigor::Plugin.register(RailsI18n)
|
|
@@ -110,7 +110,7 @@ module Rigor
|
|
|
110
110
|
# ADR-37 slice 2 — matcher narrowing
|
|
111
111
|
# (`expect(x).to be_a(T)` → `post_return_facts` on `x`),
|
|
112
112
|
# method-gated by the engine on the expectation verbs.
|
|
113
|
-
|
|
113
|
+
narrowing_facts methods: %i[to not_to to_not] do |call_node, scope|
|
|
114
114
|
MatcherAnalyzer.contribution_for(call_node, environment: scope&.environment)&.post_return_facts
|
|
115
115
|
end
|
|
116
116
|
|
|
@@ -169,13 +169,13 @@ module Rigor
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact,
|
|
172
|
-
# contributed via the method-gated `
|
|
172
|
+
# contributed via the method-gated `narrowing_facts` DSL. The
|
|
173
173
|
# statement evaluator consults this path for narrowing facts.
|
|
174
174
|
# The return-type half (`Constant[nil]`) flows through the
|
|
175
175
|
# `dynamic_return` rule above; the block re-checks the `T.`
|
|
176
176
|
# receiver via the recogniser, so an unrelated `bind` call
|
|
177
177
|
# contributes nothing.
|
|
178
|
-
|
|
178
|
+
narrowing_facts methods: [:bind] do |call_node, scope|
|
|
179
179
|
bind_post_return_facts(call_node, scope)
|
|
180
180
|
end
|
|
181
181
|
|
|
@@ -258,7 +258,7 @@ module Rigor
|
|
|
258
258
|
lookup_signature(call_node, scope)&.return_type
|
|
259
259
|
end
|
|
260
260
|
|
|
261
|
-
# The `
|
|
261
|
+
# The `narrowing_facts` body for `T.bind` — same sigil gate as
|
|
262
262
|
# the return-type path, then the recogniser's
|
|
263
263
|
# `post_return_facts` (the `Fact(target_kind: :self)` that
|
|
264
264
|
# narrows `scope.self_type` for the rest of the block).
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -26,6 +26,8 @@ class Rigor::Plugin::Base
|
|
|
26
26
|
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
27
27
|
def self.dynamic_returns: () -> Array[untyped]
|
|
28
28
|
|
|
29
|
+
def self.narrowing_facts: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
|
+
# Deprecating alias for `narrowing_facts` (ADR-80); removed in 0.3.0.
|
|
29
31
|
def self.type_specifier: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
32
|
def self.type_specifiers: () -> Array[untyped]
|
|
31
33
|
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -8,6 +8,7 @@ module Rigor
|
|
|
8
8
|
attr_reader cvars: Hash[Symbol, Type::t]
|
|
9
9
|
attr_reader globals: Hash[Symbol, Type::t]
|
|
10
10
|
attr_reader discovery: DiscoveryIndex
|
|
11
|
+
attr_reader dynamic_origins: Hash[untyped, Symbol]
|
|
11
12
|
attr_reader indexed_narrowings: Hash[IndexedKey, Type::t]
|
|
12
13
|
attr_reader method_chain_narrowings: Hash[ChainKey, Type::t]
|
|
13
14
|
attr_reader source_path: String?
|
|
@@ -32,6 +33,7 @@ module Rigor
|
|
|
32
33
|
def data_member_layouts: () -> Hash[String, Array[Symbol]]
|
|
33
34
|
def struct_member_layouts: () -> Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
34
35
|
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
36
|
+
def record_dynamic_origin: (untyped node, Symbol cause) -> Scope
|
|
35
37
|
|
|
36
38
|
class DiscoveryIndex
|
|
37
39
|
attr_reader declared_types: Hash[untyped, Type::t]
|
|
@@ -71,7 +73,7 @@ module Rigor
|
|
|
71
73
|
|
|
72
74
|
def self.empty: (?environment: Environment, ?source_path: String?) -> Scope
|
|
73
75
|
|
|
74
|
-
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?) -> void
|
|
76
|
+
def initialize: (environment: Environment, locals: Hash[Symbol, Type::t], ?fact_store: Analysis::FactStore, ?self_type: Type::t?, ?ivars: Hash[Symbol, Type::t], ?cvars: Hash[Symbol, Type::t], ?globals: Hash[Symbol, Type::t], ?discovery: DiscoveryIndex, ?source_path: String?, ?dynamic_origins: Hash[untyped, Symbol]) -> void
|
|
75
77
|
def with_source_path: (String? path) -> Scope
|
|
76
78
|
def with_struct_fold_safe: (Set[Symbol] locals) -> Scope
|
|
77
79
|
def struct_fold_safe?: (String | Symbol name) -> bool
|
|
@@ -97,19 +97,22 @@ AST walk per file — hands every matching node to the block along with a
|
|
|
97
97
|
`scope` it can query for inferred types. The block returns an array of
|
|
98
98
|
`Rigor::Analysis::Diagnostic` (built via the `diagnostic` helper).
|
|
99
99
|
Optionally the plugin also declares `dynamic_return(receivers:)` /
|
|
100
|
-
`
|
|
101
|
-
for call sites the core analyzer types as `Dynamic`.
|
|
100
|
+
`narrowing_facts(methods:)` to *supply* a return type or narrowing facts
|
|
101
|
+
for call sites the core analyzer types as `Dynamic`. (`narrowing_facts`
|
|
102
|
+
was renamed from `type_specifier` in ADR-80; `type_specifier` remains as a
|
|
103
|
+
deprecating alias removed in 0.3.0 — use `narrowing_facts` in new plugins.)
|
|
104
|
+
`#diagnostics_for_file`
|
|
102
105
|
is the file-rule surface for whole-file diagnostics a per-node walk can't
|
|
103
106
|
express. (`flow_contribution_for` was removed pre-1.0 in ADR-52 WD3 —
|
|
104
107
|
defining it now raises `ArgumentError`; use `dynamic_return` /
|
|
105
|
-
`
|
|
108
|
+
`narrowing_facts`. See Phase 2.)
|
|
106
109
|
|
|
107
110
|
## Phase outline
|
|
108
111
|
|
|
109
112
|
| Phase | What | Reference |
|
|
110
113
|
| --- | --- | --- |
|
|
111
114
|
| 1 | Package and scaffold — gem vs project-private layout, gemspec / Gemfile, the plugin class skeleton, `.rigor.yml` activation. | [`references/01-plan-and-scaffold.md`](references/01-plan-and-scaffold.md) |
|
|
112
|
-
| 2 | Node rules — `node_rule` (engine-owned walk), building `Diagnostic`s via `Base#diagnostic`, querying `scope.type_of`, calling the target library directly instead of reimplementing it (ADR-39: `Plugin::Inflector`, `Base.suggest`), optional `dynamic_return` / `
|
|
115
|
+
| 2 | Node rules — `node_rule` (engine-owned walk), building `Diagnostic`s via `Base#diagnostic`, querying `scope.type_of`, calling the target library directly instead of reimplementing it (ADR-39: `Plugin::Inflector`, `Base.suggest`), optional `dynamic_return` / `narrowing_facts`, RBS for the DSL. | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) |
|
|
113
116
|
| 3 | Test and ship — fixture-based tests (RSpec / Minitest, no rigor internals), version pinning, README, publish or keep private. | [`references/03-test-and-ship.md`](references/03-test-and-ship.md) |
|
|
114
117
|
|
|
115
118
|
## Reading order — modules
|
|
@@ -117,5 +120,5 @@ defining it now raises `ArgumentError`; use `dynamic_return` /
|
|
|
117
120
|
| Module | Read | Covers |
|
|
118
121
|
| --- | --- | --- |
|
|
119
122
|
| 1 | [`references/01-plan-and-scaffold.md`](references/01-plan-and-scaffold.md) | **Phase 1.** The gem vs project-private packaging split, directory trees for both, gemspec template, project-private path-gem / `RUBYLIB` activation, the `Rigor::Plugin::Base` skeleton, `.rigor.yml` `plugins:` wiring. |
|
|
120
|
-
| 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `
|
|
123
|
+
| 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `narrowing_facts` return-type hooks (`narrowing_facts` was renamed from `type_specifier` in ADR-80, alias removed in 0.3.0; `flow_contribution_for` was removed pre-1.0 in ADR-52 WD3), calling the target library's pure methods directly rather than reimplementing them (ADR-39: `Plugin::Inflector` over the real `ActiveSupport::Inflector`; `Base.suggest` for did-you-mean), and shipping `sig/*.rbs` so the DSL's types are visible. |
|
|
121
124
|
| 3 | [`references/03-test-and-ship.md`](references/03-test-and-ship.md) | **Phase 3.** Testing a plugin from outside the monorepo — fixture projects driven through `rigor check --format json`, plus pure unit tests of dispatch tables — with RSpec or Minitest. Version pinning against the pre-1.0 contract. README. Publishing to RubyGems or keeping the plugin private. |
|
|
@@ -162,7 +162,7 @@ rather than hand-rolling Levenshtein:
|
|
|
162
162
|
Rigor::Plugin::Base.suggest(typo, known_names) # nearest match, or nil
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
## Optional — contribute a return type with `dynamic_return` / `
|
|
165
|
+
## Optional — contribute a return type with `dynamic_return` / `narrowing_facts`
|
|
166
166
|
|
|
167
167
|
> **Critical — these hooks do NOT make a method "defined", so they do
|
|
168
168
|
> NOT suppress `call.undefined-method`.** Method *existence* and call
|
|
@@ -197,11 +197,15 @@ end
|
|
|
197
197
|
# Post-return NARROWING FACTS, gated on the call's method name.
|
|
198
198
|
# Return an Array of facts (or nil). Used for assertion / predicate
|
|
199
199
|
# narrowing (`assert_kind_of(Foo, x)` ⇒ x is Foo afterwards).
|
|
200
|
-
|
|
200
|
+
narrowing_facts methods: [:assert_kind_of] do |call_node, scope|
|
|
201
201
|
# ... build and return the post-return facts ...
|
|
202
202
|
end
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
> **`narrowing_facts` was renamed from `type_specifier` in ADR-80.**
|
|
206
|
+
> `type_specifier` remains as a deprecating alias, removed in 0.3.0 — use
|
|
207
|
+
> `narrowing_facts` in new plugins.
|
|
208
|
+
|
|
205
209
|
Build return types with `Rigor::Type::Combinator`:
|
|
206
210
|
|
|
207
211
|
```ruby
|
|
@@ -240,7 +244,7 @@ If the DSL introduces methods or classes that Rigor cannot see (a
|
|
|
240
244
|
Rigor RBS declaring them so *core* inference — not just your plugin —
|
|
241
245
|
treats them as **defined**. This is what removes the
|
|
242
246
|
`call.undefined-method` diagnostics on those methods; nothing else
|
|
243
|
-
(not a `node_rule`, not `dynamic_return` / `
|
|
247
|
+
(not a `node_rule`, not `dynamic_return` / `narrowing_facts`) makes a
|
|
244
248
|
method exist in Rigor's view.
|
|
245
249
|
|
|
246
250
|
Two ways to wire the RBS, depending on how the plugin is packaged:
|
|
@@ -288,6 +292,6 @@ They compose — many plugins ship both.
|
|
|
288
292
|
|
|
289
293
|
A plugin whose `node_rule`(s) recognise the DSL and emit diagnostics
|
|
290
294
|
with correct severities and rule ids — optionally a `dynamic_return` /
|
|
291
|
-
`
|
|
295
|
+
`narrowing_facts` and a `sig/` bundle. Verify by eye with `rigor check`;
|
|
292
296
|
lock it down with tests in Phase 3
|
|
293
297
|
([`03-test-and-ship.md`](03-test-and-ship.md)).
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rigortype
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: language_server-protocol
|
|
@@ -109,6 +109,26 @@ dependencies:
|
|
|
109
109
|
- - "<"
|
|
110
110
|
- !ruby/object:Gem::Version
|
|
111
111
|
version: '4.0'
|
|
112
|
+
- !ruby/object:Gem::Dependency
|
|
113
|
+
name: binpacker
|
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: 0.1.0
|
|
119
|
+
- - "<"
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '1.0'
|
|
122
|
+
type: :development
|
|
123
|
+
prerelease: false
|
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: 0.1.0
|
|
129
|
+
- - "<"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '1.0'
|
|
112
132
|
- !ruby/object:Gem::Dependency
|
|
113
133
|
name: parallel_tests
|
|
114
134
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -440,6 +460,7 @@ files:
|
|
|
440
460
|
- lib/rigor/cli/annotate_command.rb
|
|
441
461
|
- lib/rigor/cli/baseline_command.rb
|
|
442
462
|
- lib/rigor/cli/check_command.rb
|
|
463
|
+
- lib/rigor/cli/check_runner_factory.rb
|
|
443
464
|
- lib/rigor/cli/ci_detector.rb
|
|
444
465
|
- lib/rigor/cli/command.rb
|
|
445
466
|
- lib/rigor/cli/coverage_command.rb
|
|
@@ -450,6 +471,7 @@ files:
|
|
|
450
471
|
- lib/rigor/cli/diagnostic_formats.rb
|
|
451
472
|
- lib/rigor/cli/diff_command.rb
|
|
452
473
|
- lib/rigor/cli/docs_command.rb
|
|
474
|
+
- lib/rigor/cli/doctor_command.rb
|
|
453
475
|
- lib/rigor/cli/explain_command.rb
|
|
454
476
|
- lib/rigor/cli/fused_protection_renderer.rb
|
|
455
477
|
- lib/rigor/cli/fused_protection_report.rb
|
|
@@ -478,6 +500,7 @@ files:
|
|
|
478
500
|
- lib/rigor/cli/type_scan_command.rb
|
|
479
501
|
- lib/rigor/cli/type_scan_renderer.rb
|
|
480
502
|
- lib/rigor/cli/type_scan_report.rb
|
|
503
|
+
- lib/rigor/cli/upgrade_command.rb
|
|
481
504
|
- lib/rigor/config_audit.rb
|
|
482
505
|
- lib/rigor/configuration.rb
|
|
483
506
|
- lib/rigor/configuration/dependencies.rb
|
|
@@ -527,6 +550,7 @@ files:
|
|
|
527
550
|
- lib/rigor/inference/closure_escape_analyzer.rb
|
|
528
551
|
- lib/rigor/inference/coverage_scanner.rb
|
|
529
552
|
- lib/rigor/inference/def_return_typer.rb
|
|
553
|
+
- lib/rigor/inference/dynamic_origin.rb
|
|
530
554
|
- lib/rigor/inference/expression_typer.rb
|
|
531
555
|
- lib/rigor/inference/fallback.rb
|
|
532
556
|
- lib/rigor/inference/fallback_tracer.rb
|
|
@@ -934,7 +958,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
934
958
|
- !ruby/object:Gem::Version
|
|
935
959
|
version: '0'
|
|
936
960
|
requirements: []
|
|
937
|
-
rubygems_version:
|
|
961
|
+
rubygems_version: 4.0.10
|
|
938
962
|
specification_version: 4
|
|
939
963
|
summary: Inference-first static analysis for Ruby.
|
|
940
964
|
test_files: []
|