rigortype 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/rigor/analysis/baseline.rb +51 -15
- data/lib/rigor/analysis/runner.rb +67 -9
- data/lib/rigor/analysis/worker_session.rb +13 -4
- data/lib/rigor/cache/rbs_descriptor.rb +21 -2
- data/lib/rigor/cache/rbs_environment.rb +2 -1
- data/lib/rigor/cli/annotate_command.rb +57 -7
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/coverage_command.rb +126 -0
- data/lib/rigor/cli/coverage_renderer.rb +162 -0
- data/lib/rigor/cli/coverage_report.rb +75 -0
- data/lib/rigor/cli/mcp_command.rb +70 -0
- data/lib/rigor/cli.rb +88 -5
- data/lib/rigor/environment/rbs_loader.rb +46 -5
- data/lib/rigor/environment/reporters.rb +3 -2
- data/lib/rigor/environment.rb +159 -4
- data/lib/rigor/inference/def_return_typer.rb +98 -0
- data/lib/rigor/inference/expression_typer.rb +143 -12
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +62 -15
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +115 -7
- data/lib/rigor/inference/precision_scanner.rb +131 -0
- data/lib/rigor/inference/statement_evaluator.rb +26 -2
- data/lib/rigor/mcp/loop.rb +43 -0
- data/lib/rigor/mcp/server.rb +263 -0
- data/lib/rigor/mcp.rb +16 -0
- data/lib/rigor/plugin/base.rb +28 -5
- data/lib/rigor/plugin/manifest.rb +33 -5
- data/lib/rigor/plugin/registry.rb +21 -0
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +51 -0
- data/lib/rigor/sig_gen/generator.rb +150 -75
- data/lib/rigor/type/combinator.rb +57 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +190 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +189 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +81 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +142 -0
- data/plugins/rigor-actioncable/lib/rigor-actioncable.rb +3 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +178 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +310 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +76 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +177 -0
- data/plugins/rigor-actionmailer/lib/rigor-actionmailer.rb +3 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +589 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +150 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +123 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +247 -0
- data/plugins/rigor-actionpack/lib/rigor-actionpack.rb +3 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +114 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +177 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +65 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +117 -0
- data/plugins/rigor-activejob/lib/rigor-activejob.rb +3 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +273 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/inflector.rb +114 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +561 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +194 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +240 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +94 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +514 -0
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +8 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +182 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +78 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +162 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +43 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +170 -0
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +8 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +34 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +20 -0
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +463 -0
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +108 -0
- data/plugins/rigor-devise/lib/rigor-devise.rb +8 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +285 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +124 -0
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +8 -0
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +116 -0
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +8 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +341 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +120 -0
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +8 -0
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +120 -0
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +85 -0
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +7 -0
- data/plugins/rigor-dry-validation/sig/dry_validation.rbs +25 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +177 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +242 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +56 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +174 -0
- data/plugins/rigor-factorybot/lib/rigor-factorybot.rb +3 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +409 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +114 -0
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +8 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +124 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +111 -0
- data/plugins/rigor-hanami/lib/rigor-hanami.rb +3 -0
- data/plugins/rigor-hanami/sig/hanami_action.rbs +78 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +302 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +72 -0
- data/plugins/rigor-minitest/lib/rigor-minitest.rb +3 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +194 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +140 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +65 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +130 -0
- data/plugins/rigor-pundit/lib/rigor-pundit.rb +3 -0
- data/plugins/rigor-rails/lib/rigor-rails.rb +31 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +277 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +108 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +138 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +167 -0
- data/plugins/rigor-rails-i18n/lib/rigor-rails-i18n.rb +3 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +161 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +103 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +490 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +158 -0
- data/plugins/rigor-rails-routes/lib/rigor-rails-routes.rb +3 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +163 -0
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +24 -0
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +110 -0
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +200 -0
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +170 -0
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +233 -0
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +190 -0
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +188 -0
- data/plugins/rigor-rspec/lib/rigor-rspec.rb +3 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +128 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +60 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +75 -0
- data/plugins/rigor-rspec-rails/lib/rigor-rspec-rails.rb +3 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +266 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +113 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor-shoulda-matchers.rb +3 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +152 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +190 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +61 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +124 -0
- data/plugins/rigor-sidekiq/lib/rigor-sidekiq.rb +3 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +85 -0
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +8 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +108 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +250 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +95 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +226 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +28 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +154 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +100 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +323 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +660 -0
- data/plugins/rigor-sorbet/lib/rigor-sorbet.rb +3 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +209 -0
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +8 -0
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +163 -0
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +9 -0
- data/sig/rigor/analysis/baseline.rbs +39 -0
- data/sig/rigor/environment.rbs +3 -2
- data/sig/rigor/type.rbs +4 -0
- data/sig/rigor.rbs +2 -0
- metadata +180 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require "rigor/plugin"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Plugin
|
|
8
|
+
# Example plugin: validates state-machine references.
|
|
9
|
+
# Demonstrates the **two-pass DSL analysis** pattern many
|
|
10
|
+
# plugins reuse:
|
|
11
|
+
#
|
|
12
|
+
# 1. **Collect pass.** Walk the file once to gather every
|
|
13
|
+
# state name declared inside a `state_machine do ... end`
|
|
14
|
+
# block (`state :draft`, `state :submitted`, ...).
|
|
15
|
+
# 2. **Validate pass.** Walk the file again, validating each
|
|
16
|
+
# `transition_to(:sym)` and `event :sym` reference against
|
|
17
|
+
# the collected state set. Levenshtein distance ≤ 3 drives
|
|
18
|
+
# the did-you-mean suggestions.
|
|
19
|
+
#
|
|
20
|
+
# Useful for `aasm` / `statesman` / hand-rolled DSLs and any
|
|
21
|
+
# framework where declarations and uses live in the same
|
|
22
|
+
# file. The same skeleton lifts to GraphQL types,
|
|
23
|
+
# ActiveModel validations, route declarations — anywhere a
|
|
24
|
+
# declarative DSL produces a closed namespace and the rest
|
|
25
|
+
# of the file references that namespace by literal symbol.
|
|
26
|
+
#
|
|
27
|
+
# ## Configuration
|
|
28
|
+
#
|
|
29
|
+
# Defaults match the `Statesman::Machine` API; override via
|
|
30
|
+
# `.rigor.yml` if your DSL uses different names:
|
|
31
|
+
#
|
|
32
|
+
# plugins:
|
|
33
|
+
# - gem: rigor-statesman
|
|
34
|
+
# config:
|
|
35
|
+
# dsl_method: state_machine # the do-block opener
|
|
36
|
+
# state_method: state # state declaration inside the block
|
|
37
|
+
# transition_method: transition_to # call-site under check
|
|
38
|
+
#
|
|
39
|
+
# ## Diagnostics
|
|
40
|
+
#
|
|
41
|
+
# | Event | Severity | Rule |
|
|
42
|
+
# | --- | --- | --- |
|
|
43
|
+
# | `transition_to(:known_state)` | `:info` | `known-state` |
|
|
44
|
+
# | `transition_to(:typo)` (close match) | `:error` | `unknown-state` (with did-you-mean) |
|
|
45
|
+
# | `transition_to(:typo)` (no close match) | `:error` | `unknown-state` |
|
|
46
|
+
# | file declares no state machine | silent | — |
|
|
47
|
+
class Statesman < Rigor::Plugin::Base
|
|
48
|
+
manifest(
|
|
49
|
+
id: "statesman",
|
|
50
|
+
version: "0.1.0",
|
|
51
|
+
description: "Validates state-machine transition references against declared states.",
|
|
52
|
+
config_schema: {
|
|
53
|
+
"dsl_method" => :string,
|
|
54
|
+
"state_method" => :string,
|
|
55
|
+
"transition_method" => :string
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
DEFAULT_DSL_METHOD = "state_machine"
|
|
60
|
+
DEFAULT_STATE_METHOD = "state"
|
|
61
|
+
DEFAULT_TRANSITION_METHOD = "transition_to"
|
|
62
|
+
DID_YOU_MEAN_DISTANCE = 3
|
|
63
|
+
|
|
64
|
+
def init(_services)
|
|
65
|
+
@dsl_method = config.fetch("dsl_method", DEFAULT_DSL_METHOD).to_sym
|
|
66
|
+
@state_method = config.fetch("state_method", DEFAULT_STATE_METHOD).to_sym
|
|
67
|
+
@transition_method = config.fetch("transition_method", DEFAULT_TRANSITION_METHOD).to_sym
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
71
|
+
states = collect_states(root)
|
|
72
|
+
return [] if states.empty?
|
|
73
|
+
|
|
74
|
+
validate_transitions(path, root, states)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Pass 1 — every `state :foo` declaration inside a
|
|
80
|
+
# `<dsl_method> do ... end` block on the file. Returns a
|
|
81
|
+
# frozen Set of state name Symbols.
|
|
82
|
+
def collect_states(root)
|
|
83
|
+
states = Set.new
|
|
84
|
+
walk(root) do |node|
|
|
85
|
+
next unless dsl_call?(node)
|
|
86
|
+
|
|
87
|
+
walk(node.block) do |inner|
|
|
88
|
+
next unless state_declaration?(inner)
|
|
89
|
+
|
|
90
|
+
sym = literal_symbol_arg(inner, 0)
|
|
91
|
+
states << sym if sym
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
states.freeze
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Pass 2 — every `<transition_method>(:sym)` call.
|
|
98
|
+
def validate_transitions(path, root, states)
|
|
99
|
+
diagnostics = []
|
|
100
|
+
walk(root) do |node|
|
|
101
|
+
next unless transition_call?(node)
|
|
102
|
+
|
|
103
|
+
sym = literal_symbol_arg(node, 0)
|
|
104
|
+
next if sym.nil? # not a literal — defer to runtime
|
|
105
|
+
|
|
106
|
+
diagnostics << build_diagnostic(path, node, sym, states)
|
|
107
|
+
end
|
|
108
|
+
diagnostics
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_diagnostic(path, node, sym, states)
|
|
112
|
+
if states.include?(sym)
|
|
113
|
+
diagnostic(
|
|
114
|
+
path, node,
|
|
115
|
+
severity: :info,
|
|
116
|
+
rule: "known-state",
|
|
117
|
+
message: "#{@transition_method}(:#{sym}) — declared state"
|
|
118
|
+
)
|
|
119
|
+
else
|
|
120
|
+
hint = did_you_mean(sym, states)
|
|
121
|
+
message = "unknown state :#{sym}"
|
|
122
|
+
message += " (did you mean :#{hint}?)" if hint
|
|
123
|
+
diagnostic(path, node, severity: :error, rule: "unknown-state", message: message)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def dsl_call?(node)
|
|
128
|
+
node.is_a?(Prism::CallNode) &&
|
|
129
|
+
node.name == @dsl_method &&
|
|
130
|
+
node.block
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def state_declaration?(node)
|
|
134
|
+
node.is_a?(Prism::CallNode) &&
|
|
135
|
+
node.name == @state_method &&
|
|
136
|
+
!node.arguments.nil?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def transition_call?(node)
|
|
140
|
+
node.is_a?(Prism::CallNode) &&
|
|
141
|
+
node.name == @transition_method &&
|
|
142
|
+
!node.arguments.nil?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def literal_symbol_arg(call, index)
|
|
146
|
+
node = call.arguments.arguments[index]
|
|
147
|
+
return nil unless node.is_a?(Prism::SymbolNode)
|
|
148
|
+
|
|
149
|
+
node.unescaped.to_sym
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def walk(node, &)
|
|
153
|
+
return if node.nil?
|
|
154
|
+
|
|
155
|
+
yield node
|
|
156
|
+
node.compact_child_nodes.each { |child| walk(child, &) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def did_you_mean(name, states)
|
|
160
|
+
target = name.to_s
|
|
161
|
+
best = nil
|
|
162
|
+
best_distance = DID_YOU_MEAN_DISTANCE + 1
|
|
163
|
+
states.each do |state|
|
|
164
|
+
distance = levenshtein(target, state.to_s)
|
|
165
|
+
if distance < best_distance
|
|
166
|
+
best = state
|
|
167
|
+
best_distance = distance
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
best
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def levenshtein(a, b) # rubocop:disable Naming/MethodParameterName
|
|
174
|
+
return b.length if a.empty?
|
|
175
|
+
return a.length if b.empty?
|
|
176
|
+
|
|
177
|
+
rows = Array.new(a.length + 1) { |_i| Array.new(b.length + 1, 0) }
|
|
178
|
+
(0..a.length).each { |i| rows[i][0] = i }
|
|
179
|
+
(0..b.length).each { |j| rows[0][j] = j }
|
|
180
|
+
|
|
181
|
+
(1..a.length).each do |i|
|
|
182
|
+
(1..b.length).each do |j|
|
|
183
|
+
cost = a[i - 1] == b[j - 1] ? 0 : 1
|
|
184
|
+
rows[i][j] = [
|
|
185
|
+
rows[i - 1][j] + 1,
|
|
186
|
+
rows[i][j - 1] + 1,
|
|
187
|
+
rows[i - 1][j - 1] + cost
|
|
188
|
+
].min
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
rows[a.length][b.length]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def diagnostic(path, node, severity:, rule:, message:)
|
|
195
|
+
location = node.location
|
|
196
|
+
Rigor::Analysis::Diagnostic.new(
|
|
197
|
+
path: path,
|
|
198
|
+
line: location.start_line,
|
|
199
|
+
column: location.start_column + 1,
|
|
200
|
+
message: message,
|
|
201
|
+
severity: severity,
|
|
202
|
+
rule: rule
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
Rigor::Plugin.register(Statesman)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Gem entry point. Required by Rigor's plugin loader when
|
|
4
|
+
# `.rigor.yml` lists `rigor-statesman` under `plugins:`. The
|
|
5
|
+
# loader expects this `require` to side-effect a call to
|
|
6
|
+
# `Rigor::Plugin.register`, which the body of
|
|
7
|
+
# `lib/rigor/plugin/statesman.rb` performs at load time.
|
|
8
|
+
require_relative "rigor/plugin/statesman"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
# Example plugin: ships the TypeScript-canonical utility-type
|
|
8
|
+
# aliases (`Pick<T, K>`, `Omit<T, K>`, `Partial<T>`,
|
|
9
|
+
# `Required<T>`, `Readonly<T>`) as opt-in vocabulary that
|
|
10
|
+
# resolves to the Rigor-canonical shape-projection type
|
|
11
|
+
# functions introduced in ADR-13.
|
|
12
|
+
#
|
|
13
|
+
# Background. `docs/type-specification/imported-built-in-types.md`
|
|
14
|
+
# § "Deferred or rejected imports" deliberately rejects
|
|
15
|
+
# TypeScript-canonical names from the Rigor core surface: the
|
|
16
|
+
# core stays RBS-canonical per ADR-0 / ADR-1, and the shape
|
|
17
|
+
# semantics live in core under `pick_of[T, K]` /
|
|
18
|
+
# `omit_of[T, K]` / `partial_of[T]` / `required_of[T]` /
|
|
19
|
+
# `readonly_of[T]` (added in ADR-13 slice 4). This plugin
|
|
20
|
+
# ships the TS spellings as an opt-in translation layer for
|
|
21
|
+
# users migrating from TypeScript / Sorbet / Flow-style RBI.
|
|
22
|
+
#
|
|
23
|
+
# Off by default — users add the gem to their `.rigor.yml`
|
|
24
|
+
# plugins list to enable.
|
|
25
|
+
#
|
|
26
|
+
# @example .rigor.yml
|
|
27
|
+
# plugins:
|
|
28
|
+
# - gem: rigor-typescript-utility-types
|
|
29
|
+
#
|
|
30
|
+
# Once loaded, an RBS::Extended payload such as
|
|
31
|
+
# `%a{rigor:v1:return: Pick[Foo, :a | :b]}` resolves through
|
|
32
|
+
# the chain: the parser builds a `Generic("Pick", …)` AST,
|
|
33
|
+
# the registry / built-in parametric builders decline, the
|
|
34
|
+
# chain consults `Resolvers::Pick`, which recursively
|
|
35
|
+
# resolves the two arguments to Rigor types and calls
|
|
36
|
+
# `Type::Combinator.pick_of` on them.
|
|
37
|
+
#
|
|
38
|
+
# ## Translation table
|
|
39
|
+
#
|
|
40
|
+
# | TypeScript spelling | Rigor core call |
|
|
41
|
+
# | --- | --- |
|
|
42
|
+
# | `Pick<T, K>` | `Type::Combinator.pick_of(T, K)` |
|
|
43
|
+
# | `Omit<T, K>` | `Type::Combinator.omit_of(T, K)` |
|
|
44
|
+
# | `Partial<T>` | `Type::Combinator.partial_of(T)` |
|
|
45
|
+
# | `Required<T>` | `Type::Combinator.required_of(T)` |
|
|
46
|
+
# | `Readonly<T>` | `Type::Combinator.readonly_of(T)` |
|
|
47
|
+
#
|
|
48
|
+
# ## Deferred TypeScript utility names
|
|
49
|
+
#
|
|
50
|
+
# `Parameters<F>` / `ReturnType<F>` / `InstanceType<C>` /
|
|
51
|
+
# `Awaited<P>` / `Uppercase<S>` / `Lowercase<S>` /
|
|
52
|
+
# `Capitalize<S>` / `Uncapitalize<S>` / `ThisParameterType<F>`
|
|
53
|
+
# / `OmitThisParameter<F>` / `ConstructorParameters<C>` /
|
|
54
|
+
# `NoInfer<T>` are NOT mapped today. The plugin returns `nil`
|
|
55
|
+
# from `#resolve` for those heads, leaving the parser's
|
|
56
|
+
# default Nominal-fallback to produce `Nominal[ReturnType, …]`
|
|
57
|
+
# etc. Function-type projection operators (`params_of[F]` /
|
|
58
|
+
# `return_of[F]`) and class projections (`instance_type[C]`)
|
|
59
|
+
# would unlock these — they remain deferred in core.
|
|
60
|
+
class TypescriptUtilityTypes < Rigor::Plugin::Base
|
|
61
|
+
module Resolvers
|
|
62
|
+
# Common helper: extract a single-arg utility-type Generic
|
|
63
|
+
# whose head matches `name`. Returns the resolved
|
|
64
|
+
# argument's `Rigor::Type` or `nil` (so the chain falls
|
|
65
|
+
# through).
|
|
66
|
+
module SingleArg
|
|
67
|
+
def self.resolve(node, scope, name)
|
|
68
|
+
return nil unless node.is_a?(Rigor::TypeNode::Generic)
|
|
69
|
+
return nil unless node.head == name
|
|
70
|
+
return nil unless node.args.size == 1
|
|
71
|
+
|
|
72
|
+
scope.resolver.resolve(node.args[0], scope)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Common helper for two-arg utility types.
|
|
77
|
+
module TwoArg
|
|
78
|
+
def self.resolve(node, scope, name)
|
|
79
|
+
return nil unless node.is_a?(Rigor::TypeNode::Generic)
|
|
80
|
+
return nil unless node.head == name
|
|
81
|
+
return nil unless node.args.size == 2
|
|
82
|
+
|
|
83
|
+
[scope.resolver.resolve(node.args[0], scope),
|
|
84
|
+
scope.resolver.resolve(node.args[1], scope)]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# `Pick<T, K>` → `pick_of[T, K]`.
|
|
89
|
+
class Pick < Rigor::Plugin::TypeNodeResolver
|
|
90
|
+
def resolve(node, scope)
|
|
91
|
+
resolved = TwoArg.resolve(node, scope, "Pick")
|
|
92
|
+
return nil if resolved.nil?
|
|
93
|
+
|
|
94
|
+
t_type, k_type = resolved
|
|
95
|
+
return nil if t_type.nil? || k_type.nil?
|
|
96
|
+
|
|
97
|
+
Rigor::Type::Combinator.pick_of(t_type, k_type)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# `Omit<T, K>` → `omit_of[T, K]`.
|
|
102
|
+
class Omit < Rigor::Plugin::TypeNodeResolver
|
|
103
|
+
def resolve(node, scope)
|
|
104
|
+
resolved = TwoArg.resolve(node, scope, "Omit")
|
|
105
|
+
return nil if resolved.nil?
|
|
106
|
+
|
|
107
|
+
t_type, k_type = resolved
|
|
108
|
+
return nil if t_type.nil? || k_type.nil?
|
|
109
|
+
|
|
110
|
+
Rigor::Type::Combinator.omit_of(t_type, k_type)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# `Partial<T>` → `partial_of[T]`.
|
|
115
|
+
class Partial < Rigor::Plugin::TypeNodeResolver
|
|
116
|
+
def resolve(node, scope)
|
|
117
|
+
t_type = SingleArg.resolve(node, scope, "Partial")
|
|
118
|
+
return nil if t_type.nil?
|
|
119
|
+
|
|
120
|
+
Rigor::Type::Combinator.partial_of(t_type)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# `Required<T>` → `required_of[T]`.
|
|
125
|
+
class Required < Rigor::Plugin::TypeNodeResolver
|
|
126
|
+
def resolve(node, scope)
|
|
127
|
+
t_type = SingleArg.resolve(node, scope, "Required")
|
|
128
|
+
return nil if t_type.nil?
|
|
129
|
+
|
|
130
|
+
Rigor::Type::Combinator.required_of(t_type)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# `Readonly<T>` → `readonly_of[T]`.
|
|
135
|
+
class Readonly < Rigor::Plugin::TypeNodeResolver
|
|
136
|
+
def resolve(node, scope)
|
|
137
|
+
t_type = SingleArg.resolve(node, scope, "Readonly")
|
|
138
|
+
return nil if t_type.nil?
|
|
139
|
+
|
|
140
|
+
Rigor::Type::Combinator.readonly_of(t_type)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
manifest(
|
|
146
|
+
id: "typescript-utility-types",
|
|
147
|
+
version: "0.1.0",
|
|
148
|
+
description: "Maps TypeScript-canonical utility-type aliases " \
|
|
149
|
+
"(Pick, Omit, Partial, Required, Readonly) onto the " \
|
|
150
|
+
"Rigor-canonical shape-projection type functions.",
|
|
151
|
+
type_node_resolvers: [
|
|
152
|
+
Resolvers::Pick.new,
|
|
153
|
+
Resolvers::Omit.new,
|
|
154
|
+
Resolvers::Partial.new,
|
|
155
|
+
Resolvers::Required.new,
|
|
156
|
+
Resolvers::Readonly.new
|
|
157
|
+
]
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
register(TypescriptUtilityTypes)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Gem entry point. Required by Rigor's plugin loader when
|
|
4
|
+
# `.rigor.yml` lists `rigor-typescript-utility-types` under
|
|
5
|
+
# `plugins:`. The loader expects this `require` to side-effect
|
|
6
|
+
# a call to `Rigor::Plugin.register`, which the body of
|
|
7
|
+
# `lib/rigor/plugin/typescript_utility_types.rb` performs at
|
|
8
|
+
# load time.
|
|
9
|
+
require_relative "rigor/plugin/typescript_utility_types"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Rigor
|
|
2
|
+
module Analysis
|
|
3
|
+
# ADR-22 Slice 1 — per-project baseline filter.
|
|
4
|
+
# See lib/rigor/analysis/baseline.rb for the full contract.
|
|
5
|
+
class Baseline
|
|
6
|
+
class LoadError < ::StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Load a baseline file from disk.
|
|
10
|
+
# Returns `nil` when `path` is nil (no baseline configured).
|
|
11
|
+
# Raises {LoadError} on malformed or unsupported content.
|
|
12
|
+
def self.load: (::String? path) -> Baseline?
|
|
13
|
+
|
|
14
|
+
# Build a baseline from a current run's diagnostic stream.
|
|
15
|
+
def self.from_diagnostics: (untyped diagnostics, ?match_mode: :rule | :message) -> Baseline
|
|
16
|
+
|
|
17
|
+
attr_reader buckets: untyped
|
|
18
|
+
|
|
19
|
+
def initialize: (untyped buckets) -> void
|
|
20
|
+
|
|
21
|
+
# Apply the baseline filter. Returns [surfaced_diagnostics, silenced_count].
|
|
22
|
+
def filter: (untyped diagnostics) -> [untyped, ::Integer]
|
|
23
|
+
|
|
24
|
+
# Report bucket-level drift for each recorded baseline bucket.
|
|
25
|
+
def audit: (untyped diagnostics) -> untyped
|
|
26
|
+
|
|
27
|
+
# Return a new Baseline with the given buckets removed.
|
|
28
|
+
def without: (untyped buckets_to_drop) -> Baseline
|
|
29
|
+
|
|
30
|
+
# Serialise to a YAML string.
|
|
31
|
+
def to_yaml: () -> ::String
|
|
32
|
+
|
|
33
|
+
# Number of recorded buckets.
|
|
34
|
+
def size: () -> ::Integer
|
|
35
|
+
|
|
36
|
+
def empty?: () -> bool
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/sig/rigor/environment.rbs
CHANGED
|
@@ -15,11 +15,12 @@ module Rigor
|
|
|
15
15
|
attr_reader hkt_registry: untyped?
|
|
16
16
|
|
|
17
17
|
def self.default: () -> Environment
|
|
18
|
-
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> Environment
|
|
18
|
+
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> Environment
|
|
19
19
|
|
|
20
|
-
def initialize: (?class_registry: ClassRegistry, ?rbs_loader: RbsLoader?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> void
|
|
20
|
+
def initialize: (?class_registry: ClassRegistry, ?rbs_loader: RbsLoader?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> void
|
|
21
21
|
def rbs_extended_reporter: () -> untyped?
|
|
22
22
|
def boundary_cross_reporter: () -> untyped?
|
|
23
|
+
def source_rbs_synthesis_reporter: () -> untyped?
|
|
23
24
|
def attach_reporters!: (rbs_extended_reporter: untyped?, boundary_cross_reporter: untyped?) -> nil
|
|
24
25
|
def nominal_for_name: (String | Symbol name) -> Type::Nominal?
|
|
25
26
|
def singleton_for_name: (String | Symbol name) -> Type::Singleton?
|
data/sig/rigor/type.rbs
CHANGED
|
@@ -266,6 +266,10 @@ module Rigor
|
|
|
266
266
|
def self?.non_numeric_string: () -> Refined
|
|
267
267
|
def self?.literal_string_carrier?: (Type::t refined) -> bool
|
|
268
268
|
def self?.literal_string_compatible?: (Type::t type) -> bool
|
|
269
|
+
def self?.non_empty_string_compatible?: (Type::t type) -> bool
|
|
270
|
+
def self?.non_empty_string_difference?: (Type::t diff) -> bool
|
|
271
|
+
def self?.non_zero_int_compatible?: (Type::t type) -> bool
|
|
272
|
+
def self?.non_zero_int_difference?: (Type::t diff) -> bool
|
|
269
273
|
def self?.intersection: (*Type::t members) -> Type::t
|
|
270
274
|
def self?.non_empty_lowercase_string: () -> Type::t
|
|
271
275
|
def self?.non_empty_uppercase_string: () -> Type::t
|
data/sig/rigor.rbs
CHANGED
|
@@ -12,6 +12,8 @@ module Rigor
|
|
|
12
12
|
attr_reader baseline_path: String?
|
|
13
13
|
|
|
14
14
|
def self.load: (?String path) -> Configuration
|
|
15
|
+
def self.discover: () -> String?
|
|
16
|
+
def self.load_with_includes: (String path, ?visited: Set[String]) -> Hash[String, untyped]
|
|
15
17
|
def initialize: (?Hash[String, untyped] data) -> void
|
|
16
18
|
def to_h: () -> Hash[String, untyped]
|
|
17
19
|
end
|