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.
- checksums.yaml +4 -4
- data/lib/rigor/analysis/baseline.rb +51 -15
- data/lib/rigor/analysis/erb_template_detector.rb +38 -0
- data/lib/rigor/analysis/runner.rb +6 -1
- data/lib/rigor/analysis/worker_session.rb +6 -1
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/plugins_command.rb +308 -0
- data/lib/rigor/cli/plugins_renderer.rb +173 -0
- data/lib/rigor/cli.rb +44 -3
- data/lib/rigor/inference/block_parameter_binder.rb +35 -0
- data/lib/rigor/inference/expression_typer.rb +69 -30
- data/lib/rigor/inference/indexed_narrowing.rb +187 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +24 -0
- data/lib/rigor/inference/method_dispatcher.rb +23 -0
- data/lib/rigor/inference/mutation_widening.rb +285 -0
- data/lib/rigor/inference/narrowing.rb +72 -4
- data/lib/rigor/inference/scope_indexer.rb +409 -12
- data/lib/rigor/inference/statement_evaluator.rb +256 -4
- data/lib/rigor/scope.rb +181 -4
- 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 +199 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +398 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +86 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +183 -0
- data/plugins/rigor-actionmailer/lib/rigor-actionmailer.rb +3 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +713 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +201 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +226 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +261 -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 +283 -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 +250 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +98 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +590 -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 +37 -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 +478 -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 +353 -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 +175 -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 +350 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +264 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +100 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +175 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +164 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1538 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +235 -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/scope.rbs +22 -0
- metadata +157 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ActiveRecord::Relation — generic signature bundled by
|
|
2
|
+
# rigor-activerecord (ADR-25 plugin-contributed RBS).
|
|
3
|
+
#
|
|
4
|
+
# Purpose: give `has_many` / `has_and_belongs_to_many` accessors,
|
|
5
|
+
# `Model.where(...)` / `Model.all` / `Model.order(...)` and
|
|
6
|
+
# user-declared `scope`s a precise *element* type. The plugin
|
|
7
|
+
# contributes `ActiveRecord::Relation[Model]` for those call
|
|
8
|
+
# sites; this file is what makes the chained relation methods
|
|
9
|
+
# (`.where`, `.order`, `.first`, `.each { |m| ... }`, …) resolve.
|
|
10
|
+
#
|
|
11
|
+
# This is NOT a faithful transcription of Rails' Relation API —
|
|
12
|
+
# it is the surface Rigor needs to type-check idiomatic relation
|
|
13
|
+
# usage WITHOUT false positives. Two design rules:
|
|
14
|
+
#
|
|
15
|
+
# - Chain methods return `self`, so `Relation[Model]` survives an
|
|
16
|
+
# arbitrarily long query chain.
|
|
17
|
+
# - The method list is deliberately generous. A method that is
|
|
18
|
+
# genuinely on `ActiveRecord::Relation` but missing here would
|
|
19
|
+
# surface as a false `call.undefined-method` once the plugin
|
|
20
|
+
# has typed the receiver — so the list errs toward inclusion.
|
|
21
|
+
# `include Enumerable[Elem]` carries the whole iteration
|
|
22
|
+
# surface; the class body only adds relation-specific methods
|
|
23
|
+
# and the ones whose argument shape differs from Enumerable's.
|
|
24
|
+
#
|
|
25
|
+
# If the project supplies its own ActiveRecord RBS (e.g. via
|
|
26
|
+
# `rbs collection`), that definition conflicts with this one and
|
|
27
|
+
# the conflict degrades through Rigor's plugin-RBS failure memo
|
|
28
|
+
# (ADR-25 WD4) — the richer upstream definition simply wins.
|
|
29
|
+
|
|
30
|
+
module ActiveRecord
|
|
31
|
+
class Relation[Elem]
|
|
32
|
+
include Enumerable[Elem]
|
|
33
|
+
|
|
34
|
+
# `Enumerable` only *requires* `each` (the includer supplies
|
|
35
|
+
# it); it is declared here so `relation.each { |m| ... }`
|
|
36
|
+
# resolves and the block parameter carries the element type.
|
|
37
|
+
def each: () { (Elem) -> void } -> self
|
|
38
|
+
| () -> Enumerator[Elem, self]
|
|
39
|
+
|
|
40
|
+
# --- query builders (return another relation) ---
|
|
41
|
+
def where: (*untyped) ?{ () -> untyped } -> self
|
|
42
|
+
def rewhere: (*untyped) -> self
|
|
43
|
+
def not: (*untyped) -> self
|
|
44
|
+
def or: (untyped) -> self
|
|
45
|
+
def and: (untyped) -> self
|
|
46
|
+
def order: (*untyped) -> self
|
|
47
|
+
def reorder: (*untyped) -> self
|
|
48
|
+
def in_order_of: (*untyped) -> self
|
|
49
|
+
def limit: (untyped) -> self
|
|
50
|
+
def offset: (untyped) -> self
|
|
51
|
+
def distinct: (?untyped) -> self
|
|
52
|
+
def group: (*untyped) -> self
|
|
53
|
+
def regroup: (*untyped) -> self
|
|
54
|
+
def having: (*untyped) -> self
|
|
55
|
+
def includes: (*untyped) -> self
|
|
56
|
+
def preload: (*untyped) -> self
|
|
57
|
+
def eager_load: (*untyped) -> self
|
|
58
|
+
def joins: (*untyped) -> self
|
|
59
|
+
def left_joins: (*untyped) -> self
|
|
60
|
+
def left_outer_joins: (*untyped) -> self
|
|
61
|
+
def references: (*untyped) -> self
|
|
62
|
+
def select: (*untyped) -> self
|
|
63
|
+
def reselect: (*untyped) -> self
|
|
64
|
+
def unscope: (*untyped) -> self
|
|
65
|
+
def merge: (untyped) -> self
|
|
66
|
+
def except: (*untyped) -> self
|
|
67
|
+
def only: (*untyped) -> self
|
|
68
|
+
def extending: (*untyped) -> self
|
|
69
|
+
def from: (untyped, ?untyped) -> self
|
|
70
|
+
def lock: (?untyped) -> self
|
|
71
|
+
def readonly: (?untyped) -> self
|
|
72
|
+
def strict_loading: (?untyped) -> self
|
|
73
|
+
def create_with: (untyped) -> self
|
|
74
|
+
def none: () -> self
|
|
75
|
+
def all: () -> self
|
|
76
|
+
def unscoped: () -> self
|
|
77
|
+
def default_scoped: () -> self
|
|
78
|
+
def annotate: (*untyped) -> self
|
|
79
|
+
def optimizer_hints: (*untyped) -> self
|
|
80
|
+
def excluding: (*untyped) -> self
|
|
81
|
+
def without: (*untyped) -> self
|
|
82
|
+
def with: (*untyped) -> self
|
|
83
|
+
def scoping: () { () -> untyped } -> untyped
|
|
84
|
+
|
|
85
|
+
# --- finders (return an element) ---
|
|
86
|
+
def find: (*untyped) -> Elem
|
|
87
|
+
def find_by: (*untyped) -> Elem?
|
|
88
|
+
def find_by!: (*untyped) -> Elem
|
|
89
|
+
def find_sole_by: (*untyped) -> Elem
|
|
90
|
+
def sole: () -> Elem
|
|
91
|
+
def first: () -> Elem?
|
|
92
|
+
| (Integer limit) -> Array[Elem]
|
|
93
|
+
def first!: () -> Elem
|
|
94
|
+
def last: () -> Elem?
|
|
95
|
+
| (Integer limit) -> Array[Elem]
|
|
96
|
+
def last!: () -> Elem
|
|
97
|
+
def take: () -> Elem?
|
|
98
|
+
| (Integer limit) -> Array[Elem]
|
|
99
|
+
def take!: () -> Elem
|
|
100
|
+
def second: () -> Elem?
|
|
101
|
+
def third: () -> Elem?
|
|
102
|
+
def fourth: () -> Elem?
|
|
103
|
+
def fifth: () -> Elem?
|
|
104
|
+
def forty_two: () -> Elem?
|
|
105
|
+
def second_to_last: () -> Elem?
|
|
106
|
+
def third_to_last: () -> Elem?
|
|
107
|
+
|
|
108
|
+
# --- batched enumeration ---
|
|
109
|
+
def find_each: (**untyped) ?{ (Elem) -> void } -> untyped
|
|
110
|
+
def find_in_batches: (**untyped) ?{ (Array[Elem]) -> void } -> untyped
|
|
111
|
+
def in_batches: (**untyped) ?{ (ActiveRecord::Relation[Elem]) -> void } -> untyped
|
|
112
|
+
|
|
113
|
+
# --- scalars / aggregates ---
|
|
114
|
+
def count: (*untyped) ?{ (Elem) -> untyped } -> untyped
|
|
115
|
+
def sum: (*untyped) ?{ (Elem) -> untyped } -> untyped
|
|
116
|
+
def average: (untyped) -> untyped
|
|
117
|
+
def minimum: (untyped) -> untyped
|
|
118
|
+
def maximum: (untyped) -> untyped
|
|
119
|
+
def calculate: (*untyped) -> untyped
|
|
120
|
+
def pluck: (*untyped) -> Array[untyped]
|
|
121
|
+
def pick: (*untyped) -> untyped
|
|
122
|
+
def ids: () -> Array[untyped]
|
|
123
|
+
def size: () -> Integer
|
|
124
|
+
def length: () -> Integer
|
|
125
|
+
def empty?: () -> bool
|
|
126
|
+
def any?: (*untyped) ?{ (Elem) -> boolish } -> bool
|
|
127
|
+
def none?: (*untyped) ?{ (Elem) -> boolish } -> bool
|
|
128
|
+
def one?: (*untyped) ?{ (Elem) -> boolish } -> bool
|
|
129
|
+
def many?: () ?{ (Elem) -> boolish } -> bool
|
|
130
|
+
def exists?: (*untyped) -> bool
|
|
131
|
+
def include?: (untyped) -> bool
|
|
132
|
+
def member?: (untyped) -> bool
|
|
133
|
+
|
|
134
|
+
# --- conversion / state ---
|
|
135
|
+
def to_a: () -> Array[Elem]
|
|
136
|
+
def to_ary: () -> Array[Elem]
|
|
137
|
+
def entries: () -> Array[Elem]
|
|
138
|
+
def load: (?untyped) -> self
|
|
139
|
+
def reload: () -> self
|
|
140
|
+
def reset: () -> self
|
|
141
|
+
def loaded?: () -> bool
|
|
142
|
+
def to_sql: () -> String
|
|
143
|
+
def explain: (*untyped) -> String
|
|
144
|
+
def cache_key: (?untyped) -> String
|
|
145
|
+
def cache_version: (?untyped) -> untyped
|
|
146
|
+
def to_json: (*untyped) -> String
|
|
147
|
+
def as_json: (?untyped) -> untyped
|
|
148
|
+
def blank?: () -> bool
|
|
149
|
+
def present?: () -> bool
|
|
150
|
+
def klass: () -> untyped
|
|
151
|
+
def model: () -> untyped
|
|
152
|
+
def table: () -> untyped
|
|
153
|
+
def arel: () -> untyped
|
|
154
|
+
|
|
155
|
+
# --- whole-relation persistence ---
|
|
156
|
+
def update_all: (untyped) -> Integer
|
|
157
|
+
def delete_all: () -> Integer
|
|
158
|
+
def destroy_all: () -> Array[Elem]
|
|
159
|
+
def update: (*untyped) -> untyped
|
|
160
|
+
def update!: (*untyped) -> untyped
|
|
161
|
+
def delete_by: (*untyped) -> Integer
|
|
162
|
+
def destroy_by: (*untyped) -> Array[Elem]
|
|
163
|
+
def touch_all: (*untyped) -> Integer
|
|
164
|
+
def insert_all: (untyped, **untyped) -> untyped
|
|
165
|
+
def insert_all!: (untyped, **untyped) -> untyped
|
|
166
|
+
def upsert_all: (untyped, **untyped) -> untyped
|
|
167
|
+
|
|
168
|
+
# --- instantiating builders (return an element) ---
|
|
169
|
+
def new: (*untyped) ?{ (Elem) -> void } -> Elem
|
|
170
|
+
def build: (*untyped) ?{ (Elem) -> void } -> Elem
|
|
171
|
+
def create: (*untyped) ?{ (Elem) -> void } -> Elem
|
|
172
|
+
def create!: (*untyped) ?{ (Elem) -> void } -> Elem
|
|
173
|
+
def find_or_create_by: (untyped) ?{ (Elem) -> void } -> Elem
|
|
174
|
+
def find_or_create_by!: (untyped) ?{ (Elem) -> void } -> Elem
|
|
175
|
+
def find_or_initialize_by: (untyped) ?{ (Elem) -> void } -> Elem
|
|
176
|
+
def create_or_find_by: (untyped) ?{ (Elem) -> void } -> Elem
|
|
177
|
+
def create_or_find_by!: (untyped) ?{ (Elem) -> void } -> Elem
|
|
178
|
+
def first_or_create: (?untyped) ?{ (Elem) -> void } -> Elem
|
|
179
|
+
def first_or_create!: (?untyped) ?{ (Elem) -> void } -> Elem
|
|
180
|
+
def first_or_initialize: (?untyped) ?{ (Elem) -> void } -> Elem
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class Activestorage < Rigor::Plugin::Base
|
|
8
|
+
# Per-file walker. Emits one `:info` `attachment-call`
|
|
9
|
+
# diagnostic per recognised attachment access on a
|
|
10
|
+
# known AR class. The diagnostic surfaces what the
|
|
11
|
+
# plugin recognised so users can verify the model →
|
|
12
|
+
# attachment mapping the plugin sees.
|
|
13
|
+
#
|
|
14
|
+
# No `:error` diagnostics in this slice — the
|
|
15
|
+
# `flow_contribution_for` return-type narrowing carries
|
|
16
|
+
# the type-checking value; surfacing unknown attachment
|
|
17
|
+
# names as errors requires a coupled receiver-class
|
|
18
|
+
# narrowing pass that the integration spec doesn't yet
|
|
19
|
+
# rely on. A future slice can add `unknown-attachment`
|
|
20
|
+
# similar to `rigor-activerecord`'s `unknown-column`.
|
|
21
|
+
class Analyzer
|
|
22
|
+
attr_reader :diagnostics
|
|
23
|
+
|
|
24
|
+
def initialize(path:, attachment_index:)
|
|
25
|
+
@path = path
|
|
26
|
+
@attachment_index = attachment_index
|
|
27
|
+
@diagnostics = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def analyze(root)
|
|
31
|
+
walk(root) { |node| visit_call(node) if node.is_a?(Prism::CallNode) }
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def walk(node, &)
|
|
38
|
+
return if node.nil?
|
|
39
|
+
|
|
40
|
+
yield node
|
|
41
|
+
node.compact_child_nodes.each { |child| walk(child, &) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def visit_call(node)
|
|
45
|
+
return unless node.receiver.is_a?(Prism::ConstantReadNode)
|
|
46
|
+
|
|
47
|
+
owner = node.receiver.name.to_s
|
|
48
|
+
attachments = @attachment_index.attachments_for(owner) ||
|
|
49
|
+
@attachment_index.attachments_for("::#{owner}")
|
|
50
|
+
return if attachments.nil?
|
|
51
|
+
|
|
52
|
+
# Only flag when the method matches a known
|
|
53
|
+
# attachment name (the `flow_contribution_for`
|
|
54
|
+
# tier provides the narrowing; the diagnostic just
|
|
55
|
+
# confirms the recognition).
|
|
56
|
+
attachment = attachments.find { |a| a[:name] == node.name.to_s }
|
|
57
|
+
return if attachment.nil?
|
|
58
|
+
|
|
59
|
+
push_info(node, "attachment-call",
|
|
60
|
+
"`#{owner}.#{attachment[:name]}` returns " \
|
|
61
|
+
"ActiveStorage::Attached::#{attachment[:kind] == :singular ? 'One' : 'Many'}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def push_info(node, rule, message)
|
|
65
|
+
location = node.location
|
|
66
|
+
@diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
67
|
+
path: @path,
|
|
68
|
+
line: location.start_line,
|
|
69
|
+
column: location.start_column + 1,
|
|
70
|
+
message: message,
|
|
71
|
+
severity: :info,
|
|
72
|
+
rule: rule
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class Activestorage < Rigor::Plugin::Base
|
|
8
|
+
# Walks the configured model search paths via the plugin's
|
|
9
|
+
# `IoBoundary`, parses each `.rb` file with Prism, and
|
|
10
|
+
# collects `has_one_attached` / `has_many_attached`
|
|
11
|
+
# declarations.
|
|
12
|
+
#
|
|
13
|
+
# Returns rows the {AttachmentIndex} consumes:
|
|
14
|
+
#
|
|
15
|
+
# { class_name: "User",
|
|
16
|
+
# attachments: [{ name: "avatar", kind: :singular },
|
|
17
|
+
# { name: "photos", kind: :collection }] }
|
|
18
|
+
#
|
|
19
|
+
# Limitations (intentional for v0.1.0 of the plugin):
|
|
20
|
+
#
|
|
21
|
+
# - The walker matches any class declaration that
|
|
22
|
+
# contains a `has_*_attached` call. Whether the class
|
|
23
|
+
# IS an ActiveRecord model is not verified here —
|
|
24
|
+
# `rigor-activerecord` provides that check via its
|
|
25
|
+
# own model index. The analyser is intentionally
|
|
26
|
+
# lenient so the plugin runs standalone in projects
|
|
27
|
+
# that haven't loaded `rigor-activerecord` yet.
|
|
28
|
+
# - Only Symbol-literal attachment names are recognised.
|
|
29
|
+
# `has_one_attached(args)` or computed names decline.
|
|
30
|
+
# - Modules (`class Admin::User`) are recognised; the
|
|
31
|
+
# resulting class name is the lexical path
|
|
32
|
+
# (`Admin::User`).
|
|
33
|
+
class AttachmentDiscoverer
|
|
34
|
+
ATTACHMENT_METHODS = {
|
|
35
|
+
has_one_attached: :singular,
|
|
36
|
+
has_many_attached: :collection
|
|
37
|
+
}.freeze
|
|
38
|
+
private_constant :ATTACHMENT_METHODS
|
|
39
|
+
|
|
40
|
+
def initialize(io_boundary:, search_paths:)
|
|
41
|
+
@io_boundary = io_boundary
|
|
42
|
+
@search_paths = search_paths
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def discover
|
|
46
|
+
rows = []
|
|
47
|
+
ruby_files_under(@search_paths).each do |path|
|
|
48
|
+
contents = read_safely(path)
|
|
49
|
+
next if contents.nil?
|
|
50
|
+
|
|
51
|
+
tree = Prism.parse(contents).value
|
|
52
|
+
walk(tree, []) do |class_name, attachments|
|
|
53
|
+
rows << { class_name: class_name, attachments: attachments } unless attachments.empty?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
rows
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def read_safely(path)
|
|
62
|
+
@io_boundary.read_file(path)
|
|
63
|
+
rescue Plugin::AccessDeniedError, Errno::ENOENT
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ruby_files_under(roots)
|
|
68
|
+
roots.flat_map do |root|
|
|
69
|
+
absolute = File.expand_path(root)
|
|
70
|
+
next [] unless File.directory?(absolute)
|
|
71
|
+
|
|
72
|
+
Dir.glob(File.join(absolute, "**", "*.rb"))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def walk(node, lexical_path, &)
|
|
77
|
+
return if node.nil?
|
|
78
|
+
|
|
79
|
+
case node
|
|
80
|
+
when Prism::ClassNode
|
|
81
|
+
visit_class(node, lexical_path, &)
|
|
82
|
+
when Prism::ModuleNode
|
|
83
|
+
visit_module(node, lexical_path, &)
|
|
84
|
+
else
|
|
85
|
+
node.compact_child_nodes.each { |child| walk(child, lexical_path, &) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def visit_class(node, lexical_path, &)
|
|
90
|
+
class_local_name = constant_path_name(node.constant_path)
|
|
91
|
+
return if class_local_name.nil?
|
|
92
|
+
|
|
93
|
+
full_name = (lexical_path + [class_local_name]).join("::")
|
|
94
|
+
attachments = collect_attachments(node.body)
|
|
95
|
+
yield full_name, attachments
|
|
96
|
+
|
|
97
|
+
inner_path = lexical_path + [class_local_name]
|
|
98
|
+
walk(node.body, inner_path, &) if node.body
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def visit_module(node, lexical_path, &)
|
|
102
|
+
module_local_name = constant_path_name(node.constant_path)
|
|
103
|
+
return if module_local_name.nil?
|
|
104
|
+
|
|
105
|
+
inner_path = lexical_path + [module_local_name]
|
|
106
|
+
walk(node.body, inner_path, &) if node.body
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def collect_attachments(body)
|
|
110
|
+
return [] if body.nil?
|
|
111
|
+
|
|
112
|
+
rows = []
|
|
113
|
+
body.compact_child_nodes.each do |node|
|
|
114
|
+
next unless node.is_a?(Prism::CallNode)
|
|
115
|
+
|
|
116
|
+
kind = ATTACHMENT_METHODS[node.name]
|
|
117
|
+
next if kind.nil?
|
|
118
|
+
next if node.receiver # skip self.has_one_attached and similar
|
|
119
|
+
|
|
120
|
+
name = symbol_literal_arg(node)
|
|
121
|
+
next if name.nil?
|
|
122
|
+
|
|
123
|
+
rows << { name: name, kind: kind }
|
|
124
|
+
end
|
|
125
|
+
rows
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def symbol_literal_arg(node)
|
|
129
|
+
args = node.arguments&.arguments
|
|
130
|
+
return nil if args.nil? || args.empty?
|
|
131
|
+
|
|
132
|
+
first = args.first
|
|
133
|
+
return nil unless first.is_a?(Prism::SymbolNode)
|
|
134
|
+
|
|
135
|
+
first.unescaped
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def constant_path_name(node)
|
|
139
|
+
return nil if node.nil?
|
|
140
|
+
|
|
141
|
+
case node
|
|
142
|
+
when Prism::ConstantReadNode
|
|
143
|
+
node.name.to_s
|
|
144
|
+
when Prism::ConstantPathNode
|
|
145
|
+
parts = []
|
|
146
|
+
current = node
|
|
147
|
+
while current.is_a?(Prism::ConstantPathNode)
|
|
148
|
+
parts.unshift(current.name.to_s)
|
|
149
|
+
current = current.parent
|
|
150
|
+
end
|
|
151
|
+
case current
|
|
152
|
+
when nil
|
|
153
|
+
"::#{parts.join('::')}"
|
|
154
|
+
when Prism::ConstantReadNode
|
|
155
|
+
"#{current.name}::#{parts.join('::')}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
class Activestorage < Rigor::Plugin::Base
|
|
6
|
+
# Maps a discovered class name to the list of attachment
|
|
7
|
+
# rows declared on it. Marshal-clean so the cache producer
|
|
8
|
+
# round-trips it through the standard pair.
|
|
9
|
+
#
|
|
10
|
+
# Each row is `{ name:, kind: }`:
|
|
11
|
+
#
|
|
12
|
+
# - `name` — String, the attachment method name as the
|
|
13
|
+
# user invokes it (`"avatar"`).
|
|
14
|
+
# - `kind` — `:singular` (`has_one_attached`) or
|
|
15
|
+
# `:collection` (`has_many_attached`).
|
|
16
|
+
class AttachmentIndex
|
|
17
|
+
attr_reader :entries
|
|
18
|
+
|
|
19
|
+
def initialize(entries)
|
|
20
|
+
@entries = entries.freeze
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def attachments_for(class_name)
|
|
25
|
+
entries[class_name.to_s]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def class_names = entries.keys
|
|
29
|
+
|
|
30
|
+
def empty? = entries.empty?
|
|
31
|
+
|
|
32
|
+
def self.build(rows:)
|
|
33
|
+
entries = rows.each_with_object({}) do |row, acc|
|
|
34
|
+
class_name = row.fetch(:class_name)
|
|
35
|
+
attachments = Array(row[:attachments]).map(&:freeze).freeze
|
|
36
|
+
acc[class_name] = attachments
|
|
37
|
+
end
|
|
38
|
+
new(entries.freeze)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
|
|
5
|
+
require_relative "activestorage/attachment_discoverer"
|
|
6
|
+
require_relative "activestorage/attachment_index"
|
|
7
|
+
require_relative "activestorage/analyzer"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
module Plugin
|
|
11
|
+
# rigor-activestorage — recognises `has_one_attached` /
|
|
12
|
+
# `has_many_attached` macros on ActiveRecord models and
|
|
13
|
+
# contributes attachment accessor return types so chained
|
|
14
|
+
# calls (`user.avatar.attached?`) route through Rigor's
|
|
15
|
+
# normal dispatch.
|
|
16
|
+
#
|
|
17
|
+
# ## Architecture
|
|
18
|
+
#
|
|
19
|
+
# One discovery pass per run reads the configured AR model
|
|
20
|
+
# paths (default `app/models/`) via the plugin's
|
|
21
|
+
# `IoBoundary`, walks each `.rb` file with Prism, and
|
|
22
|
+
# collects `has_one_attached :avatar` /
|
|
23
|
+
# `has_many_attached :photos` macros into an
|
|
24
|
+
# {AttachmentIndex} keyed by class name. The walker is
|
|
25
|
+
# stand-alone (mirrors `rigor-activerecord`'s
|
|
26
|
+
# `ModelDiscoverer`) so the plugin works even when
|
|
27
|
+
# `rigor-activerecord` is not loaded; when it IS loaded,
|
|
28
|
+
# the published `:model_index` fact (ADR-9 cross-plugin
|
|
29
|
+
# API) drives the same class set so the two plugins agree
|
|
30
|
+
# on what counts as a model.
|
|
31
|
+
#
|
|
32
|
+
# ## Configuration
|
|
33
|
+
#
|
|
34
|
+
# plugins:
|
|
35
|
+
# - gem: rigor-activerecord # producer of :model_index
|
|
36
|
+
# - gem: rigor-activestorage
|
|
37
|
+
# config:
|
|
38
|
+
# model_search_paths: ["app/models"]
|
|
39
|
+
#
|
|
40
|
+
# `model_search_paths` defaults to `["app/models"]`.
|
|
41
|
+
#
|
|
42
|
+
# The class name `Rigor::Plugin::Activestorage` (single
|
|
43
|
+
# capital A) matches the constant-distinguishing convention
|
|
44
|
+
# used by `rigor-activerecord`.
|
|
45
|
+
class Activestorage < Rigor::Plugin::Base
|
|
46
|
+
manifest(
|
|
47
|
+
id: "activestorage",
|
|
48
|
+
version: "0.1.0",
|
|
49
|
+
description: "Types ActiveStorage attachment macros (has_one_attached / has_many_attached) on AR models.",
|
|
50
|
+
config_schema: {
|
|
51
|
+
"model_search_paths" => :array
|
|
52
|
+
},
|
|
53
|
+
consumes: [{ plugin_id: "activerecord", name: :model_index, optional: true }]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
DEFAULT_MODEL_SEARCH_PATHS = ["app/models"].freeze
|
|
57
|
+
|
|
58
|
+
# Cached: attachment index. Walks every `.rb` file under
|
|
59
|
+
# `model_search_paths` for `has_*_attached` macros.
|
|
60
|
+
producer :attachment_index do |_params|
|
|
61
|
+
rows = AttachmentDiscoverer.new(
|
|
62
|
+
io_boundary: io_boundary,
|
|
63
|
+
search_paths: @model_search_paths
|
|
64
|
+
).discover
|
|
65
|
+
AttachmentIndex.build(rows: rows)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def init(_services)
|
|
69
|
+
@model_search_paths = Array(
|
|
70
|
+
config.fetch("model_search_paths", DEFAULT_MODEL_SEARCH_PATHS)
|
|
71
|
+
).map(&:to_s)
|
|
72
|
+
@attachment_index = nil
|
|
73
|
+
@load_errors = []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
77
|
+
index = attachment_index
|
|
78
|
+
return load_error_diagnostics(path) if index.nil?
|
|
79
|
+
return [] if index.empty?
|
|
80
|
+
|
|
81
|
+
Analyzer.new(path: path, attachment_index: index).analyze(root).diagnostics
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Return-type contribution: when the receiver is
|
|
85
|
+
# `Nominal[Model]` and the method matches a discovered
|
|
86
|
+
# attachment, narrow to
|
|
87
|
+
# `Nominal[ActiveStorage::Attached::One]` (singular) or
|
|
88
|
+
# `Nominal[ActiveStorage::Attached::Many]` (collection).
|
|
89
|
+
# The chained call (`.attached?`, `.purge`, `.url`)
|
|
90
|
+
# then resolves through ActiveStorage's RBS surface.
|
|
91
|
+
# Attachment setters (`user.avatar=`) decline — they
|
|
92
|
+
# take side-effecting argument types that the RBS
|
|
93
|
+
# surface already covers.
|
|
94
|
+
def flow_contribution_for(call_node:, scope:)
|
|
95
|
+
return nil unless call_node.is_a?(Prism::CallNode)
|
|
96
|
+
return nil if call_node.receiver.nil?
|
|
97
|
+
return nil unless call_node.arguments.nil?
|
|
98
|
+
|
|
99
|
+
index = attachment_index
|
|
100
|
+
return nil if index.nil? || index.empty?
|
|
101
|
+
|
|
102
|
+
receiver_type = scope.type_of(call_node.receiver)
|
|
103
|
+
return nil unless receiver_type.is_a?(Rigor::Type::Nominal)
|
|
104
|
+
|
|
105
|
+
attachments = index.attachments_for(receiver_type.class_name) ||
|
|
106
|
+
index.attachments_for("::#{receiver_type.class_name}")
|
|
107
|
+
return nil if attachments.nil?
|
|
108
|
+
|
|
109
|
+
attachment = attachments.find { |a| a[:name] == call_node.name.to_s }
|
|
110
|
+
return nil if attachment.nil?
|
|
111
|
+
|
|
112
|
+
target = case attachment[:kind]
|
|
113
|
+
when :singular then "ActiveStorage::Attached::One"
|
|
114
|
+
when :collection then "ActiveStorage::Attached::Many"
|
|
115
|
+
end
|
|
116
|
+
return nil if target.nil?
|
|
117
|
+
|
|
118
|
+
Rigor::FlowContribution.new(
|
|
119
|
+
return_type: Rigor::Type::Combinator.nominal_of(target),
|
|
120
|
+
provenance: Rigor::FlowContribution::Provenance.new(
|
|
121
|
+
source_family: "plugin.#{manifest.id}",
|
|
122
|
+
plugin_id: manifest.id,
|
|
123
|
+
node: call_node,
|
|
124
|
+
descriptor: nil
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @!visibility private
|
|
130
|
+
def attachment_index_for_spec
|
|
131
|
+
attachment_index
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def attachment_index
|
|
137
|
+
return @attachment_index if @attachment_index
|
|
138
|
+
|
|
139
|
+
# Walk first so the IoBoundary's digest list captures
|
|
140
|
+
# the model file digests before cache_for snapshots.
|
|
141
|
+
AttachmentDiscoverer.new(
|
|
142
|
+
io_boundary: io_boundary,
|
|
143
|
+
search_paths: @model_search_paths
|
|
144
|
+
).discover
|
|
145
|
+
@attachment_index = cache_for(:attachment_index, params: {}).call
|
|
146
|
+
rescue Plugin::AccessDeniedError => e
|
|
147
|
+
@load_errors << "rigor-activestorage: #{e.message}"
|
|
148
|
+
nil
|
|
149
|
+
rescue StandardError => e
|
|
150
|
+
@load_errors << "rigor-activestorage: discovery failed: #{e.class}: #{e.message}"
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def load_error_diagnostics(path)
|
|
155
|
+
@load_errors.uniq.map do |message|
|
|
156
|
+
Rigor::Analysis::Diagnostic.new(
|
|
157
|
+
path: path,
|
|
158
|
+
line: 1,
|
|
159
|
+
column: 1,
|
|
160
|
+
message: message,
|
|
161
|
+
severity: :warning,
|
|
162
|
+
rule: "load-error"
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
Rigor::Plugin.register(Activestorage)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Entry point matching the `rigor-<id>` gem-name convention.
|
|
4
|
+
# The Plugin::Loader's `require_gem!` step calls
|
|
5
|
+
# `require "rigor-activestorage"`, and this file loads the
|
|
6
|
+
# plugin's actual implementation under the `rigor/plugin/`
|
|
7
|
+
# namespace so the require / register pair fires.
|
|
8
|
+
require "rigor/plugin/activestorage"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
# ADR-25 — a pure RBS-bundle plugin. It ships NO analyzer code:
|
|
8
|
+
# no `diagnostics_for_file`, no `flow_contribution_for`. Its
|
|
9
|
+
# whole contribution is the manifest's `signature_paths: ["sig"]`,
|
|
10
|
+
# which declares the bundled ActiveSupport `core_ext` RBS
|
|
11
|
+
# directory. `Plugin::Loader` resolves that directory against
|
|
12
|
+
# this gem's root and `Environment.for_project` merges it into
|
|
13
|
+
# the RBS environment, so the ActiveSupport core-extension
|
|
14
|
+
# selectors (`3.days`, `"x".squish`, `Time.current`, …) resolve.
|
|
15
|
+
#
|
|
16
|
+
# Activate it like any plugin — no path, no vendoring:
|
|
17
|
+
#
|
|
18
|
+
# # .rigor.yml
|
|
19
|
+
# plugins:
|
|
20
|
+
# - rigor-activesupport-core-ext
|
|
21
|
+
#
|
|
22
|
+
# Coverage scope and rationale: see `README.md` in this gem.
|
|
23
|
+
class ActivesupportCoreExt < Rigor::Plugin::Base
|
|
24
|
+
manifest(
|
|
25
|
+
id: "activesupport-core-ext",
|
|
26
|
+
# Bumped 2026-05-28 — added Date#midnight /
|
|
27
|
+
# at_midnight / beginning_of_day / end_of_day (Rails
|
|
28
|
+
# aliases that return Time, not Date).
|
|
29
|
+
version: "0.2.0",
|
|
30
|
+
description: "RBS bundle for the most-frequently-flagged ActiveSupport core_ext extensions.",
|
|
31
|
+
signature_paths: ["sig"]
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Rigor::Plugin.register(ActivesupportCoreExt)
|
|
36
|
+
end
|
|
37
|
+
end
|