rigortype 0.1.19 → 0.2.1
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 +41 -6
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +3 -21
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules.rb +492 -71
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +17 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +9 -8
- data/lib/rigor/analysis/runner.rb +17 -6
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +10 -14
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +28 -7
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +138 -16
- data/lib/rigor/cli/coverage_command.rb +138 -31
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration.rb +57 -7
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +76 -5
- data/lib/rigor/environment.rb +66 -8
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +20 -28
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +169 -24
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +90 -15
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +40 -48
- data/lib/rigor/inference/mutation_widening.rb +5 -11
- data/lib/rigor/inference/narrowing.rb +14 -16
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +129 -55
- data/lib/rigor/inference/statement_evaluator.rb +271 -114
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +10 -8
- data/lib/rigor/plugin/macro/block_as_method.rb +3 -4
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +4 -5
- data/lib/rigor/plugin/manifest.rb +45 -66
- data/lib/rigor/plugin/registry.rb +6 -7
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +180 -0
- data/lib/rigor/protection/mutator.rb +267 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +14 -2
- data/lib/rigor/scope.rb +54 -11
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +16 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +3 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +7 -10
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +6 -8
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +7 -9
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +3 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +19 -14
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +28 -41
- data/sig/rigor/scope.rbs +9 -1
- data/sig/rigor/type.rbs +36 -1
- metadata +49 -1
|
@@ -23,8 +23,8 @@ module Rigor
|
|
|
23
23
|
# - `factory :users, aliases: [:author] do ... end` — alias form
|
|
24
24
|
#
|
|
25
25
|
# Inside a factory block, attribute declarations come in
|
|
26
|
-
# several shapes.
|
|
27
|
-
#
|
|
26
|
+
# several shapes. Only literal-name forms are recognised
|
|
27
|
+
# (Symbol arg / String arg):
|
|
28
28
|
#
|
|
29
29
|
# - `name { "Alice" }` — implicit attribute via
|
|
30
30
|
# `method_missing` with a block (FactoryBot's modern
|
|
@@ -116,8 +116,8 @@ module Rigor
|
|
|
116
116
|
Rigor::Source::Literals.symbol_or_string_name(call_node.arguments&.arguments&.first)
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
#
|
|
120
|
-
#
|
|
119
|
+
# Resolves the model class name for the factory.
|
|
120
|
+
# Three sources, in priority order:
|
|
121
121
|
#
|
|
122
122
|
# 1. Explicit `class: <Const>` keyword arg —
|
|
123
123
|
# ConstantReadNode / ConstantPathNode value.
|
|
@@ -188,11 +188,10 @@ module Rigor
|
|
|
188
188
|
attributes
|
|
189
189
|
end
|
|
190
190
|
|
|
191
|
-
# Walks the block body collecting attribute names.
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# (traits ship in a follow-up).
|
|
191
|
+
# Walks the block body collecting attribute names. Only
|
|
192
|
+
# top-level statements are examined — attributes inside
|
|
193
|
+
# `trait :admin do ... end` or other nested blocks are
|
|
194
|
+
# not collected (traits deferred to a follow-up).
|
|
196
195
|
def collect_attributes_from(node, accumulator)
|
|
197
196
|
return unless node.is_a?(Prism::Node)
|
|
198
197
|
|
|
@@ -206,8 +205,7 @@ module Rigor
|
|
|
206
205
|
def record_attribute(node, accumulator)
|
|
207
206
|
return unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
208
207
|
# Skip association / sequence / trait / framework
|
|
209
|
-
# methods —
|
|
210
|
-
# declarations.
|
|
208
|
+
# methods — only plain attribute declarations are recorded.
|
|
211
209
|
return if SKIPPED_METHODS.include?(node.name)
|
|
212
210
|
|
|
213
211
|
name = if node.name == :add_attribute
|
|
@@ -4,15 +4,14 @@ module Rigor
|
|
|
4
4
|
module Plugin
|
|
5
5
|
class Factorybot < Rigor::Plugin::Base
|
|
6
6
|
# Per-run frozen index of discovered FactoryBot factories
|
|
7
|
-
# and the attribute keys each declares.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
# and the attribute keys each declares. Indexes only
|
|
8
|
+
# **literal symbol/string** factory names + **literal
|
|
9
|
+
# symbol** attribute names; sequences, parent/child
|
|
10
|
+
# relationships, traits, and dynamically-named factories
|
|
11
|
+
# are deferred to follow-up slices.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# builds. Resolved from:
|
|
13
|
+
# Each entry carries a `model_class` — the inferred or
|
|
14
|
+
# explicit class the factory builds. Resolved from:
|
|
16
15
|
#
|
|
17
16
|
# 1. An explicit `factory :user, class: User do`
|
|
18
17
|
# keyword option (ConstantReadNode / ConstantPathNode
|
|
@@ -13,12 +13,10 @@ module Rigor
|
|
|
13
13
|
# attributes_for / *_list family against a per-run index
|
|
14
14
|
# built from `factory_search_paths`.
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# `rigor-activerecord` `:model_index` ADR-9 fact, after
|
|
21
|
-
# `rigor-activerecord` adds the matching publish hook.
|
|
16
|
+
# Recognises factory NAMES + literal ATTRIBUTE KEYS in
|
|
17
|
+
# the call's keyword hash. The AR column cross-check uses
|
|
18
|
+
# the `rigor-activerecord` `:model_index` ADR-9 fact when
|
|
19
|
+
# that plugin is loaded (optional `consumes:`).
|
|
22
20
|
# Traits, sequences, parent / child factories, and dynamic
|
|
23
21
|
# factory names are deferred to follow-up slices.
|
|
24
22
|
#
|
|
@@ -54,9 +52,9 @@ module Rigor
|
|
|
54
52
|
# `.build_stubbed_list`. The legacy `FactoryGirl` constant
|
|
55
53
|
# is recognised identically. Implicit-receiver calls
|
|
56
54
|
# (`create(:name)` inside an `include FactoryBot::Syntax::Methods`
|
|
57
|
-
# context) are NOT recognised
|
|
58
|
-
#
|
|
59
|
-
#
|
|
55
|
+
# context) are NOT recognised — too many false positives on
|
|
56
|
+
# plain `create` calls outside test files; deferred until
|
|
57
|
+
# receiver-type inference can disambiguate.
|
|
60
58
|
#
|
|
61
59
|
# ## What's recognised inside `factory :name do ... end`
|
|
62
60
|
#
|
|
@@ -50,12 +50,13 @@ module Rigor
|
|
|
50
50
|
|
|
51
51
|
# @param paths [Array<String>] absolute paths to `.rb` files
|
|
52
52
|
# the project's `paths:` resolves to.
|
|
53
|
-
# @return [Hash{Symbol => Hash}] frozen
|
|
53
|
+
# @return [Hash{Symbol => Hash}] frozen 4-key result:
|
|
54
54
|
# `:types` (per-`Schema::Object` field table),
|
|
55
|
-
# `:enums` (per-`Schema::Enum` value list),
|
|
56
|
-
# `:input_objects` (per-`Schema::InputObject` argument
|
|
57
|
-
# table).
|
|
58
|
-
#
|
|
55
|
+
# `:enums` (per-`Schema::Enum` value list),
|
|
56
|
+
# `:input_objects` (per-`Schema::InputObject` argument table),
|
|
57
|
+
# `:mutations` (per-`Schema::Mutation` arguments+fields table).
|
|
58
|
+
# Any subset may be empty when no recognisable declaration
|
|
59
|
+
# of that kind is found.
|
|
59
60
|
def scan(paths:)
|
|
60
61
|
acc = empty_accumulator
|
|
61
62
|
paths.each do |path|
|
|
@@ -97,10 +98,9 @@ module Rigor
|
|
|
97
98
|
private_class_method :scan_file
|
|
98
99
|
|
|
99
100
|
# Walks the AST collecting `class X < GraphQL::Schema::Object`,
|
|
100
|
-
# `
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
# publish multiple cross-plugin facts from one walk.
|
|
101
|
+
# `Schema::Enum`, `Schema::InputObject`, and `Schema::Mutation`
|
|
102
|
+
# decls at any nesting level. Returns a 4-key hash so the
|
|
103
|
+
# caller can publish multiple cross-plugin facts from one walk.
|
|
104
104
|
def collect_definitions(node, qualified_prefix)
|
|
105
105
|
return empty_accumulator if node.nil?
|
|
106
106
|
|
|
@@ -203,10 +203,9 @@ module Rigor
|
|
|
203
203
|
# The first positional must be a String literal — the
|
|
204
204
|
# graphql-ruby `value` API also accepts a Symbol form
|
|
205
205
|
# (`value :ACTIVE`) but the documented idiom is String.
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
# the floor.
|
|
206
|
+
# Only the GraphQL-side value name is stored; the optional
|
|
207
|
+
# `value:` kwarg (Ruby-side override) and `description:`
|
|
208
|
+
# are omitted from the published table.
|
|
210
209
|
def collect_values(body)
|
|
211
210
|
return [] if body.nil?
|
|
212
211
|
|
|
@@ -25,10 +25,10 @@ module Rigor
|
|
|
25
25
|
# resolver methods themselves; rigor's value here is producing a
|
|
26
26
|
# static type table downstream consumers can cross-reference.
|
|
27
27
|
#
|
|
28
|
-
# ## What downstream consumers DO with
|
|
28
|
+
# ## What downstream consumers DO with the published facts
|
|
29
29
|
#
|
|
30
|
-
# The
|
|
31
|
-
# demand-driven,
|
|
30
|
+
# The tables are the substrate for two future capabilities
|
|
31
|
+
# (demand-driven, not yet implemented):
|
|
32
32
|
#
|
|
33
33
|
# - Resolver-method check: for each `field :name, Type` whose
|
|
34
34
|
# `name` is also defined as a Ruby method on the class, verify
|
|
@@ -37,33 +37,25 @@ module Rigor
|
|
|
37
37
|
# plugin could type `Schema.execute(query).to_h` against the
|
|
38
38
|
# queried fields.
|
|
39
39
|
#
|
|
40
|
-
# ##
|
|
40
|
+
# ## What's recognised
|
|
41
41
|
#
|
|
42
|
-
#
|
|
42
|
+
# - `class T < GraphQL::Schema::Object` subclasses (including
|
|
43
|
+
# nested namespaces); `field :name, Type, null: ...`
|
|
44
|
+
# declarations with constant-reference or list-array types
|
|
45
|
+
# and GraphQL→Ruby scalar mapping.
|
|
46
|
+
# - `class T < GraphQL::Schema::Enum`; `value "ACTIVE"` calls.
|
|
47
|
+
# - `class T < GraphQL::Schema::InputObject` /
|
|
48
|
+
# `GraphQL::Schema::Mutation`; `argument :name, Type,
|
|
49
|
+
# required: ...` declarations.
|
|
50
|
+
# - No user-facing diagnostics yet.
|
|
43
51
|
#
|
|
44
|
-
#
|
|
45
|
-
# (including nested namespaces: `class Types::User < ...`,
|
|
46
|
-
# `module Types; class User < ...; end; end`).
|
|
47
|
-
# - Recognises the `field :name, Type, **opts` declaration with:
|
|
48
|
-
# - `Type` as a `ConstantReadNode` / `ConstantPathNode` (`String`
|
|
49
|
-
# / `Integer` / `Boolean` / `Float` / `ID`, or a user-defined
|
|
50
|
-
# `Types::OtherObject`).
|
|
51
|
-
# - `null: true` / `null: false` keyword extracts nullability.
|
|
52
|
-
# - Maps the canonical GraphQL scalar names to underlying Ruby
|
|
53
|
-
# classes (`String` → `String`, `Integer` → `Integer`,
|
|
54
|
-
# `Boolean` → `TrueClass`, `Float` → `Float`, `ID` → `String`).
|
|
55
|
-
# - Publishes the table; no user-facing diagnostics yet.
|
|
52
|
+
# ## Deferred (demand-driven)
|
|
56
53
|
#
|
|
57
|
-
# The **ceiling** (future slices, demand-driven):
|
|
58
|
-
#
|
|
59
|
-
# - **`GraphQL::Schema::Enum`** with `value "ACTIVE"` calls.
|
|
60
|
-
# - **`GraphQL::Schema::Mutation`** + **`GraphQL::Schema::InputObject`**.
|
|
61
|
-
# - **List / Non-Null wrappers** (`[String]`, `String.array`).
|
|
62
54
|
# - **`resolver:` / `mutation:` reroute** recognition.
|
|
63
55
|
# - **String type expressions** (`field :foo, "User"`) — defeats
|
|
64
56
|
# static resolution by design (graphql-ruby's `BuildType.parse_type`
|
|
65
57
|
# constantizes at runtime); a future slice could surface these
|
|
66
|
-
# as `graphql.string-type` `:info` diagnostics
|
|
58
|
+
# as `graphql.string-type` `:info` diagnostics pointing the
|
|
67
59
|
# user at the constant-reference form for static typing.
|
|
68
60
|
class Graphql < Rigor::Plugin::Base
|
|
69
61
|
manifest(
|
|
@@ -71,9 +71,9 @@ module Rigor
|
|
|
71
71
|
# - `is_a?(Result::Ok)` / `Some` / `None` exhaustive
|
|
72
72
|
# narrowing — core control-flow analysis over a sealed
|
|
73
73
|
# hierarchy, not a plugin surface.
|
|
74
|
-
# - The `variants do variant Const, Type end` Enum DSL
|
|
75
|
-
#
|
|
76
|
-
#
|
|
74
|
+
# - The `variants do variant Const, Type end` Enum DSL is handled
|
|
75
|
+
# via ADR-36 `nested_class_templates:` in this plugin's manifest
|
|
76
|
+
# (Slice A — see `nested_class_templates:` block below).
|
|
77
77
|
class Mangrove < Rigor::Plugin::Base
|
|
78
78
|
manifest(
|
|
79
79
|
id: "mangrove",
|
|
@@ -30,10 +30,10 @@ module Rigor
|
|
|
30
30
|
#
|
|
31
31
|
# ## Configuration
|
|
32
32
|
#
|
|
33
|
-
# No knobs
|
|
34
|
-
# in `.rigor.yml`.
|
|
33
|
+
# No configuration knobs. Activate via
|
|
34
|
+
# `plugins: ["rigor-minitest"]` in `.rigor.yml`.
|
|
35
35
|
#
|
|
36
|
-
# ## Limitations
|
|
36
|
+
# ## Limitations
|
|
37
37
|
#
|
|
38
38
|
# - **No `assert_raises(T) { ... }`** — that's a block-shape
|
|
39
39
|
# matcher and Rigor's narrowing model is for
|
|
@@ -169,7 +169,7 @@ module Rigor
|
|
|
169
169
|
|
|
170
170
|
# Extracts the literal-string first argument when
|
|
171
171
|
# present. Returns nil for variable / expression keys —
|
|
172
|
-
#
|
|
172
|
+
# only literal keys are statically validated.
|
|
173
173
|
def literal_key_for(call_node)
|
|
174
174
|
args = call_node.arguments&.arguments || []
|
|
175
175
|
return nil if args.empty?
|
|
@@ -224,9 +224,7 @@ module Rigor
|
|
|
224
224
|
|
|
225
225
|
def collect_assoc_keys(hash_node)
|
|
226
226
|
# Both `Prism::HashNode` and `Prism::KeywordHashNode`
|
|
227
|
-
# expose `#elements
|
|
228
|
-
# accidental no-op carried over from an earlier
|
|
229
|
-
# draft.
|
|
227
|
+
# expose `#elements`, so a single path handles both.
|
|
230
228
|
hash_node.elements.filter_map do |element|
|
|
231
229
|
next nil unless element.is_a?(Prism::AssocNode)
|
|
232
230
|
|
|
@@ -57,7 +57,7 @@ module Rigor
|
|
|
57
57
|
parsed.each do |locale, tree|
|
|
58
58
|
locale = locale.to_s
|
|
59
59
|
locales << locale
|
|
60
|
-
|
|
60
|
+
each_flattened(tree, []) do |dotted_key, value|
|
|
61
61
|
placeholders = (per_key[dotted_key] ||= {})
|
|
62
62
|
placeholders[locale] = extract_placeholders(value)
|
|
63
63
|
kinds = (per_key_kinds[dotted_key] ||= {})
|
|
@@ -101,24 +101,40 @@ module Rigor
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Recursively walks the per-locale subtree, yielding
|
|
104
|
-
# `[dotted_key, leaf_value]`
|
|
104
|
+
# `[dotted_key, leaf_value]` for each leaf. Hash leaves are
|
|
105
105
|
# *not* recorded as entries themselves — only their
|
|
106
|
-
# descendants — but every leaf scalar / array IS
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
# descendants — but every leaf scalar / array IS recorded.
|
|
107
|
+
#
|
|
108
|
+
# `breadcrumbs` is a single mutable stack reused across the
|
|
109
|
+
# whole walk (push before recursing, pop after): for the
|
|
110
|
+
# 530-file / 14 MB Mastodon locale corpus the old
|
|
111
|
+
# `flat_map { flatten_tree(v, breadcrumbs + [k]) }` shape
|
|
112
|
+
# allocated a fresh breadcrumb Array at every node plus an
|
|
113
|
+
# intermediate result Array at every level — millions of
|
|
114
|
+
# short-lived objects and the run's top allocation site. The
|
|
115
|
+
# dotted key is still materialised once per leaf (it has to
|
|
116
|
+
# be); everything else is now allocation-free traversal.
|
|
117
|
+
def each_flattened(node, breadcrumbs, &)
|
|
118
|
+
if node.is_a?(Hash)
|
|
119
|
+
node.each do |k, v|
|
|
120
|
+
breadcrumbs.push(k.to_s)
|
|
121
|
+
each_flattened(v, breadcrumbs, &)
|
|
122
|
+
breadcrumbs.pop
|
|
113
123
|
end
|
|
114
124
|
else
|
|
115
|
-
|
|
125
|
+
yield breadcrumbs.join("."), node
|
|
116
126
|
end
|
|
117
127
|
end
|
|
118
128
|
|
|
119
129
|
def extract_placeholders(value)
|
|
120
130
|
case value
|
|
121
|
-
when String
|
|
131
|
+
when String
|
|
132
|
+
# Most locale leaves carry no `%{var}`; skip the scan +
|
|
133
|
+
# flatten + to_set allocation trio for them. A string with
|
|
134
|
+
# no `%{` yields an empty placeholder set either way.
|
|
135
|
+
return Set.new unless value.include?("%{")
|
|
136
|
+
|
|
137
|
+
value.scan(PLACEHOLDER_RE).flatten.to_set
|
|
122
138
|
when Array then value.map { |v| extract_placeholders(v) }.reduce(Set.new) { |a, s| a | s }
|
|
123
139
|
else Set.new
|
|
124
140
|
end
|
|
@@ -40,7 +40,7 @@ module Rigor
|
|
|
40
40
|
# arguments. Missing placeholders are errors; extra
|
|
41
41
|
# arguments are warnings.
|
|
42
42
|
#
|
|
43
|
-
# ## Limitations
|
|
43
|
+
# ## Limitations
|
|
44
44
|
#
|
|
45
45
|
# - Only literal-string keys are validated. `t(key)` with
|
|
46
46
|
# a variable receiver is silently passed through.
|
|
@@ -38,11 +38,9 @@ module Rigor
|
|
|
38
38
|
# (`user_facebook_omniauth_authorize_path`) and Devise
|
|
39
39
|
# declares them from the configured providers, which
|
|
40
40
|
# live in an initializer this static parser does not
|
|
41
|
-
# read.
|
|
42
|
-
# `
|
|
43
|
-
#
|
|
44
|
-
# these are NOT in the table but consulted by the
|
|
45
|
-
# `Analyzer.allowed_dynamic_pattern?` check.
|
|
41
|
+
# read. The suffix patterns are registered in
|
|
42
|
+
# `OMNIAUTH_SUFFIXES` and consulted by
|
|
43
|
+
# `HelperTable#omniauth_match?`.
|
|
46
44
|
module DeviseRoutes
|
|
47
45
|
# The standard Devise controllers and the helper
|
|
48
46
|
# actions each generates. Keys are the controller
|
|
@@ -190,7 +188,7 @@ module Rigor
|
|
|
190
188
|
|
|
191
189
|
# Returns the set of OmniAuth pattern suffixes the
|
|
192
190
|
# analyzer accepts for a given resource. The analyzer
|
|
193
|
-
# consults this set (via `HelperTable#
|
|
191
|
+
# consults this set (via `HelperTable#omniauth_match?`)
|
|
194
192
|
# when a `*_path` / `*_url` call's name does not match
|
|
195
193
|
# any registered entry and its prefix matches a Devise
|
|
196
194
|
# resource.
|
|
@@ -1171,9 +1171,10 @@ module Rigor
|
|
|
1171
1171
|
end
|
|
1172
1172
|
|
|
1173
1173
|
def in_singular_resource?(*)
|
|
1174
|
-
#
|
|
1175
|
-
#
|
|
1176
|
-
#
|
|
1174
|
+
# Stub: always returns true so member / collection
|
|
1175
|
+
# blocks descend. The singular-resource frame
|
|
1176
|
+
# (`push_singular_resource`) is modelled in Context;
|
|
1177
|
+
# a future caller could use it to tighten this check.
|
|
1177
1178
|
true
|
|
1178
1179
|
end
|
|
1179
1180
|
|
|
@@ -1181,21 +1182,14 @@ module Rigor
|
|
|
1181
1182
|
# `plural: true` for `resources :users`, `false` for
|
|
1182
1183
|
# `resource :profile`.
|
|
1183
1184
|
def register_resourceful_helpers(name, actions, base_arity, context, plural:)
|
|
1184
|
-
#
|
|
1185
|
-
#
|
|
1186
|
-
#
|
|
1187
|
-
#
|
|
1188
|
-
#
|
|
1189
|
-
#
|
|
1190
|
-
# `relationship_path
|
|
1191
|
-
#
|
|
1192
|
-
# Plural resources singularise for show / new / edit
|
|
1193
|
-
# helpers (`resources :users` → `user_path(id)`);
|
|
1194
|
-
# singular resources use the name AS-IS even when it
|
|
1195
|
-
# looks plural (Mastodon's `resource :relationships,
|
|
1196
|
-
# only: [:show, :update]` → `relationships_path`).
|
|
1197
|
-
# The path segment uses `name` in both shapes — Rails
|
|
1198
|
-
# never singularises the URL.
|
|
1185
|
+
# Plural resources (`resources :users`) singularise
|
|
1186
|
+
# for show / new / edit helpers → `user_path(id)`.
|
|
1187
|
+
# Singular resources (`resource :foo`) use the name
|
|
1188
|
+
# AS-IS — singularising would mangle deliberately-
|
|
1189
|
+
# plural names like Mastodon's `resource
|
|
1190
|
+
# :relationships` → `relationships_path`, not
|
|
1191
|
+
# `relationship_path`. The URL path uses `name`
|
|
1192
|
+
# in both shapes (Rails never singularises the URL).
|
|
1199
1193
|
singular = plural ? singularize_word(name.to_s) : name.to_s
|
|
1200
1194
|
path_base = "#{context.path_prefix}/#{name}"
|
|
1201
1195
|
|
|
@@ -26,9 +26,9 @@ module Rigor
|
|
|
26
26
|
# - One level of nested `resources`
|
|
27
27
|
#
|
|
28
28
|
# The plugin publishes its parsed `:helper_table` through
|
|
29
|
-
# the ADR-9 cross-plugin fact store
|
|
30
|
-
#
|
|
31
|
-
#
|
|
29
|
+
# the ADR-9 cross-plugin fact store; `rigor-actionpack`
|
|
30
|
+
# Phase 4 consumes it for route-helper validation in
|
|
31
|
+
# controller code.
|
|
32
32
|
#
|
|
33
33
|
# ## Configuration
|
|
34
34
|
#
|
|
@@ -147,8 +147,8 @@ module Rigor
|
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
# Publishes the parsed table to the cross-plugin fact
|
|
150
|
-
# store
|
|
151
|
-
#
|
|
150
|
+
# store; `rigor-actionpack` Phase 4 reads it via
|
|
151
|
+
# `services.fact_store.read`.
|
|
152
152
|
def prepare(services)
|
|
153
153
|
table = helper_table_or_nil
|
|
154
154
|
return if table.nil?
|
|
@@ -21,10 +21,9 @@ module Rigor
|
|
|
21
21
|
# { ... }` and `subject(:name) { ... }` declarations.
|
|
22
22
|
# `:subject` is the key for the implicit `subject { ... }`.
|
|
23
23
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# block's inferred type.
|
|
24
|
+
# Used by the plugin's let-binding `dynamic_return`
|
|
25
|
+
# rule to bind `let`-named method-shape calls inside
|
|
26
|
+
# `it` bodies to the let block's inferred type.
|
|
28
27
|
class LetScopeIndex
|
|
29
28
|
Record = Struct.new(:outer_range, :describe_const, :lets, keyword_init: true) do
|
|
30
29
|
def contains?(line) = outer_range.cover?(line)
|
|
@@ -6,7 +6,7 @@ require "rigor/type"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class Rspec < Rigor::Plugin::Base
|
|
9
|
-
#
|
|
9
|
+
# Resolves the runtime type of a
|
|
10
10
|
# `let(:name) { body }` or `subject(:name) { body }`
|
|
11
11
|
# block by pattern-matching its body's tail expression.
|
|
12
12
|
#
|
|
@@ -8,7 +8,7 @@ require "rigor/flow_contribution/fact"
|
|
|
8
8
|
module Rigor
|
|
9
9
|
module Plugin
|
|
10
10
|
class Rspec < Rigor::Plugin::Base
|
|
11
|
-
#
|
|
11
|
+
# Recognises `expect(x).to MATCHER`
|
|
12
12
|
# patterns at per-call recognition time and emits
|
|
13
13
|
# `post_return_facts` that narrow the named local on the
|
|
14
14
|
# post-call edge.
|
|
@@ -11,15 +11,13 @@ require_relative "rspec/let_type_resolver"
|
|
|
11
11
|
module Rigor
|
|
12
12
|
module Plugin
|
|
13
13
|
# rigor-rspec — validates RSpec `let` / `subject`
|
|
14
|
-
# declarations within each describe / context scope
|
|
14
|
+
# declarations within each describe / context scope,
|
|
15
|
+
# narrows `expect(x).to MATCHER` assertions downstream,
|
|
16
|
+
# and binds `let`/`subject` locals to their inferred
|
|
17
|
+
# return types inside `it` bodies.
|
|
15
18
|
#
|
|
16
19
|
# Tier 3A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
|
-
#
|
|
18
|
-
# larger plugin (let-typo detection in `it` bodies,
|
|
19
|
-
# `expect(x).to receive(:method)` mock-target
|
|
20
|
-
# validation). Both are out of scope for v0.1.0; this
|
|
21
|
-
# plugin ships the two checks that have the lowest
|
|
22
|
-
# false-positive risk:
|
|
20
|
+
# Ships four checks:
|
|
23
21
|
#
|
|
24
22
|
# 1. **Duplicate `let` / `subject` declarations** in
|
|
25
23
|
# the same scope (`warning`). RSpec's runtime lets
|
|
@@ -31,14 +29,21 @@ module Rigor
|
|
|
31
29
|
# (`error`). At runtime this infinite-loops; users
|
|
32
30
|
# typically meant to call a different method or
|
|
33
31
|
# forgot to introduce a `super`.
|
|
32
|
+
# 3. **`expect(x).to MATCHER` narrowing** — narrows
|
|
33
|
+
# the named local `x` on the post-call edge for
|
|
34
|
+
# eight matchers (see `MatcherAnalyzer`).
|
|
35
|
+
# 4. **`let`/`subject` local binding** — binds `let`
|
|
36
|
+
# / `subject` names in `it` bodies to the block's
|
|
37
|
+
# inferred return type (see `LetTypeResolver`).
|
|
34
38
|
#
|
|
35
39
|
# ## Configuration
|
|
36
40
|
#
|
|
37
|
-
# No knobs
|
|
38
|
-
# file
|
|
39
|
-
# files outside the project's `paths:` are not
|
|
41
|
+
# No configuration knobs. The plugin walks every
|
|
42
|
+
# analysed file for `RSpec.describe ... do` blocks;
|
|
43
|
+
# spec files outside the project's `paths:` are not
|
|
44
|
+
# scanned.
|
|
40
45
|
#
|
|
41
|
-
# ## Limitations
|
|
46
|
+
# ## Limitations
|
|
42
47
|
#
|
|
43
48
|
# - **No let-typo detection.** Detecting an `it`
|
|
44
49
|
# block's reference to a misspelled `let` name
|
|
@@ -102,14 +107,14 @@ module Rigor
|
|
|
102
107
|
Analyzer.diagnose(path: path, root: root).map { |diag| build_diagnostic(diag) }
|
|
103
108
|
end
|
|
104
109
|
|
|
105
|
-
# ADR-37 slice 2 —
|
|
110
|
+
# ADR-37 slice 2 — matcher narrowing
|
|
106
111
|
# (`expect(x).to be_a(T)` → `post_return_facts` on `x`),
|
|
107
112
|
# method-gated by the engine on the expectation verbs.
|
|
108
113
|
type_specifier methods: %i[to not_to to_not] do |call_node, scope|
|
|
109
114
|
MatcherAnalyzer.contribution_for(call_node, environment: scope&.environment)&.post_return_facts
|
|
110
115
|
end
|
|
111
116
|
|
|
112
|
-
#
|
|
117
|
+
# ADR-52 slice 5a — binds local reads in `it` /
|
|
113
118
|
# spec bodies to their `let(:name) { ... }` block's inferred return
|
|
114
119
|
# type. The name set varies per file (each spec file's
|
|
115
120
|
# `describe`/`let` structure), so the rule gates on the per-file
|
|
@@ -130,7 +135,7 @@ module Rigor
|
|
|
130
135
|
let_scope_index_for(path)&.let_names || []
|
|
131
136
|
end
|
|
132
137
|
|
|
133
|
-
#
|
|
138
|
+
# When the call node is a no-receiver
|
|
134
139
|
# method call (`user`, `subject`, etc.) inside an RSpec
|
|
135
140
|
# `describe` block whose lets include a matching name,
|
|
136
141
|
# return the let block's inferred type.
|
|
@@ -9,10 +9,11 @@ module Rigor
|
|
|
9
9
|
# analyzer can validate `Worker.perform_async(...)`
|
|
10
10
|
# call sites.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
12
|
+
# Uses the same `min_arity` / `max_arity` closed-range
|
|
13
|
+
# envelope as `rigor-activejob`'s `JobIndex::Entry`
|
|
14
|
+
# (`Float::INFINITY` upper bound when `*args` is present);
|
|
15
|
+
# Sidekiq workers serialize args to JSON so keyword arity
|
|
16
|
+
# is not tracked here.
|
|
16
17
|
class WorkerIndex
|
|
17
18
|
Entry = Data.define(:class_name, :min_arity, :max_arity) do
|
|
18
19
|
def arity_label
|
|
@@ -168,9 +168,8 @@ module Rigor
|
|
|
168
168
|
# contribution mirrors `T.must` minus the nil-stripping:
|
|
169
169
|
# the call's return type is the inner expression's
|
|
170
170
|
# inferred type. The companion diagnostic is emitted by
|
|
171
|
-
# the plugin's `diagnostics_for_file` hook
|
|
172
|
-
#
|
|
173
|
-
# contribution-only.
|
|
171
|
+
# the plugin's `diagnostics_for_file` hook; this
|
|
172
|
+
# method handles the contribution only.
|
|
174
173
|
def resolve_reveal_type(call_node, scope)
|
|
175
174
|
inner = nth_argument(call_node, 0)
|
|
176
175
|
return Rigor::Type::Combinator.untyped if inner.nil? || scope.nil?
|
|
@@ -4,22 +4,18 @@ module Rigor
|
|
|
4
4
|
module Plugin
|
|
5
5
|
class Sorbet < Rigor::Plugin::Base
|
|
6
6
|
# Frozen description of one Sorbet `sig` block as parsed by
|
|
7
|
-
# {SigParser}. Holds
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# compatibility).
|
|
7
|
+
# {SigParser}. Holds the return type, parameter shape, and
|
|
8
|
+
# modifier list. Call-site argument-type checking and
|
|
9
|
+
# override-compatibility validation are deferred; only the
|
|
10
|
+
# return-type contribution is active.
|
|
12
11
|
#
|
|
13
12
|
# `kind` distinguishes `def foo` (`:instance`) from
|
|
14
13
|
# `def self.foo` / `class << self; def foo; end`
|
|
15
14
|
# (`:singleton`).
|
|
16
15
|
#
|
|
17
|
-
# `modifiers`
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# later slices wire `:abstract` into the existing
|
|
21
|
-
# `def.return-type-mismatch` check and `:override` into
|
|
22
|
-
# override-compatibility validation.
|
|
16
|
+
# `modifiers` records the `sig`-level flags observed:
|
|
17
|
+
# `:abstract`, `:override`, `:overridable`, `:final`.
|
|
18
|
+
# Currently stored but not acted on.
|
|
23
19
|
MethodSignature = Data.define(
|
|
24
20
|
:class_name, :method_name, :kind, :params, :return_type, :modifiers
|
|
25
21
|
)
|
|
@@ -26,9 +26,8 @@ module Rigor
|
|
|
26
26
|
# whatever it recognises (`params` / `returns` / `void` /
|
|
27
27
|
# `abstract` / `override` / `overridable` / `final` /
|
|
28
28
|
# `type_parameters` / `checked` / `on_failure`) into a
|
|
29
|
-
# frozen result hash
|
|
30
|
-
#
|
|
31
|
-
# on the modifiers and `type_parameters`.
|
|
29
|
+
# frozen result hash stored in {MethodSignature}. Modifiers
|
|
30
|
+
# and `type_parameters` are recorded but not yet acted on.
|
|
32
31
|
#
|
|
33
32
|
# The parser is intentionally tolerant — unknown chain
|
|
34
33
|
# nodes degrade to "the rest of the chain is opaque" rather
|
|
@@ -93,8 +92,8 @@ module Rigor
|
|
|
93
92
|
when :params
|
|
94
93
|
accumulator[:params].merge!(parse_params(current))
|
|
95
94
|
when :type_parameters
|
|
96
|
-
#
|
|
97
|
-
#
|
|
95
|
+
# Recognised to suppress the degraded path;
|
|
96
|
+
# payload intentionally discarded (deferred).
|
|
98
97
|
when *RECOGNISED_MODIFIERS
|
|
99
98
|
accumulator[:modifiers] << current.name
|
|
100
99
|
when *RUNTIME_ONLY_STEPS
|