rigortype 0.1.10 → 0.1.12

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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rigor/analysis/baseline.rb +51 -15
  3. data/lib/rigor/analysis/erb_template_detector.rb +38 -0
  4. data/lib/rigor/analysis/runner.rb +6 -1
  5. data/lib/rigor/analysis/worker_session.rb +6 -1
  6. data/lib/rigor/cli/baseline_command.rb +4 -3
  7. data/lib/rigor/cli/plugins_command.rb +308 -0
  8. data/lib/rigor/cli/plugins_renderer.rb +173 -0
  9. data/lib/rigor/cli.rb +44 -3
  10. data/lib/rigor/inference/block_parameter_binder.rb +35 -0
  11. data/lib/rigor/inference/expression_typer.rb +69 -30
  12. data/lib/rigor/inference/indexed_narrowing.rb +187 -0
  13. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +24 -0
  14. data/lib/rigor/inference/method_dispatcher.rb +23 -0
  15. data/lib/rigor/inference/mutation_widening.rb +285 -0
  16. data/lib/rigor/inference/narrowing.rb +72 -4
  17. data/lib/rigor/inference/scope_indexer.rb +409 -12
  18. data/lib/rigor/inference/statement_evaluator.rb +256 -4
  19. data/lib/rigor/scope.rb +181 -4
  20. data/lib/rigor/version.rb +1 -1
  21. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +190 -0
  22. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +189 -0
  23. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +81 -0
  24. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +142 -0
  25. data/plugins/rigor-actioncable/lib/rigor-actioncable.rb +3 -0
  26. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +199 -0
  27. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +398 -0
  28. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +86 -0
  29. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +183 -0
  30. data/plugins/rigor-actionmailer/lib/rigor-actionmailer.rb +3 -0
  31. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +713 -0
  32. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +201 -0
  33. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +226 -0
  34. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +261 -0
  35. data/plugins/rigor-actionpack/lib/rigor-actionpack.rb +3 -0
  36. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +114 -0
  37. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +177 -0
  38. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +65 -0
  39. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +117 -0
  40. data/plugins/rigor-activejob/lib/rigor-activejob.rb +3 -0
  41. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +283 -0
  42. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/inflector.rb +114 -0
  43. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +561 -0
  44. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +194 -0
  45. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +250 -0
  46. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +98 -0
  47. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +590 -0
  48. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +8 -0
  49. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +182 -0
  50. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +78 -0
  51. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +162 -0
  52. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +43 -0
  53. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +170 -0
  54. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +8 -0
  55. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +37 -0
  56. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +20 -0
  57. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +478 -0
  58. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +108 -0
  59. data/plugins/rigor-devise/lib/rigor-devise.rb +8 -0
  60. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +285 -0
  61. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +124 -0
  62. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +8 -0
  63. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +116 -0
  64. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +8 -0
  65. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +341 -0
  66. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +120 -0
  67. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +8 -0
  68. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +120 -0
  69. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +85 -0
  70. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +7 -0
  71. data/plugins/rigor-dry-validation/sig/dry_validation.rbs +25 -0
  72. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +177 -0
  73. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +242 -0
  74. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +56 -0
  75. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +174 -0
  76. data/plugins/rigor-factorybot/lib/rigor-factorybot.rb +3 -0
  77. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +409 -0
  78. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +114 -0
  79. data/plugins/rigor-graphql/lib/rigor-graphql.rb +8 -0
  80. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +124 -0
  81. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +111 -0
  82. data/plugins/rigor-hanami/lib/rigor-hanami.rb +3 -0
  83. data/plugins/rigor-hanami/sig/hanami_action.rbs +78 -0
  84. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +302 -0
  85. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +72 -0
  86. data/plugins/rigor-minitest/lib/rigor-minitest.rb +3 -0
  87. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +194 -0
  88. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +140 -0
  89. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +65 -0
  90. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +130 -0
  91. data/plugins/rigor-pundit/lib/rigor-pundit.rb +3 -0
  92. data/plugins/rigor-rails/lib/rigor-rails.rb +31 -0
  93. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +353 -0
  94. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +108 -0
  95. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +138 -0
  96. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +175 -0
  97. data/plugins/rigor-rails-i18n/lib/rigor-rails-i18n.rb +3 -0
  98. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +350 -0
  99. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +264 -0
  100. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +100 -0
  101. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +175 -0
  102. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +164 -0
  103. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1538 -0
  104. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +235 -0
  105. data/plugins/rigor-rails-routes/lib/rigor-rails-routes.rb +3 -0
  106. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +163 -0
  107. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +24 -0
  108. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +110 -0
  109. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +200 -0
  110. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +170 -0
  111. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +233 -0
  112. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +190 -0
  113. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +188 -0
  114. data/plugins/rigor-rspec/lib/rigor-rspec.rb +3 -0
  115. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +128 -0
  116. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +60 -0
  117. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +75 -0
  118. data/plugins/rigor-rspec-rails/lib/rigor-rspec-rails.rb +3 -0
  119. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +266 -0
  120. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +113 -0
  121. data/plugins/rigor-shoulda-matchers/lib/rigor-shoulda-matchers.rb +3 -0
  122. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +152 -0
  123. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +190 -0
  124. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +61 -0
  125. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +124 -0
  126. data/plugins/rigor-sidekiq/lib/rigor-sidekiq.rb +3 -0
  127. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +85 -0
  128. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +8 -0
  129. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +108 -0
  130. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +250 -0
  131. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +95 -0
  132. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +226 -0
  133. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +28 -0
  134. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +154 -0
  135. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +100 -0
  136. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +323 -0
  137. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +660 -0
  138. data/plugins/rigor-sorbet/lib/rigor-sorbet.rb +3 -0
  139. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +209 -0
  140. data/plugins/rigor-statesman/lib/rigor-statesman.rb +8 -0
  141. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +163 -0
  142. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +9 -0
  143. data/sig/rigor/scope.rbs +22 -0
  144. metadata +157 -1
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "devise_routes"
4
+ require_relative "doorkeeper_routes"
5
+
6
+ module Rigor
7
+ module Plugin
8
+ class RailsRoutes < Rigor::Plugin::Base
9
+ # Frozen catalogue of route helpers parsed from
10
+ # `config/routes.rb`. Each entry maps a helper name
11
+ # (`users_path`, `edit_user_path`, …) to the metadata
12
+ # downstream consumers and the analyzer's per-call
13
+ # validation need:
14
+ #
15
+ # - `arity`: number of positional arguments the helper
16
+ # takes. `users_path` → 0; `user_path(:id)` → 1;
17
+ # `user_post_path(:user_id, :id)` → 2.
18
+ # - `path`: the path template Rails generates
19
+ # (`/users/:user_id/posts/:id`).
20
+ # - `http_method`: `:get` / `:post` / `:patch` / `:put` /
21
+ # `:delete` for the canonical action; `nil` for
22
+ # helpers that span multiple methods (a `resources`
23
+ # show helper isn't HTTP-method-specific in the
24
+ # helper sense — it's path-sensitive only).
25
+ # - `action`: `:index` / `:show` / `:new` / `:edit` /
26
+ # `:create` / `:update` / `:destroy` for resourceful
27
+ # routes; `:custom` for explicit `get`/`post`/etc.;
28
+ # `:root` for the root route.
29
+ #
30
+ # Both `_path` and `_url` forms share the same metadata —
31
+ # the table records each helper twice (once with `_path`,
32
+ # once with `_url`) for `O(1)` lookup at the call site.
33
+ class HelperTable
34
+ Entry = Data.define(:name, :arity, :path, :http_method, :action)
35
+
36
+ attr_reader :entries, :custom_helpers, :devise_resources
37
+
38
+ # @param entries [Array<Entry>] freshly built; the
39
+ # factory below is the canonical construction path.
40
+ # @param custom_helpers [Enumerable<String>] names of
41
+ # project-defined helper methods (typically pulled
42
+ # from `app/helpers/**/*.rb` by
43
+ # {HelperDiscoverer}) that the analyzer should treat
44
+ # as known — they do NOT correspond to a route but
45
+ # their presence MUST NOT fire `unknown-helper`. No
46
+ # arity / path metadata is recorded; the analyzer
47
+ # skips the route-side checks for these names.
48
+ # @param devise_resources [Enumerable<String>] resource
49
+ # names declared via `devise_for :resource` (already
50
+ # singularised, e.g. `"user"`). Used to recognise the
51
+ # dynamic OmniAuth helper family
52
+ # (`<resource>_<provider>_omniauth_(authorize|callback)_(path|url)`)
53
+ # whose provider segment is supplied at runtime — no
54
+ # per-name entry is registered for them but they MUST
55
+ # NOT fire `unknown-helper` either.
56
+ def initialize(entries, custom_helpers: [], devise_resources: [])
57
+ @entries = entries.freeze
58
+ # Multimap: a single helper name can map to multiple
59
+ # entries when an uncountable-noun resource registers
60
+ # both an arity-0 index helper and an arity-1 show
61
+ # helper under the same `news_path` name. `find`
62
+ # returns the first entry (preserving the previous
63
+ # API); `accepts_arity?` checks against every entry.
64
+ @by_name = entries.group_by(&:name).transform_values(&:freeze).freeze
65
+ @custom_helpers = custom_helpers.to_set.freeze
66
+ @devise_resources = devise_resources.to_set(&:to_s).freeze
67
+ freeze
68
+ end
69
+
70
+ # @return [Entry, nil] First matching entry; for the
71
+ # uncountable-noun case this is the index helper
72
+ # (the show helper is also registered but starts
73
+ # second).
74
+ def find(helper_name)
75
+ @by_name[helper_name.to_s]&.first
76
+ end
77
+
78
+ # @return [Boolean]
79
+ def known?(helper_name)
80
+ @by_name.key?(helper_name.to_s)
81
+ end
82
+
83
+ # True when `helper_name` is either a registered route
84
+ # helper, a discovered project-defined custom helper,
85
+ # OR a dynamic OmniAuth-shaped helper for one of the
86
+ # declared `devise_for` resources. The
87
+ # `unknown-helper` rule consults this predicate to
88
+ # decide whether to fire — `known?` alone misses
89
+ # custom helpers and OmniAuth providers, which would
90
+ # then false-fire on canonical Rails-app patterns.
91
+ def recognised?(helper_name)
92
+ name = helper_name.to_s
93
+ return true if @by_name.key?(name)
94
+ return true if @custom_helpers.include?(name)
95
+
96
+ omniauth_match?(name)
97
+ end
98
+
99
+ # `<resource>_<provider>_omniauth_(authorize|callback)_(path|url)` — when
100
+ # `<resource>` matches a declared `devise_for` resource
101
+ # the helper is dynamic-provider Devise OmniAuth. The
102
+ # provider segment is opaque to a static parser, so we
103
+ # accept any non-empty token between the resource and
104
+ # the omniauth suffix.
105
+ def omniauth_match?(name)
106
+ return false if @devise_resources.empty?
107
+
108
+ DeviseRoutes::OMNIAUTH_SUFFIXES.any? do |suffix|
109
+ next false unless name.end_with?(suffix)
110
+
111
+ stem = name.delete_suffix(suffix)
112
+ @devise_resources.any? do |resource|
113
+ stem.start_with?("#{resource}_") && stem.length > resource.length + 1
114
+ end
115
+ end
116
+ end
117
+
118
+ # @return [Boolean] true when any entry under this
119
+ # helper name accepts the given positional arity.
120
+ #
121
+ # Rails helpers have the signature `helper(*segments, options = {})`,
122
+ # so `expected + 1` is always valid — the extra argument is treated
123
+ # as a query-params/options hash (e.g. `users_path(page: 2)` or
124
+ # `user_path(@u, pagination_params(...))`).
125
+ def accepts_arity?(helper_name, arity)
126
+ (@by_name[helper_name.to_s] || []).any? { |entry| entry.arity == arity || entry.arity + 1 == arity }
127
+ end
128
+
129
+ # @return [Array<Integer>] all accepted positional
130
+ # arities for a helper name. Empty when unknown.
131
+ def acceptable_arities(helper_name)
132
+ (@by_name[helper_name.to_s] || []).map(&:arity).uniq
133
+ end
134
+
135
+ # All helper names — used by the "did you mean" suggester.
136
+ def names
137
+ @by_name.keys
138
+ end
139
+
140
+ def empty?
141
+ @entries.empty?
142
+ end
143
+
144
+ def size
145
+ @entries.size
146
+ end
147
+
148
+ def to_h
149
+ # Plain dump for fact-store publishing (ADR-9). Each
150
+ # name serialises as a small Hash for the FIRST entry
151
+ # under that name, with `acceptable_arities` carrying
152
+ # the full arity set so cross-plugin consumers can
153
+ # honour the uncountable-noun multi-arity case.
154
+ @by_name.transform_values do |group|
155
+ entry = group.first
156
+ { name: entry.name, arity: entry.arity, path: entry.path,
157
+ http_method: entry.http_method, action: entry.action,
158
+ acceptable_arities: group.map(&:arity).uniq }
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end