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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/rigor/analysis/baseline.rb +51 -15
  4. data/lib/rigor/analysis/runner.rb +67 -9
  5. data/lib/rigor/analysis/worker_session.rb +13 -4
  6. data/lib/rigor/cache/rbs_descriptor.rb +21 -2
  7. data/lib/rigor/cache/rbs_environment.rb +2 -1
  8. data/lib/rigor/cli/annotate_command.rb +57 -7
  9. data/lib/rigor/cli/baseline_command.rb +4 -3
  10. data/lib/rigor/cli/coverage_command.rb +126 -0
  11. data/lib/rigor/cli/coverage_renderer.rb +162 -0
  12. data/lib/rigor/cli/coverage_report.rb +75 -0
  13. data/lib/rigor/cli/mcp_command.rb +70 -0
  14. data/lib/rigor/cli.rb +88 -5
  15. data/lib/rigor/environment/rbs_loader.rb +46 -5
  16. data/lib/rigor/environment/reporters.rb +3 -2
  17. data/lib/rigor/environment.rb +159 -4
  18. data/lib/rigor/inference/def_return_typer.rb +98 -0
  19. data/lib/rigor/inference/expression_typer.rb +143 -12
  20. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +5 -0
  21. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +62 -15
  22. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +115 -7
  23. data/lib/rigor/inference/precision_scanner.rb +131 -0
  24. data/lib/rigor/inference/statement_evaluator.rb +26 -2
  25. data/lib/rigor/mcp/loop.rb +43 -0
  26. data/lib/rigor/mcp/server.rb +263 -0
  27. data/lib/rigor/mcp.rb +16 -0
  28. data/lib/rigor/plugin/base.rb +28 -5
  29. data/lib/rigor/plugin/manifest.rb +33 -5
  30. data/lib/rigor/plugin/registry.rb +21 -0
  31. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +51 -0
  32. data/lib/rigor/sig_gen/generator.rb +150 -75
  33. data/lib/rigor/type/combinator.rb +57 -0
  34. data/lib/rigor/version.rb +1 -1
  35. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +190 -0
  36. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +189 -0
  37. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +81 -0
  38. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +142 -0
  39. data/plugins/rigor-actioncable/lib/rigor-actioncable.rb +3 -0
  40. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +178 -0
  41. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +310 -0
  42. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +76 -0
  43. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +177 -0
  44. data/plugins/rigor-actionmailer/lib/rigor-actionmailer.rb +3 -0
  45. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +589 -0
  46. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +150 -0
  47. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +123 -0
  48. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +247 -0
  49. data/plugins/rigor-actionpack/lib/rigor-actionpack.rb +3 -0
  50. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +114 -0
  51. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +177 -0
  52. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +65 -0
  53. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +117 -0
  54. data/plugins/rigor-activejob/lib/rigor-activejob.rb +3 -0
  55. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +273 -0
  56. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/inflector.rb +114 -0
  57. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +561 -0
  58. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +194 -0
  59. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +240 -0
  60. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +94 -0
  61. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +514 -0
  62. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +8 -0
  63. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +182 -0
  64. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +78 -0
  65. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +162 -0
  66. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +43 -0
  67. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +170 -0
  68. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +8 -0
  69. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +34 -0
  70. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +20 -0
  71. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +463 -0
  72. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +108 -0
  73. data/plugins/rigor-devise/lib/rigor-devise.rb +8 -0
  74. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +285 -0
  75. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +124 -0
  76. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +8 -0
  77. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +116 -0
  78. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +8 -0
  79. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +341 -0
  80. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +120 -0
  81. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +8 -0
  82. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +120 -0
  83. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +85 -0
  84. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +7 -0
  85. data/plugins/rigor-dry-validation/sig/dry_validation.rbs +25 -0
  86. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +177 -0
  87. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +242 -0
  88. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +56 -0
  89. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +174 -0
  90. data/plugins/rigor-factorybot/lib/rigor-factorybot.rb +3 -0
  91. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +409 -0
  92. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +114 -0
  93. data/plugins/rigor-graphql/lib/rigor-graphql.rb +8 -0
  94. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +124 -0
  95. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +111 -0
  96. data/plugins/rigor-hanami/lib/rigor-hanami.rb +3 -0
  97. data/plugins/rigor-hanami/sig/hanami_action.rbs +78 -0
  98. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +302 -0
  99. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +72 -0
  100. data/plugins/rigor-minitest/lib/rigor-minitest.rb +3 -0
  101. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +194 -0
  102. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +140 -0
  103. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +65 -0
  104. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +130 -0
  105. data/plugins/rigor-pundit/lib/rigor-pundit.rb +3 -0
  106. data/plugins/rigor-rails/lib/rigor-rails.rb +31 -0
  107. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +277 -0
  108. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +108 -0
  109. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +138 -0
  110. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +167 -0
  111. data/plugins/rigor-rails-i18n/lib/rigor-rails-i18n.rb +3 -0
  112. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +161 -0
  113. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +103 -0
  114. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +490 -0
  115. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +158 -0
  116. data/plugins/rigor-rails-routes/lib/rigor-rails-routes.rb +3 -0
  117. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +163 -0
  118. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +24 -0
  119. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +110 -0
  120. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +200 -0
  121. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +170 -0
  122. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +233 -0
  123. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +190 -0
  124. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +188 -0
  125. data/plugins/rigor-rspec/lib/rigor-rspec.rb +3 -0
  126. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +128 -0
  127. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +60 -0
  128. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +75 -0
  129. data/plugins/rigor-rspec-rails/lib/rigor-rspec-rails.rb +3 -0
  130. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +266 -0
  131. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +113 -0
  132. data/plugins/rigor-shoulda-matchers/lib/rigor-shoulda-matchers.rb +3 -0
  133. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +152 -0
  134. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +190 -0
  135. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +61 -0
  136. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +124 -0
  137. data/plugins/rigor-sidekiq/lib/rigor-sidekiq.rb +3 -0
  138. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +85 -0
  139. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +8 -0
  140. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +108 -0
  141. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +250 -0
  142. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +95 -0
  143. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +226 -0
  144. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +28 -0
  145. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +154 -0
  146. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +100 -0
  147. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +323 -0
  148. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +660 -0
  149. data/plugins/rigor-sorbet/lib/rigor-sorbet.rb +3 -0
  150. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +209 -0
  151. data/plugins/rigor-statesman/lib/rigor-statesman.rb +8 -0
  152. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +163 -0
  153. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +9 -0
  154. data/sig/rigor/analysis/baseline.rbs +39 -0
  155. data/sig/rigor/environment.rbs +3 -2
  156. data/sig/rigor/type.rbs +4 -0
  157. data/sig/rigor.rbs +2 -0
  158. 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
@@ -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