rigortype 0.1.14 → 0.1.16
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 +10 -2
- data/exe/rigor +19 -0
- data/lib/rigor/analysis/check_rules.rb +428 -6
- data/lib/rigor/analysis/diagnostic.rb +55 -3
- data/lib/rigor/analysis/rule_catalog.rb +80 -0
- data/lib/rigor/analysis/runner.rb +71 -2
- data/lib/rigor/analysis/worker_session.rb +3 -2
- data/lib/rigor/cache/descriptor.rb +6 -2
- data/lib/rigor/cli/plugin_command.rb +245 -0
- data/lib/rigor/cli/plugins_command.rb +51 -4
- data/lib/rigor/cli/plugins_renderer.rb +86 -1
- data/lib/rigor/cli.rb +143 -5
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/environment/rbs_loader.rb +259 -1
- data/lib/rigor/environment.rb +8 -2
- data/lib/rigor/inference/budget_trace.rb +137 -0
- data/lib/rigor/inference/expression_typer.rb +9 -2
- data/lib/rigor/inference/hkt_reducer.rb +2 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -6
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +81 -14
- data/lib/rigor/inference/method_dispatcher.rb +57 -10
- data/lib/rigor/inference/precision_scanner.rb +60 -1
- data/lib/rigor/inference/scope_indexer.rb +184 -27
- data/lib/rigor/inference/statement_evaluator.rb +13 -8
- data/lib/rigor/inference/synthetic_method_index.rb +23 -4
- data/lib/rigor/inference/synthetic_method_scanner.rb +148 -14
- data/lib/rigor/plugin/additional_initializer.rb +108 -0
- data/lib/rigor/plugin/base.rb +321 -2
- data/lib/rigor/plugin/box.rb +64 -0
- data/lib/rigor/plugin/inflector.rb +121 -0
- data/lib/rigor/plugin/isolation.rb +191 -0
- data/lib/rigor/plugin/macro/nested_class_template.rb +140 -0
- data/lib/rigor/plugin/macro.rb +1 -0
- data/lib/rigor/plugin/manifest.rb +120 -23
- data/lib/rigor/plugin/node_context.rb +62 -0
- data/lib/rigor/plugin/registry.rb +10 -0
- data/lib/rigor/plugin.rb +3 -0
- data/lib/rigor/scope.rb +27 -1
- data/lib/rigor/sig_gen/generator.rb +2 -3
- data/lib/rigor/sig_gen/observation_collector.rb +2 -2
- data/lib/rigor/source/literals.rb +118 -0
- data/lib/rigor/source/node_walker.rb +26 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +71 -5
- data/lib/rigor/type/combinator.rb +6 -1
- data/lib/rigor/type/union.rb +65 -1
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +31 -53
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +21 -23
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +38 -59
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +7 -13
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +22 -33
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +298 -413
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +69 -71
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +24 -34
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +18 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +4 -46
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +1 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +17 -12
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +2 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +2 -6
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +4 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +40 -45
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +7 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +20 -42
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +7 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +4 -8
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +188 -0
- data/plugins/rigor-mangrove/lib/rigor-mangrove.rb +3 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +4 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +24 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +31 -48
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +21 -23
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +54 -82
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +25 -25
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +63 -147
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +23 -114
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +36 -31
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +6 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +4 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +13 -12
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +28 -40
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +44 -47
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +11 -10
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +45 -87
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +11 -12
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +29 -42
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +20 -19
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +73 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +43 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +36 -96
- data/sig/rigor/plugin/access_denied_error.rbs +3 -1
- data/sig/rigor/plugin/base.rbs +58 -3
- data/sig/rigor/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/plugin/manifest.rbs +31 -1
- data/sig/rigor/scope.rbs +3 -0
- data/sig/rigor/source.rbs +12 -0
- data/sig/rigor.rbs +5 -0
- data/skills/rigor-plugin-author/SKILL.md +33 -9
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +65 -26
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +213 -80
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +3 -3
- data/skills/rigor-project-init/SKILL.md +72 -7
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +233 -19
- metadata +53 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/inflector.rb +0 -114
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "box"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
# ADR-39 slice 5 — the selectable isolation strategy for target-library
|
|
8
|
+
# invocation. A plugin invokes a pure method on a trusted target
|
|
9
|
+
# library (e.g. `ActiveSupport::Inflector.pluralize("post")`) through
|
|
10
|
+
# {.call}; how much the invocation is isolated from Rigor's own process
|
|
11
|
+
# is a **configurable strategy** (`RIGOR_PLUGIN_ISOLATION` env; the
|
|
12
|
+
# `exe/rigor` launcher maps `.rigor.yml`'s `plugins_isolation:` onto it
|
|
13
|
+
# before re-exec). Three backends behind one interface:
|
|
14
|
+
#
|
|
15
|
+
# - `none` (**default**) — load into the main space and call directly.
|
|
16
|
+
# Lowest cost; no isolation. Fine for the common case because the
|
|
17
|
+
# invoked library is trusted + pure.
|
|
18
|
+
# - `ruby_box` — call inside a {Box} (`Ruby::Box`, `RUBY_BOX=1`). Isolates
|
|
19
|
+
# core-class monkey-patches + lets gem versions coexist, but a native
|
|
20
|
+
# crash in the boxed work still takes the process down (in-process).
|
|
21
|
+
# - `process` — call in a forked worker ({Process}); returns data over a
|
|
22
|
+
# pipe. The strongest: a child crash (even `SIGSEGV`) is contained —
|
|
23
|
+
# the parent survives and declines. Higher cost (fork + IPC).
|
|
24
|
+
#
|
|
25
|
+
# All three answer with the method's return value, or raise
|
|
26
|
+
# {Unavailable} (never approximate) when the target library cannot be
|
|
27
|
+
# reached in the chosen strategy — the caller's per-plugin rescue turns
|
|
28
|
+
# that into silence, never a wrong fact.
|
|
29
|
+
module Isolation
|
|
30
|
+
class Unavailable < StandardError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
STRATEGIES = %w[none ruby_box process].freeze
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# The default strategy. `process` (a crash-contained forked worker)
|
|
38
|
+
# is the default: it isolates the target library's monkey-patches +
|
|
39
|
+
# crashes from Rigor with no in-process contamination, and forks a
|
|
40
|
+
# single persistent worker (not one per call). It falls back to
|
|
41
|
+
# `none` where fork is unavailable (see {#backend}).
|
|
42
|
+
DEFAULT = "process"
|
|
43
|
+
|
|
44
|
+
# The configured strategy name (`RIGOR_PLUGIN_ISOLATION`), defaulting
|
|
45
|
+
# to {DEFAULT} for any unset / unrecognised value.
|
|
46
|
+
def strategy_name
|
|
47
|
+
name = ENV["RIGOR_PLUGIN_ISOLATION"].to_s
|
|
48
|
+
STRATEGIES.include?(name) ? name : DEFAULT
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Invokes `receiver.method(*args)` on a target library, requiring
|
|
52
|
+
# `feature` first, under the configured isolation strategy. `receiver`
|
|
53
|
+
# is a constant name (String), `method` a Symbol from the caller's
|
|
54
|
+
# allow-list, and `args` simple, Marshal-able / inspectable values
|
|
55
|
+
# (Strings) — never free input. Returns the result, or raises
|
|
56
|
+
# {Unavailable}.
|
|
57
|
+
def call(feature:, receiver:, method:, args:)
|
|
58
|
+
backend.call(feature: feature, receiver: receiver, method: method, args: args)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The backend module for the configured strategy. `process`
|
|
62
|
+
# (including the default) falls back to `Direct` where `fork` is
|
|
63
|
+
# unavailable (Windows / JRuby) so inflection still works rather than
|
|
64
|
+
# silently degrading — the libraries are trusted + pure, so the
|
|
65
|
+
# main-space fallback is acceptable when no fork-based isolation can
|
|
66
|
+
# be had.
|
|
67
|
+
def backend
|
|
68
|
+
case strategy_name
|
|
69
|
+
when "ruby_box" then RubyBox
|
|
70
|
+
when "none" then Direct
|
|
71
|
+
else Process.available? ? Process : Direct
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# `none` — load the trusted library into the main space and call it
|
|
76
|
+
# directly. No isolation; lowest cost; the current default behaviour.
|
|
77
|
+
module Direct
|
|
78
|
+
module_function
|
|
79
|
+
|
|
80
|
+
def call(feature:, receiver:, method:, args:)
|
|
81
|
+
require feature
|
|
82
|
+
Object.const_get(receiver).public_send(method, *args)
|
|
83
|
+
rescue LoadError, NameError => e
|
|
84
|
+
raise Unavailable, "#{receiver} could not be loaded (#{e.class}: #{e.message})"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# `ruby_box` — call inside the shared {Box}. The expression is built
|
|
89
|
+
# from the fixed `receiver` / allow-listed `method` and `inspect`-ed
|
|
90
|
+
# args (safe Ruby literals), so the box's `eval` carries no free
|
|
91
|
+
# input.
|
|
92
|
+
module RubyBox
|
|
93
|
+
module_function
|
|
94
|
+
|
|
95
|
+
def call(feature:, receiver:, method:, args:)
|
|
96
|
+
raise Unavailable, "ruby_box isolation requested but Ruby::Box is not active (RUBY_BOX=1)" unless Box.enabled?
|
|
97
|
+
raise Unavailable, "#{feature} could not be loaded into the Ruby::Box" unless Box.require_feature(feature)
|
|
98
|
+
|
|
99
|
+
# `receiver` is a fixed constant name and `method` an allow-listed
|
|
100
|
+
# symbol; args are rendered via `inspect` (safe Ruby literals), so
|
|
101
|
+
# the expression is e.g. `ActiveSupport::Inflector.pluralize("x")`
|
|
102
|
+
# — no free input reaches the box's eval.
|
|
103
|
+
rendered = args.map(&:inspect).join(", ")
|
|
104
|
+
expression = "#{receiver}.#{method}(#{rendered})"
|
|
105
|
+
Box.eval(expression)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# `process` — run the call in a forked worker so a crash (even a C
|
|
110
|
+
# extension `SIGSEGV`) is contained: the parent detects the dead
|
|
111
|
+
# worker (broken pipe / EOF) and declines instead of dying. A single
|
|
112
|
+
# persistent worker handles all calls over a Marshal pipe pair.
|
|
113
|
+
module Process
|
|
114
|
+
module_function
|
|
115
|
+
|
|
116
|
+
# Whether fork-based isolation can run on this platform.
|
|
117
|
+
def available?
|
|
118
|
+
::Process.respond_to?(:fork)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def call(feature:, receiver:, method:, args:)
|
|
122
|
+
raise Unavailable, "process isolation unavailable: fork is not supported" unless available?
|
|
123
|
+
|
|
124
|
+
status, value = exchange([feature, receiver, method, args])
|
|
125
|
+
raise Unavailable, "process isolation worker error: #{value}" if status == :error
|
|
126
|
+
|
|
127
|
+
value
|
|
128
|
+
rescue Unavailable
|
|
129
|
+
raise
|
|
130
|
+
rescue StandardError => e
|
|
131
|
+
# A dead worker surfaces as EOFError (Marshal.load) or Errno::EPIPE
|
|
132
|
+
# (Marshal.dump) — both StandardError. The crash is contained: the
|
|
133
|
+
# parent resets the worker (respawn next call) and declines.
|
|
134
|
+
@worker = nil
|
|
135
|
+
raise Unavailable, "process isolation worker failed (#{e.class})"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Sends one request to the persistent worker and reads its reply.
|
|
139
|
+
def exchange(request)
|
|
140
|
+
w = worker
|
|
141
|
+
Marshal.dump(request, w[:req])
|
|
142
|
+
w[:req].flush
|
|
143
|
+
Marshal.load(w[:res]) # rubocop:disable Security/MarshalLoad -- worker output, not untrusted input
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def worker
|
|
147
|
+
@worker ||= spawn_worker
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def spawn_worker
|
|
151
|
+
req_r, req_w = IO.pipe
|
|
152
|
+
res_r, res_w = IO.pipe
|
|
153
|
+
pid = fork_worker(req_r, req_w, res_r, res_w)
|
|
154
|
+
req_r.close
|
|
155
|
+
res_w.close
|
|
156
|
+
{ pid: pid, req: req_w, res: res_r }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def fork_worker(req_r, req_w, res_r, res_w)
|
|
160
|
+
::Process.fork do
|
|
161
|
+
req_w.close
|
|
162
|
+
res_r.close
|
|
163
|
+
run_worker_loop(req_r, res_w)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The child loop: read a `[feature, receiver, method, args]`
|
|
168
|
+
# request, require + call, and write `[:ok, result]` or
|
|
169
|
+
# `[:error, message]`. EOF (parent gone) ends the loop.
|
|
170
|
+
def run_worker_loop(req_r, res_w)
|
|
171
|
+
loop do
|
|
172
|
+
feature, receiver, method, args = Marshal.load(req_r) # rubocop:disable Security/MarshalLoad -- parent input
|
|
173
|
+
reply =
|
|
174
|
+
begin
|
|
175
|
+
require feature
|
|
176
|
+
[:ok, Object.const_get(receiver).public_send(method, *args)]
|
|
177
|
+
rescue StandardError, LoadError => e
|
|
178
|
+
[:error, "#{e.class}: #{e.message}"]
|
|
179
|
+
end
|
|
180
|
+
Marshal.dump(reply, res_w)
|
|
181
|
+
res_w.flush
|
|
182
|
+
end
|
|
183
|
+
rescue EOFError
|
|
184
|
+
# parent closed the request pipe — exit quietly
|
|
185
|
+
ensure
|
|
186
|
+
exit!(0)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
module Macro
|
|
6
|
+
# ADR-36 — the nested-class emission tier of the ADR-16
|
|
7
|
+
# macro substrate. Where Tier C ({HeredocTemplate}) synthesises
|
|
8
|
+
# *methods* on the calling class, this tier synthesises *nested
|
|
9
|
+
# subclasses* declared by an enum-shaped block DSL.
|
|
10
|
+
#
|
|
11
|
+
# Motivating shape — Mangrove's `Enum`:
|
|
12
|
+
#
|
|
13
|
+
# class Shape
|
|
14
|
+
# extend Mangrove::Enum
|
|
15
|
+
# variants do
|
|
16
|
+
# variant Circle, Float
|
|
17
|
+
# variant Rectangle, Float
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# Each `variant <Const>, <Type>` row is meant to mint a nested
|
|
22
|
+
# subclass `Shape::Circle < Shape` carrying `#inner : <Type>`.
|
|
23
|
+
# Mangrove builds this at runtime via `const_missing` +
|
|
24
|
+
# `class_eval`; the substrate replays the same contract
|
|
25
|
+
# statically so `Shape::Circle.new(1.0).inner` resolves the
|
|
26
|
+
# payload to `Float` instead of `call.undefined-constant` /
|
|
27
|
+
# `Dynamic[Top]`.
|
|
28
|
+
#
|
|
29
|
+
# ## Authoring shape
|
|
30
|
+
#
|
|
31
|
+
# nested_class_templates: [
|
|
32
|
+
# Rigor::Plugin::Macro::NestedClassTemplate.new(
|
|
33
|
+
# receiver_constraint: "Mangrove::Enum", # `extend`-ed marker module
|
|
34
|
+
# block_method: :variants, # the enclosing DSL block
|
|
35
|
+
# variant_method: :variant, # each declaration call
|
|
36
|
+
# name_arg_position: 0, # constant arg → nested class
|
|
37
|
+
# inner_arg_position: 1, # type arg → `#inner` return
|
|
38
|
+
# inner_reader: :inner # the payload reader name
|
|
39
|
+
# )
|
|
40
|
+
# ]
|
|
41
|
+
#
|
|
42
|
+
# ## Fields
|
|
43
|
+
#
|
|
44
|
+
# - `receiver_constraint` — fully-qualified module name (String)
|
|
45
|
+
# the enclosing class must `extend` for the block to be
|
|
46
|
+
# recognised (e.g. `"Mangrove::Enum"`).
|
|
47
|
+
# - `block_method` — Symbol naming the enclosing DSL block
|
|
48
|
+
# (`:variants`).
|
|
49
|
+
# - `variant_method` — Symbol naming each declaration call
|
|
50
|
+
# inside the block (`:variant`).
|
|
51
|
+
# - `name_arg_position` — Integer (default 0): the argument
|
|
52
|
+
# index whose literal **constant** names the nested subclass.
|
|
53
|
+
# - `inner_arg_position` — Integer (default 1): the argument
|
|
54
|
+
# index whose type expression becomes the `#inner` reader's
|
|
55
|
+
# return type. Slice A resolves a constant type argument
|
|
56
|
+
# (`Float`, `String`); non-constant inner shapes (shape
|
|
57
|
+
# hashes) degrade to `Dynamic[Top]`.
|
|
58
|
+
# - `inner_reader` — Symbol (default `:inner`): the payload
|
|
59
|
+
# reader synthesised on each variant subclass.
|
|
60
|
+
#
|
|
61
|
+
# ## Floor / ceiling per ADR-16 WD13 + ADR-36 WD4
|
|
62
|
+
#
|
|
63
|
+
# Slice A ships the floor: each variant constant resolves as a
|
|
64
|
+
# class (so `<Variant>.new(...)` and the constant reference
|
|
65
|
+
# type), and the `#inner` reader resolves to the declared inner
|
|
66
|
+
# type. The `sealed`-parent fact + `is_a?` cross-variant
|
|
67
|
+
# exhaustive narrowing (ADR-36 WD3) is the ceiling, deferred —
|
|
68
|
+
# it needs the synthetic-class hierarchy threaded into
|
|
69
|
+
# `Environment#class_ordering`.
|
|
70
|
+
class NestedClassTemplate
|
|
71
|
+
attr_reader :receiver_constraint, :block_method, :variant_method,
|
|
72
|
+
:name_arg_position, :inner_arg_position, :inner_reader
|
|
73
|
+
|
|
74
|
+
def initialize(receiver_constraint:, block_method: :variants, variant_method: :variant,
|
|
75
|
+
name_arg_position: 0, inner_arg_position: 1, inner_reader: :inner)
|
|
76
|
+
validate_constraint!(receiver_constraint)
|
|
77
|
+
validate_method!(block_method, "block_method")
|
|
78
|
+
validate_method!(variant_method, "variant_method")
|
|
79
|
+
validate_position!(name_arg_position, "name_arg_position")
|
|
80
|
+
validate_position!(inner_arg_position, "inner_arg_position")
|
|
81
|
+
validate_method!(inner_reader, "inner_reader")
|
|
82
|
+
|
|
83
|
+
@receiver_constraint = receiver_constraint.dup.freeze
|
|
84
|
+
@block_method = block_method.to_sym
|
|
85
|
+
@variant_method = variant_method.to_sym
|
|
86
|
+
@name_arg_position = name_arg_position
|
|
87
|
+
@inner_arg_position = inner_arg_position
|
|
88
|
+
@inner_reader = inner_reader.to_sym
|
|
89
|
+
freeze
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_h
|
|
93
|
+
{
|
|
94
|
+
"receiver_constraint" => receiver_constraint,
|
|
95
|
+
"block_method" => block_method.to_s,
|
|
96
|
+
"variant_method" => variant_method.to_s,
|
|
97
|
+
"name_arg_position" => name_arg_position,
|
|
98
|
+
"inner_arg_position" => inner_arg_position,
|
|
99
|
+
"inner_reader" => inner_reader.to_s
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def ==(other)
|
|
104
|
+
other.is_a?(NestedClassTemplate) && to_h == other.to_h
|
|
105
|
+
end
|
|
106
|
+
alias eql? ==
|
|
107
|
+
|
|
108
|
+
def hash
|
|
109
|
+
to_h.hash
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def validate_constraint!(value)
|
|
115
|
+
return if value.is_a?(String) && !value.empty?
|
|
116
|
+
|
|
117
|
+
raise ArgumentError,
|
|
118
|
+
"Plugin::Macro::NestedClassTemplate#receiver_constraint must be a non-empty String, " \
|
|
119
|
+
"got #{value.inspect}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def validate_method!(value, label)
|
|
123
|
+
return if value.is_a?(Symbol) || (value.is_a?(String) && !value.empty?)
|
|
124
|
+
|
|
125
|
+
raise ArgumentError,
|
|
126
|
+
"Plugin::Macro::NestedClassTemplate##{label} must be a Symbol or non-empty String, " \
|
|
127
|
+
"got #{value.inspect}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def validate_position!(value, label)
|
|
131
|
+
return if value.is_a?(Integer) && value >= 0
|
|
132
|
+
|
|
133
|
+
raise ArgumentError,
|
|
134
|
+
"Plugin::Macro::NestedClassTemplate##{label} must be a non-negative Integer, " \
|
|
135
|
+
"got #{value.inspect}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
data/lib/rigor/plugin/macro.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../inference/hkt_registry"
|
|
4
4
|
require_relative "protocol_contract"
|
|
5
|
+
require_relative "additional_initializer"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Plugin
|
|
@@ -40,22 +41,23 @@ module Rigor
|
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
attr_reader :id, :version, :description, :
|
|
44
|
+
attr_reader :id, :version, :description, :config_schema, :config_defaults, :produces, :consumes,
|
|
44
45
|
:owns_receivers, :open_receivers, :type_node_resolvers, :block_as_methods,
|
|
45
|
-
:heredoc_templates, :
|
|
46
|
-
:hkt_definitions, :signature_paths, :protocol_contracts,
|
|
46
|
+
:heredoc_templates, :nested_class_templates, :trait_registries, :external_files,
|
|
47
|
+
:hkt_registrations, :hkt_definitions, :signature_paths, :protocol_contracts,
|
|
48
|
+
:source_rbs_synthesizer, :additional_initializers
|
|
47
49
|
|
|
48
50
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
49
51
|
id:, version:,
|
|
50
|
-
description: nil,
|
|
52
|
+
description: nil, config_schema: {},
|
|
51
53
|
produces: [], consumes: [], owns_receivers: [], open_receivers: [], type_node_resolvers: [],
|
|
52
|
-
block_as_methods: [], heredoc_templates: [],
|
|
54
|
+
block_as_methods: [], heredoc_templates: [], nested_class_templates: [],
|
|
55
|
+
trait_registries: [], external_files: [],
|
|
53
56
|
hkt_registrations: [], hkt_definitions: [], signature_paths: [], protocol_contracts: [],
|
|
54
|
-
source_rbs_synthesizer: nil
|
|
57
|
+
source_rbs_synthesizer: nil, additional_initializers: []
|
|
55
58
|
)
|
|
56
59
|
validate_id!(id)
|
|
57
60
|
validate_version!(version)
|
|
58
|
-
validate_protocols!(protocols)
|
|
59
61
|
validate_config_schema!(config_schema)
|
|
60
62
|
validate_produces!(produces)
|
|
61
63
|
validate_owns_receivers!(owns_receivers)
|
|
@@ -63,6 +65,7 @@ module Rigor
|
|
|
63
65
|
validate_type_node_resolvers!(type_node_resolvers)
|
|
64
66
|
validate_block_as_methods!(block_as_methods)
|
|
65
67
|
validate_heredoc_templates!(heredoc_templates)
|
|
68
|
+
validate_nested_class_templates!(nested_class_templates)
|
|
66
69
|
validate_trait_registries!(trait_registries)
|
|
67
70
|
validate_external_files!(external_files)
|
|
68
71
|
validate_hkt_registrations!(hkt_registrations)
|
|
@@ -70,26 +73,29 @@ module Rigor
|
|
|
70
73
|
validate_signature_paths!(signature_paths)
|
|
71
74
|
validate_protocol_contracts!(protocol_contracts)
|
|
72
75
|
validate_source_rbs_synthesizer!(source_rbs_synthesizer)
|
|
76
|
+
validate_additional_initializers!(additional_initializers)
|
|
73
77
|
|
|
74
|
-
assign_fields(id, version, description,
|
|
78
|
+
assign_fields(id, version, description, config_schema, produces, consumes, owns_receivers,
|
|
75
79
|
open_receivers, type_node_resolvers, block_as_methods, heredoc_templates, trait_registries,
|
|
76
80
|
external_files, hkt_registrations, hkt_definitions, signature_paths, protocol_contracts,
|
|
77
81
|
source_rbs_synthesizer)
|
|
82
|
+
assign_nested_class_templates(nested_class_templates)
|
|
83
|
+
assign_additional_initializers(additional_initializers)
|
|
78
84
|
freeze
|
|
79
85
|
end
|
|
80
86
|
|
|
81
87
|
private
|
|
82
88
|
|
|
83
89
|
# rubocop:disable Metrics/ParameterLists, Metrics/AbcSize
|
|
84
|
-
def assign_fields(id, version, description,
|
|
90
|
+
def assign_fields(id, version, description, config_schema, produces, consumes, owns_receivers,
|
|
85
91
|
open_receivers, type_node_resolvers, block_as_methods, heredoc_templates, trait_registries,
|
|
86
92
|
external_files, hkt_registrations, hkt_definitions, signature_paths, protocol_contracts,
|
|
87
93
|
source_rbs_synthesizer)
|
|
88
94
|
@id = id.dup.freeze
|
|
89
95
|
@version = version.dup.freeze
|
|
90
96
|
@description = description.nil? ? nil : description.to_s.dup.freeze
|
|
91
|
-
@
|
|
92
|
-
@
|
|
97
|
+
@config_schema = config_schema.to_h { |k, v| [k.to_s.dup.freeze, schema_kind(v)] }.freeze
|
|
98
|
+
@config_defaults = extract_config_defaults(config_schema)
|
|
93
99
|
@produces = produces.map(&:to_sym).freeze
|
|
94
100
|
@consumes = coerce_consumes(consumes)
|
|
95
101
|
@owns_receivers = owns_receivers.map { |c| c.to_s.dup.freeze }.freeze
|
|
@@ -105,6 +111,22 @@ module Rigor
|
|
|
105
111
|
@protocol_contracts = protocol_contracts.dup.freeze
|
|
106
112
|
@source_rbs_synthesizer = source_rbs_synthesizer
|
|
107
113
|
end
|
|
114
|
+
|
|
115
|
+
# Assigned outside assign_fields (which already carries the
|
|
116
|
+
# maximum positional arity) — set in `initialize` before the
|
|
117
|
+
# final freeze. ADR-36 nested-class emission tier.
|
|
118
|
+
def assign_nested_class_templates(nested_class_templates)
|
|
119
|
+
@nested_class_templates = nested_class_templates.dup.freeze
|
|
120
|
+
end
|
|
121
|
+
private :assign_nested_class_templates
|
|
122
|
+
|
|
123
|
+
# ADR-38 — assigned outside assign_fields (which already carries
|
|
124
|
+
# the maximum positional arity), set in `initialize` before the
|
|
125
|
+
# final freeze.
|
|
126
|
+
def assign_additional_initializers(additional_initializers)
|
|
127
|
+
@additional_initializers = additional_initializers.dup.freeze
|
|
128
|
+
end
|
|
129
|
+
private :assign_additional_initializers
|
|
108
130
|
# rubocop:enable Metrics/ParameterLists, Metrics/AbcSize
|
|
109
131
|
|
|
110
132
|
public
|
|
@@ -137,8 +159,8 @@ module Rigor
|
|
|
137
159
|
"id" => id,
|
|
138
160
|
"version" => version,
|
|
139
161
|
"description" => description,
|
|
140
|
-
"protocols" => protocols.map(&:to_s),
|
|
141
162
|
"config_schema" => config_schema.to_h { |k, v| [k, v.to_s] },
|
|
163
|
+
"config_defaults" => config_defaults,
|
|
142
164
|
"produces" => produces.map(&:to_s),
|
|
143
165
|
"consumes" => consumes.map { |c| consumption_hash(c) },
|
|
144
166
|
"owns_receivers" => owns_receivers,
|
|
@@ -146,13 +168,15 @@ module Rigor
|
|
|
146
168
|
"type_node_resolvers" => type_node_resolvers.map { |r| r.class.name },
|
|
147
169
|
"block_as_methods" => block_as_methods.map(&:to_h),
|
|
148
170
|
"heredoc_templates" => heredoc_templates.map(&:to_h),
|
|
171
|
+
"nested_class_templates" => nested_class_templates.map(&:to_h),
|
|
149
172
|
"trait_registries" => trait_registries.map(&:to_h),
|
|
150
173
|
"external_files" => external_files.map(&:to_h),
|
|
151
174
|
"hkt_registrations" => hkt_registrations.map(&:to_h),
|
|
152
175
|
"hkt_definitions" => hkt_definitions.map { |d| { "uri" => d.uri, "params" => d.params } },
|
|
153
176
|
"signature_paths" => signature_paths,
|
|
154
177
|
"protocol_contracts" => protocol_contracts.map(&:to_h),
|
|
155
|
-
"source_rbs_synthesizer" => source_rbs_synthesizer&.class&.name
|
|
178
|
+
"source_rbs_synthesizer" => source_rbs_synthesizer&.class&.name,
|
|
179
|
+
"additional_initializers" => additional_initializers.map(&:to_h)
|
|
156
180
|
}
|
|
157
181
|
end
|
|
158
182
|
|
|
@@ -180,27 +204,70 @@ module Rigor
|
|
|
180
204
|
raise ArgumentError, "plugin manifest version must be a non-empty String, got #{version.inspect}"
|
|
181
205
|
end
|
|
182
206
|
|
|
183
|
-
def validate_protocols!(protocols)
|
|
184
|
-
return if protocols.is_a?(Array) && protocols.all? { |p| p.is_a?(Symbol) || p.is_a?(String) }
|
|
185
|
-
|
|
186
|
-
raise ArgumentError, "plugin manifest protocols must be an Array of Symbol/String, got #{protocols.inspect}"
|
|
187
|
-
end
|
|
188
|
-
|
|
189
207
|
def validate_config_schema!(schema)
|
|
190
208
|
unless schema.is_a?(Hash)
|
|
191
209
|
raise ArgumentError,
|
|
192
210
|
"plugin manifest config_schema must be a Hash, got #{schema.inspect}"
|
|
193
211
|
end
|
|
194
212
|
|
|
195
|
-
schema.
|
|
196
|
-
|
|
213
|
+
schema.each do |key, value|
|
|
214
|
+
kind = schema_kind(value)
|
|
215
|
+
unless VALID_VALUE_KINDS.include?(kind)
|
|
216
|
+
raise ArgumentError,
|
|
217
|
+
"plugin manifest config_schema value kind must be one of " \
|
|
218
|
+
"#{VALID_VALUE_KINDS.inspect}, got #{value.inspect}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
next unless schema_default?(value)
|
|
222
|
+
|
|
223
|
+
default = schema_default(value)
|
|
224
|
+
next if value_matches?(default, kind)
|
|
225
|
+
|
|
226
|
+
raise ArgumentError,
|
|
227
|
+
"plugin manifest config_schema default for #{key.to_s.inspect} expected " \
|
|
228
|
+
"#{kind}, got #{default.class}"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# ADR-40 — a `config_schema` value is either a bare kind
|
|
233
|
+
# (`Symbol`/`String`, the original form) or a `{kind:, default:}`
|
|
234
|
+
# Hash. These three helpers read whichever shape was given.
|
|
235
|
+
def schema_kind(value)
|
|
236
|
+
if value.is_a?(Hash)
|
|
237
|
+
kind = value[:kind] || value["kind"]
|
|
238
|
+
if kind.nil?
|
|
239
|
+
raise ArgumentError,
|
|
240
|
+
"plugin manifest config_schema entry Hash must declare :kind, got #{value.inspect}"
|
|
241
|
+
end
|
|
197
242
|
|
|
243
|
+
kind.to_sym
|
|
244
|
+
elsif value.is_a?(Symbol) || value.is_a?(String)
|
|
245
|
+
value.to_sym
|
|
246
|
+
else
|
|
198
247
|
raise ArgumentError,
|
|
199
|
-
"plugin manifest config_schema value
|
|
200
|
-
"
|
|
248
|
+
"plugin manifest config_schema value must be a kind Symbol/String or a " \
|
|
249
|
+
"{kind:, default:} Hash, got #{value.inspect}"
|
|
201
250
|
end
|
|
202
251
|
end
|
|
203
252
|
|
|
253
|
+
def schema_default?(value)
|
|
254
|
+
value.is_a?(Hash) && (value.key?(:default) || value.key?("default"))
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def schema_default(value)
|
|
258
|
+
value.key?(:default) ? value[:default] : value["default"]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def extract_config_defaults(schema)
|
|
262
|
+
schema.each_with_object({}) do |(key, value), acc|
|
|
263
|
+
acc[key.to_s.dup.freeze] = value_default_frozen(schema_default(value)) if schema_default?(value)
|
|
264
|
+
end.freeze
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def value_default_frozen(default)
|
|
268
|
+
default.frozen? ? default : default.dup.freeze
|
|
269
|
+
end
|
|
270
|
+
|
|
204
271
|
def value_matches?(value, kind)
|
|
205
272
|
case kind
|
|
206
273
|
when :string then value.is_a?(String)
|
|
@@ -294,6 +361,20 @@ module Rigor
|
|
|
294
361
|
"Rigor::Plugin::Macro::HeredocTemplate instances, got #{entries.inspect}"
|
|
295
362
|
end
|
|
296
363
|
|
|
364
|
+
# ADR-36 — `nested_class_templates:` declares the
|
|
365
|
+
# nested-class emission tier (enum-shaped block DSLs that mint
|
|
366
|
+
# nested subclasses, e.g. Mangrove's `variants do variant
|
|
367
|
+
# Const, Type end`). The scanner synthesises the variant
|
|
368
|
+
# subclasses + their `#inner` reader through the existing
|
|
369
|
+
# `SyntheticMethodIndex` primitive.
|
|
370
|
+
def validate_nested_class_templates!(entries)
|
|
371
|
+
return if entries.is_a?(Array) && entries.all?(Macro::NestedClassTemplate)
|
|
372
|
+
|
|
373
|
+
raise ArgumentError,
|
|
374
|
+
"plugin manifest nested_class_templates must be an Array of " \
|
|
375
|
+
"Rigor::Plugin::Macro::NestedClassTemplate instances, got #{entries.inspect}"
|
|
376
|
+
end
|
|
377
|
+
|
|
297
378
|
# ADR-16 slice 3a — `trait_registries:` declares the Tier B
|
|
298
379
|
# substrate entries (trait-inlining via bundled module
|
|
299
380
|
# registry). Slice 3a carries the declarations on the
|
|
@@ -395,6 +476,22 @@ module Rigor
|
|
|
395
476
|
"Rigor::Plugin::ProtocolContract instances, got #{entries.inspect}"
|
|
396
477
|
end
|
|
397
478
|
|
|
479
|
+
# ADR-38 — `additional_initializers:` declares the
|
|
480
|
+
# (receiver_constraint, methods) pairs whose `def`-form methods
|
|
481
|
+
# the engine treats like `initialize` for the read-before-write
|
|
482
|
+
# nil soundness gate. Each entry MUST be a
|
|
483
|
+
# `Rigor::Plugin::AdditionalInitializer`. The registry
|
|
484
|
+
# aggregator on `Plugin::Registry` flattens entries across
|
|
485
|
+
# loaded plugins; `Inference::ScopeIndexer` consults the set at
|
|
486
|
+
# its single gate.
|
|
487
|
+
def validate_additional_initializers!(entries)
|
|
488
|
+
return if entries.is_a?(Array) && entries.all?(AdditionalInitializer)
|
|
489
|
+
|
|
490
|
+
raise ArgumentError,
|
|
491
|
+
"plugin manifest additional_initializers must be an Array of " \
|
|
492
|
+
"Rigor::Plugin::AdditionalInitializer instances, got #{entries.inspect}"
|
|
493
|
+
end
|
|
494
|
+
|
|
398
495
|
# ADR-32 WD4 — `source_rbs_synthesizer:` declares a callable
|
|
399
496
|
# the engine invokes once per analysed Ruby source file at
|
|
400
497
|
# env-build time. The callable receives a source file path
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
# ADR-37 slice 1d — the lexical context of a node, handed to a
|
|
8
|
+
# {Base.node_rule} block as its fifth argument. Realises the
|
|
9
|
+
# `ContextInfo` ADR-2 § "Scope Object" promised: the enclosing
|
|
10
|
+
# class / module, method, and block-DSL nesting that a per-node rule
|
|
11
|
+
# needs but that `Scope` (value/type facts) does not carry.
|
|
12
|
+
#
|
|
13
|
+
# The engine builds one per matching node from the descent stack
|
|
14
|
+
# (snapshotted, so it is safe to retain). Rules read the bits they
|
|
15
|
+
# need:
|
|
16
|
+
#
|
|
17
|
+
# node_rule Prism::CallNode do |node, _scope, path, _fc, context|
|
|
18
|
+
# action = context.enclosing_def&.name # rails-i18n
|
|
19
|
+
# model = context.enclosing_block(:describe) # shoulda
|
|
20
|
+
# …
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# `ancestors` is the full chain, outermost first, EXCLUDING the node
|
|
24
|
+
# itself — the general primitive; the accessors below are
|
|
25
|
+
# conveniences derived from it.
|
|
26
|
+
class NodeContext
|
|
27
|
+
EMPTY = nil # sentinel documented for readers; rules get a real instance
|
|
28
|
+
|
|
29
|
+
attr_reader :ancestors
|
|
30
|
+
|
|
31
|
+
def initialize(ancestors)
|
|
32
|
+
@ancestors = ancestors.dup.freeze
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The innermost enclosing `Prism::DefNode`, or nil. Its `#name`
|
|
37
|
+
# is the method the node sits in (rails-i18n uses it to expand a
|
|
38
|
+
# lazy `t('.key')` against the controller action).
|
|
39
|
+
def enclosing_def
|
|
40
|
+
ancestors.rfind { |n| n.is_a?(Prism::DefNode) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The innermost enclosing `Prism::ClassNode` / `Prism::ModuleNode`,
|
|
44
|
+
# or nil (actionpack uses it to resolve the controller a
|
|
45
|
+
# `before_action` / `render` sits in).
|
|
46
|
+
def enclosing_module
|
|
47
|
+
ancestors.rfind { |n| n.is_a?(Prism::ClassNode) || n.is_a?(Prism::ModuleNode) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The innermost enclosing `Prism::CallNode` that carries a block
|
|
51
|
+
# and whose method name is `method_name` — e.g. the
|
|
52
|
+
# `RSpec.describe(Model) do … end` a shoulda matcher sits in.
|
|
53
|
+
# Returns the CallNode (so the caller can read its arguments /
|
|
54
|
+
# receiver), or nil.
|
|
55
|
+
def enclosing_block(method_name)
|
|
56
|
+
ancestors.rfind do |n|
|
|
57
|
+
n.is_a?(Prism::CallNode) && n.block && n.name == method_name
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -143,6 +143,16 @@ module Rigor
|
|
|
143
143
|
plugins.flat_map(&:protocol_contracts)
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
+
# ADR-38 — flat, ordered list of every loaded plugin's
|
|
147
|
+
# manifest-declared `Rigor::Plugin::AdditionalInitializer`
|
|
148
|
+
# entries. `Inference::ScopeIndexer` consults the set at its
|
|
149
|
+
# read-before-write nil soundness gate: a `def` whose name an
|
|
150
|
+
# entry covers, on a class that equals or inherits from the
|
|
151
|
+
# entry's `receiver_constraint`, is treated like `initialize`.
|
|
152
|
+
def additional_initializers
|
|
153
|
+
plugins.flat_map { |plugin| plugin.manifest.additional_initializers }
|
|
154
|
+
end
|
|
155
|
+
|
|
146
156
|
# ADR-28 — the subset of `protocol_contracts` whose
|
|
147
157
|
# `path_glob` matches `path`. Contract globs are authored
|
|
148
158
|
# project-root-relative (`lib/controller/**/*.rb`); the
|
data/lib/rigor/plugin.rb
CHANGED
|
@@ -11,6 +11,9 @@ require_relative "plugin/services"
|
|
|
11
11
|
require_relative "plugin/base"
|
|
12
12
|
require_relative "plugin/registry"
|
|
13
13
|
require_relative "plugin/load_error"
|
|
14
|
+
require_relative "plugin/box"
|
|
15
|
+
require_relative "plugin/isolation"
|
|
16
|
+
require_relative "plugin/inflector"
|
|
14
17
|
|
|
15
18
|
module Rigor
|
|
16
19
|
module Plugin
|