rigortype 0.1.15 → 0.1.17
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 +4 -2
- data/exe/rigor +19 -0
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +209 -0
- data/lib/rigor/analysis/check_rules.rb +174 -71
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +58 -0
- data/lib/rigor/analysis/incremental.rb +162 -0
- data/lib/rigor/analysis/incremental_session.rb +337 -0
- data/lib/rigor/analysis/rule_catalog.rb +48 -0
- data/lib/rigor/analysis/runner.rb +485 -29
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/analysis/worker_session.rb +3 -2
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +56 -51
- data/lib/rigor/cache/incremental_snapshot.rb +147 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +30 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
- data/lib/rigor/cache/rbs_constant_table.rb +2 -8
- data/lib/rigor/cache/rbs_environment.rb +2 -8
- data/lib/rigor/cache/rbs_instance_definitions.rb +3 -16
- data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +99 -1
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/command.rb +47 -0
- data/lib/rigor/cli/coverage_command.rb +3 -23
- data/lib/rigor/cli/coverage_renderer.rb +3 -8
- data/lib/rigor/cli/diff_command.rb +3 -7
- data/lib/rigor/cli/explain_command.rb +2 -7
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mcp_command.rb +3 -7
- data/lib/rigor/cli/options.rb +57 -0
- data/lib/rigor/cli/plugin_command.rb +3 -7
- data/lib/rigor/cli/plugins_command.rb +52 -10
- data/lib/rigor/cli/plugins_renderer.rb +86 -1
- data/lib/rigor/cli/renderable.rb +26 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -7
- data/lib/rigor/cli/skill_command.rb +3 -7
- data/lib/rigor/cli/triage_command.rb +2 -7
- data/lib/rigor/cli/type_of_command.rb +5 -38
- data/lib/rigor/cli/type_of_renderer.rb +4 -9
- data/lib/rigor/cli/type_scan_command.rb +3 -23
- data/lib/rigor/cli/type_scan_renderer.rb +4 -9
- data/lib/rigor/cli.rb +260 -48
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +13 -3
- data/lib/rigor/environment/rbs_loader.rb +335 -4
- data/lib/rigor/environment.rb +8 -2
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- data/lib/rigor/inference/budget_trace.rb +137 -0
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
- data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
- data/lib/rigor/inference/expression_typer.rb +149 -22
- data/lib/rigor/inference/hkt_reducer.rb +2 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
- data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -6
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +100 -23
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher.rb +147 -60
- data/lib/rigor/inference/narrowing.rb +202 -5
- data/lib/rigor/inference/precision_scanner.rb +60 -1
- data/lib/rigor/inference/scope_indexer.rb +257 -11
- data/lib/rigor/inference/statement_evaluator.rb +110 -26
- data/lib/rigor/inference/synthetic_method_index.rb +23 -4
- data/lib/rigor/inference/synthetic_method_scanner.rb +148 -14
- data/lib/rigor/language_server/buffer_resolution.rb +33 -0
- data/lib/rigor/language_server/completion_provider.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
- data/lib/rigor/language_server/folding_range_provider.rb +4 -4
- data/lib/rigor/language_server/hover_provider.rb +4 -4
- data/lib/rigor/language_server/selection_range_provider.rb +4 -4
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/plugin/additional_initializer.rb +108 -0
- data/lib/rigor/plugin/base.rb +337 -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 +49 -1
- data/lib/rigor/plugin.rb +3 -0
- data/lib/rigor/rbs_extended/conformance_checker.rb +208 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope.rb +123 -9
- 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/type/acceptance_router.rb +19 -0
- data/lib/rigor/type/accepts_result.rb +3 -10
- data/lib/rigor/type/app.rb +3 -7
- data/lib/rigor/type/bot.rb +2 -3
- data/lib/rigor/type/bound_method.rb +5 -12
- data/lib/rigor/type/combinator.rb +23 -1
- data/lib/rigor/type/constant.rb +2 -3
- data/lib/rigor/type/data_class.rb +80 -0
- data/lib/rigor/type/data_instance.rb +100 -0
- data/lib/rigor/type/difference.rb +5 -10
- data/lib/rigor/type/dynamic.rb +5 -10
- data/lib/rigor/type/hash_shape.rb +5 -15
- data/lib/rigor/type/integer_range.rb +5 -10
- data/lib/rigor/type/intersection.rb +5 -10
- data/lib/rigor/type/nominal.rb +5 -10
- data/lib/rigor/type/refined.rb +5 -10
- data/lib/rigor/type/singleton.rb +5 -10
- data/lib/rigor/type/top.rb +2 -3
- data/lib/rigor/type/tuple.rb +5 -10
- data/lib/rigor/type/union.rb +69 -10
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/value_semantics.rb +77 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -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 +48 -33
- 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/cache.rbs +19 -0
- data/sig/rigor/inference.rbs +22 -0
- 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/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +5 -0
- data/sig/rigor/source.rbs +12 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +11 -1
- data/skills/rigor-plugin-author/SKILL.md +13 -9
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +6 -5
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +159 -75
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +3 -3
- metadata +73 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/inflector.rb +0 -114
|
@@ -58,14 +58,11 @@ module Rigor
|
|
|
58
58
|
version: "0.1.0",
|
|
59
59
|
description: "Validates Sidekiq `Worker.perform_async` argument arity.",
|
|
60
60
|
config_schema: {
|
|
61
|
-
"worker_search_paths" => :array,
|
|
62
|
-
"worker_marker_modules" => :array
|
|
61
|
+
"worker_search_paths" => { kind: :array, default: ["app/workers", "app/sidekiq"] },
|
|
62
|
+
"worker_marker_modules" => { kind: :array, default: %w[Sidekiq::Job Sidekiq::Worker] }
|
|
63
63
|
}
|
|
64
64
|
)
|
|
65
65
|
|
|
66
|
-
DEFAULT_WORKER_SEARCH_PATHS = ["app/workers", "app/sidekiq"].freeze
|
|
67
|
-
DEFAULT_WORKER_MARKER_MODULES = %w[Sidekiq::Job Sidekiq::Worker].freeze
|
|
68
|
-
|
|
69
66
|
producer :worker_index do |_params|
|
|
70
67
|
WorkerDiscoverer.new(
|
|
71
68
|
io_boundary: io_boundary,
|
|
@@ -75,20 +72,30 @@ module Rigor
|
|
|
75
72
|
end
|
|
76
73
|
|
|
77
74
|
def init(_services)
|
|
78
|
-
@worker_search_paths = Array(config.fetch("worker_search_paths"
|
|
79
|
-
@worker_marker_modules = Array(
|
|
80
|
-
config.fetch("worker_marker_modules", DEFAULT_WORKER_MARKER_MODULES)
|
|
81
|
-
).map(&:to_s)
|
|
75
|
+
@worker_search_paths = Array(config.fetch("worker_search_paths")).map(&:to_s)
|
|
76
|
+
@worker_marker_modules = Array(config.fetch("worker_marker_modules")).map(&:to_s)
|
|
82
77
|
@worker_index = nil
|
|
83
78
|
@load_error = nil
|
|
84
79
|
end
|
|
85
80
|
|
|
81
|
+
# File-level only: the load-error emission. The per-call arity
|
|
82
|
+
# validation runs over the engine-owned walk via the node_rule
|
|
83
|
+
# below (ADR-37). The worker index is lazily loaded + memoised by
|
|
84
|
+
# worker_index_or_nil, shared by both surfaces.
|
|
86
85
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
87
86
|
index = worker_index_or_nil
|
|
88
87
|
return [load_error_diagnostic(path)] if index.nil? && @load_error
|
|
89
|
-
return [] if index.nil? || index.empty?
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
[]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
93
|
+
index = worker_index_or_nil
|
|
94
|
+
next [] if index.nil? || index.empty?
|
|
95
|
+
|
|
96
|
+
Analyzer.violations_for(call_node: node, worker_index: index).map do |violation|
|
|
97
|
+
diagnostic(node, path: path, message: violation.message, severity: violation.severity, rule: violation.rule)
|
|
98
|
+
end
|
|
92
99
|
end
|
|
93
100
|
|
|
94
101
|
private
|
|
@@ -96,7 +103,8 @@ module Rigor
|
|
|
96
103
|
def worker_index_or_nil
|
|
97
104
|
return @worker_index if @worker_index
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
descriptor = glob_descriptor(@worker_search_paths, "**/*.rb")
|
|
107
|
+
@worker_index = cache_for(:worker_index, params: {}, descriptor: descriptor).call
|
|
100
108
|
rescue StandardError => e
|
|
101
109
|
@load_error = "rigor-sidekiq: failed to discover workers: #{e.class}: #{e.message}"
|
|
102
110
|
nil
|
|
@@ -110,13 +118,6 @@ module Rigor
|
|
|
110
118
|
rule: "load-error"
|
|
111
119
|
)
|
|
112
120
|
end
|
|
113
|
-
|
|
114
|
-
def build_diagnostic(diag)
|
|
115
|
-
Rigor::Analysis::Diagnostic.new(
|
|
116
|
-
path: diag.path, line: diag.line, column: diag.column,
|
|
117
|
-
message: diag.message, severity: diag.severity, rule: diag.rule
|
|
118
|
-
)
|
|
119
|
-
end
|
|
120
121
|
end
|
|
121
122
|
|
|
122
123
|
Rigor::Plugin.register(Sidekiq)
|
|
@@ -106,6 +106,21 @@ module Rigor
|
|
|
106
106
|
if pending_sig && def_node?(child)
|
|
107
107
|
record_def_with_sig(child, pending_sig, state, lexical_path, in_singleton_class)
|
|
108
108
|
pending_sig = nil
|
|
109
|
+
elsif pending_sig && attr_macro_call?(child)
|
|
110
|
+
# `sig { returns(T) }` above `attr_reader :name` /
|
|
111
|
+
# `attr_accessor` / `attr_writer` types the generated
|
|
112
|
+
# accessor(s). This is a first-class Sorbet idiom — not
|
|
113
|
+
# a dangling sig — so record the accessor signature(s)
|
|
114
|
+
# instead of warning.
|
|
115
|
+
record_attr_with_sig(child, pending_sig, state, lexical_path, in_singleton_class)
|
|
116
|
+
pending_sig = nil
|
|
117
|
+
elsif pending_sig && (inner_def = visibility_wrapped_def(child))
|
|
118
|
+
# `sig { ... }` above `private def foo` /
|
|
119
|
+
# `private_class_method def self.foo` / `module_function
|
|
120
|
+
# def bar` — a visibility macro wrapping the very def the
|
|
121
|
+
# sig types. Record the inner def, not a dangling sig.
|
|
122
|
+
record_def_with_sig(inner_def, pending_sig, state, lexical_path, in_singleton_class)
|
|
123
|
+
pending_sig = nil
|
|
109
124
|
elsif sig_call?(child)
|
|
110
125
|
state.record_error(:duplicate_sig, pending_sig) if pending_sig
|
|
111
126
|
pending_sig = child
|
|
@@ -171,6 +186,64 @@ module Rigor
|
|
|
171
186
|
node.is_a?(Prism::DefNode)
|
|
172
187
|
end
|
|
173
188
|
|
|
189
|
+
VISIBILITY_MACROS = %i[
|
|
190
|
+
private public protected
|
|
191
|
+
private_class_method public_class_method module_function
|
|
192
|
+
].freeze
|
|
193
|
+
|
|
194
|
+
# `private def foo` / `private_class_method def self.bar` /
|
|
195
|
+
# `module_function def baz` — a receiverless visibility macro
|
|
196
|
+
# whose single argument is the `def` it wraps. Returns the
|
|
197
|
+
# inner `Prism::DefNode` (so a preceding sig types it), or nil.
|
|
198
|
+
# The def's own receiver still drives instance-vs-singleton, so
|
|
199
|
+
# `private_class_method def self.bar` records as singleton.
|
|
200
|
+
def visibility_wrapped_def(node)
|
|
201
|
+
return nil unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
202
|
+
return nil unless VISIBILITY_MACROS.include?(node.name)
|
|
203
|
+
|
|
204
|
+
args = node.arguments&.arguments
|
|
205
|
+
return nil unless args&.size == 1 && args.first.is_a?(Prism::DefNode)
|
|
206
|
+
|
|
207
|
+
args.first
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
ATTR_MACROS = %i[attr_reader attr_writer attr_accessor].freeze
|
|
211
|
+
|
|
212
|
+
# `attr_reader :a, :b` / `attr_writer` / `attr_accessor` —
|
|
213
|
+
# a receiverless attribute macro whose arguments are all
|
|
214
|
+
# symbol literals (the only shape a preceding `sig` types).
|
|
215
|
+
def attr_macro_call?(node)
|
|
216
|
+
return false unless node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
217
|
+
return false unless ATTR_MACROS.include?(node.name)
|
|
218
|
+
|
|
219
|
+
args = node.arguments&.arguments
|
|
220
|
+
!args.nil? && !args.empty? && args.all?(Prism::SymbolNode)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Records the method signature(s) a `sig` contributes to the
|
|
224
|
+
# accessor(s) it precedes: the reader `name` and/or the
|
|
225
|
+
# writer `name=`, each carrying the sig's `returns(...)`
|
|
226
|
+
# type. `attr_reader` emits the reader, `attr_writer` the
|
|
227
|
+
# writer, `attr_accessor` both — for every symbol argument.
|
|
228
|
+
def record_attr_with_sig(attr_node, sig_call, state, lexical_path, in_singleton_class)
|
|
229
|
+
parsed = SigParser.parse(sig_call)
|
|
230
|
+
if parsed.is_a?(SigParser::ParseError)
|
|
231
|
+
state.record_error(parsed.reason, sig_call)
|
|
232
|
+
return
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
class_name = lexical_path.empty? ? "Object" : lexical_path.join("::")
|
|
236
|
+
kind = in_singleton_class ? :singleton : :instance
|
|
237
|
+
reader = attr_node.name != :attr_writer
|
|
238
|
+
writer = attr_node.name != :attr_reader
|
|
239
|
+
|
|
240
|
+
attr_node.arguments.arguments.each do |arg|
|
|
241
|
+
name = arg.unescaped
|
|
242
|
+
catalog_record(state.catalog, class_name, name.to_sym, kind, parsed) if reader
|
|
243
|
+
catalog_record(state.catalog, class_name, :"#{name}=", kind, parsed) if writer
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
174
247
|
def record_def_with_sig(def_node, sig_call, state, lexical_path, in_singleton_class)
|
|
175
248
|
parsed = SigParser.parse(sig_call)
|
|
176
249
|
if parsed.is_a?(SigParser::ParseError)
|
|
@@ -38,6 +38,7 @@ module Rigor
|
|
|
38
38
|
# | `T.class_of(C)` | `Singleton[C]` |
|
|
39
39
|
# | `[A, B]` (tuple in sig) | `Tuple[A, B]` |
|
|
40
40
|
# | `{a: A, b: B}` (shape) | `HashShape{a: A, b: B}` (closed) |
|
|
41
|
+
# | `Foo::Bar[A, B]` (user) | `Nominal["Foo::Bar", [A, B]]` |
|
|
41
42
|
#
|
|
42
43
|
# Anything else (`T.proc`, `T.attached_class`,
|
|
43
44
|
# `T.self_type`, `T.type_parameter`, `T::Struct` / `T::Enum`
|
|
@@ -101,16 +102,26 @@ module Rigor
|
|
|
101
102
|
Rigor::Type::Combinator.nominal_of(name)
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
# `Prism::CallNode` covers
|
|
105
|
+
# `Prism::CallNode` covers three distinct surfaces:
|
|
105
106
|
#
|
|
106
107
|
# 1. `T.something(...)` — `untyped` / `anything` /
|
|
107
108
|
# `noreturn` / `nilable` / `any` / `all` / `class_of`.
|
|
108
109
|
# 2. `T::SomeClass[...]` — the `[]` method on a generic
|
|
109
110
|
# `T::*` constant (slice 3 widening). Maps to
|
|
110
111
|
# `Nominal[name, type_args]`.
|
|
112
|
+
# 3. `Some::User::Generic[...]` — the `[]` method on any
|
|
113
|
+
# OTHER constant (a user-defined generic application,
|
|
114
|
+
# e.g. `Mangrove::Result::Ok[String, StandardError]`).
|
|
115
|
+
# Maps to `Nominal[name, type_args]` so generic carriers
|
|
116
|
+
# authored outside Sorbet's `T::*` set round-trip with
|
|
117
|
+
# their instantiation intact (the unwrap-site receiver a
|
|
118
|
+
# plugin like rigor-mangrove reads `type_args` from).
|
|
119
|
+
# Without this branch such forms degraded to `untyped`,
|
|
120
|
+
# silently dropping the receiver's generic arguments.
|
|
111
121
|
def translate_call(node)
|
|
112
122
|
return translate_t_method(node) if sorbet_t_namespaced?(node.receiver)
|
|
113
123
|
return translate_t_subscript(node) if sorbet_subscript?(node)
|
|
124
|
+
return translate_user_subscript(node) if user_generic_subscript?(node)
|
|
114
125
|
|
|
115
126
|
degraded
|
|
116
127
|
end
|
|
@@ -188,6 +199,37 @@ module Rigor
|
|
|
188
199
|
end
|
|
189
200
|
end
|
|
190
201
|
|
|
202
|
+
# A user-defined generic application — a `[]` call on a
|
|
203
|
+
# constant that is NOT `T`-rooted, e.g.
|
|
204
|
+
# `Mangrove::Result::Ok[String, StandardError]` or a
|
|
205
|
+
# top-level `Box[Integer]`. Translates to
|
|
206
|
+
# `Nominal[name, type_args]`, recursively translating each
|
|
207
|
+
# argument (so nested `T::Array[...]` inside a user generic
|
|
208
|
+
# still resolves). Ordered AFTER `translate_t_subscript` in
|
|
209
|
+
# `translate_call`, so the `T::*` forms never reach here.
|
|
210
|
+
def user_generic_subscript?(node)
|
|
211
|
+
return false unless node.name == :[]
|
|
212
|
+
|
|
213
|
+
receiver = node.receiver
|
|
214
|
+
return false unless receiver.is_a?(Prism::ConstantReadNode) ||
|
|
215
|
+
receiver.is_a?(Prism::ConstantPathNode)
|
|
216
|
+
|
|
217
|
+
# `T::Foo[...]` is handled by translate_t_subscript; only
|
|
218
|
+
# non-`T`-rooted constants are user generics.
|
|
219
|
+
!sorbet_t_qualified?(receiver)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def translate_user_subscript(node)
|
|
223
|
+
# Parity with translate_constant_path: the constant's
|
|
224
|
+
# rendered name (carrying a leading `::` for absolute
|
|
225
|
+
# paths) becomes the nominal class name.
|
|
226
|
+
name = constant_path_name(node.receiver)
|
|
227
|
+
return degraded if name.nil?
|
|
228
|
+
|
|
229
|
+
args = call_arguments(node).map { |arg| translate(arg) }
|
|
230
|
+
Rigor::Type::Combinator.nominal_of(name, type_args: args)
|
|
231
|
+
end
|
|
232
|
+
|
|
191
233
|
# `T::Class[T]` — Sorbet's "any class object whose
|
|
192
234
|
# instances are at least `T`". Rigor has no exact
|
|
193
235
|
# analogue (Singleton names a specific class); the
|
|
@@ -76,29 +76,29 @@ module Rigor
|
|
|
76
76
|
version: "0.1.0",
|
|
77
77
|
description: "Ingests Sorbet `sig` blocks as method-signature contributions.",
|
|
78
78
|
config_schema: {
|
|
79
|
+
# `paths` keeps a `fetch`-with-default in `init` because its
|
|
80
|
+
# default is dynamic (the project's configured `paths`), not a
|
|
81
|
+
# static literal the manifest can declare.
|
|
79
82
|
"paths" => :array,
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
# Default RBI directory tree. Matches the layout `tapioca init`
|
|
84
|
+
# generates — see Sorbet's `rbi.md`. Slice 4 walks every `.rbi`
|
|
85
|
+
# file under these roots recursively; the four standard Tapioca
|
|
86
|
+
# subdirectories (`gems` / `annotations` / `dsl` / `shims`) are
|
|
87
|
+
# picked up as a side effect of recursing into the parent root.
|
|
88
|
+
"rbi_paths" => { kind: :array, default: ["sorbet/rbi"] },
|
|
89
|
+
"enforce_sigil" => { kind: :boolean, default: true }
|
|
82
90
|
}
|
|
83
91
|
)
|
|
84
92
|
|
|
85
|
-
# Default RBI directory tree. Matches the layout
|
|
86
|
-
# `tapioca init` generates — see Sorbet's `rbi.md`. Slice 4
|
|
87
|
-
# walks every `.rbi` file under these roots recursively;
|
|
88
|
-
# the four standard Tapioca subdirectories
|
|
89
|
-
# (`gems` / `annotations` / `dsl` / `shims`) are picked
|
|
90
|
-
# up as a side effect of recursing into the parent root.
|
|
91
|
-
DEFAULT_RBI_PATHS = ["sorbet/rbi"].freeze
|
|
92
|
-
|
|
93
93
|
def init(services)
|
|
94
94
|
@services = services
|
|
95
95
|
@configured_paths = Array(config.fetch("paths", services.configuration.paths)).map(&:to_s)
|
|
96
|
-
@rbi_paths = Array(config.fetch("rbi_paths"
|
|
97
|
-
#
|
|
96
|
+
@rbi_paths = Array(config.fetch("rbi_paths")).map(&:to_s)
|
|
97
|
+
# Schema default `true` — only files marked `# typed: true` /
|
|
98
98
|
# `:strict` / `:strong` contribute their sigs. Set to
|
|
99
99
|
# `false` to record every file's sigs regardless of
|
|
100
100
|
# sigil (current behaviour pre-this-config).
|
|
101
|
-
@enforce_sigil = config.fetch("enforce_sigil"
|
|
101
|
+
@enforce_sigil = config.fetch("enforce_sigil")
|
|
102
102
|
# ADR-11 deferred follow-up — per-call-site assertion
|
|
103
103
|
# gating. Catalog harvest's `@sigil_by_path` cache is
|
|
104
104
|
# consulted at every `flow_contribution_for` call so
|
|
@@ -503,11 +503,9 @@ module Rigor
|
|
|
503
503
|
end
|
|
504
504
|
|
|
505
505
|
def absurd_diagnostic(path, call_node)
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
Rigor::Analysis::Diagnostic.from_node(
|
|
507
|
+
call_node,
|
|
508
508
|
path: path,
|
|
509
|
-
line: location.start_line,
|
|
510
|
-
column: location.start_column + 1,
|
|
511
509
|
message: "`T.absurd` is reachable: the discriminant did not narrow to `T.noreturn`. " \
|
|
512
510
|
"Either add the missing case branch above the `else`, or remove the " \
|
|
513
511
|
"`T.absurd(...)` call.",
|
|
@@ -558,11 +556,9 @@ module Rigor
|
|
|
558
556
|
end
|
|
559
557
|
|
|
560
558
|
def reveal_type_diagnostic(path, call_node, display)
|
|
561
|
-
|
|
562
|
-
|
|
559
|
+
Rigor::Analysis::Diagnostic.from_node(
|
|
560
|
+
call_node,
|
|
563
561
|
path: path,
|
|
564
|
-
line: location.start_line,
|
|
565
|
-
column: location.start_column + 1,
|
|
566
562
|
message: "`T.reveal_type` inferred type: #{display}",
|
|
567
563
|
severity: :info,
|
|
568
564
|
rule: "reveal-type"
|
|
@@ -616,11 +612,9 @@ module Rigor
|
|
|
616
612
|
end
|
|
617
613
|
|
|
618
614
|
def assert_type_mismatch_diagnostic(path, call_node, inferred_display, asserted_display)
|
|
619
|
-
|
|
620
|
-
|
|
615
|
+
Rigor::Analysis::Diagnostic.from_node(
|
|
616
|
+
call_node,
|
|
621
617
|
path: path,
|
|
622
|
-
line: location.start_line,
|
|
623
|
-
column: location.start_column + 1,
|
|
624
618
|
message: "`T.assert_type!` failed: inferred type #{inferred_display} is not " \
|
|
625
619
|
"compatible with asserted type #{asserted_display}.",
|
|
626
620
|
severity: :error,
|
|
@@ -629,11 +623,9 @@ module Rigor
|
|
|
629
623
|
end
|
|
630
624
|
|
|
631
625
|
def parse_error_diagnostic(path, error)
|
|
632
|
-
|
|
633
|
-
|
|
626
|
+
Rigor::Analysis::Diagnostic.from_node(
|
|
627
|
+
error.node,
|
|
634
628
|
path: path,
|
|
635
|
-
line: location.start_line,
|
|
636
|
-
column: location.start_column + 1,
|
|
637
629
|
message: parse_error_message(error.kind),
|
|
638
630
|
severity: :warning,
|
|
639
631
|
rule: "parse-error"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
require "rigor/plugin"
|
|
5
|
+
require "rigor/source/literals"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Plugin
|
|
@@ -50,41 +51,51 @@ module Rigor
|
|
|
50
51
|
version: "0.1.0",
|
|
51
52
|
description: "Validates state-machine transition references against declared states.",
|
|
52
53
|
config_schema: {
|
|
53
|
-
"dsl_method" => :string,
|
|
54
|
-
"state_method" => :string,
|
|
55
|
-
"transition_method" => :string
|
|
54
|
+
"dsl_method" => { kind: :string, default: "state_machine" },
|
|
55
|
+
"state_method" => { kind: :string, default: "state" },
|
|
56
|
+
"transition_method" => { kind: :string, default: "transition_to" }
|
|
56
57
|
}
|
|
57
58
|
)
|
|
58
59
|
|
|
59
|
-
DEFAULT_DSL_METHOD = "state_machine"
|
|
60
|
-
DEFAULT_STATE_METHOD = "state"
|
|
61
|
-
DEFAULT_TRANSITION_METHOD = "transition_to"
|
|
62
|
-
DID_YOU_MEAN_DISTANCE = 3
|
|
63
|
-
|
|
64
60
|
def init(_services)
|
|
65
|
-
@dsl_method = config.fetch("dsl_method"
|
|
66
|
-
@state_method = config.fetch("state_method"
|
|
67
|
-
@transition_method = config.fetch("transition_method"
|
|
61
|
+
@dsl_method = config.fetch("dsl_method").to_sym
|
|
62
|
+
@state_method = config.fetch("state_method").to_sym
|
|
63
|
+
@transition_method = config.fetch("transition_method").to_sym
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ADR-37 — the two-pass shape made explicit. The collect pass
|
|
67
|
+
# (pass 1) runs once per file as the node-rule file context: it
|
|
68
|
+
# MUST complete before validation because a `transition_to` may
|
|
69
|
+
# precede the `state` that declares its target, so it cannot be a
|
|
70
|
+
# per-node rule in the engine's single forward walk. The validate
|
|
71
|
+
# pass (pass 2) is then a per-`CallNode` rule over the
|
|
72
|
+
# engine-owned walk — no hand-rolled traversal.
|
|
73
|
+
node_file_context do |root, _scope|
|
|
74
|
+
collect_states(root)
|
|
68
75
|
end
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
states
|
|
72
|
-
|
|
77
|
+
node_rule Prism::CallNode do |node, _scope, path, states|
|
|
78
|
+
next [] if states.nil? || states.empty?
|
|
79
|
+
next [] unless transition_call?(node)
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
sym = literal_symbol_arg(node, 0)
|
|
82
|
+
next [] if sym.nil? # not a literal — defer to runtime
|
|
83
|
+
|
|
84
|
+
[build_diagnostic(path, node, sym, states)]
|
|
75
85
|
end
|
|
76
86
|
|
|
77
87
|
private
|
|
78
88
|
|
|
79
89
|
# Pass 1 — every `state :foo` declaration inside a
|
|
80
90
|
# `<dsl_method> do ... end` block on the file. Returns a
|
|
81
|
-
# frozen Set of state name Symbols.
|
|
91
|
+
# frozen Set of state name Symbols. Walks via the engine's
|
|
92
|
+
# shared `Source::NodeWalker` rather than a hand-rolled traversal.
|
|
82
93
|
def collect_states(root)
|
|
83
94
|
states = Set.new
|
|
84
|
-
|
|
95
|
+
Source::NodeWalker.each(root) do |node|
|
|
85
96
|
next unless dsl_call?(node)
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
Source::NodeWalker.each(node.block) do |inner|
|
|
88
99
|
next unless state_declaration?(inner)
|
|
89
100
|
|
|
90
101
|
sym = literal_symbol_arg(inner, 0)
|
|
@@ -94,33 +105,19 @@ module Rigor
|
|
|
94
105
|
states.freeze
|
|
95
106
|
end
|
|
96
107
|
|
|
97
|
-
# Pass 2 — every `<transition_method>(:sym)` call.
|
|
98
|
-
def validate_transitions(path, root, states)
|
|
99
|
-
diagnostics = []
|
|
100
|
-
walk(root) do |node|
|
|
101
|
-
next unless transition_call?(node)
|
|
102
|
-
|
|
103
|
-
sym = literal_symbol_arg(node, 0)
|
|
104
|
-
next if sym.nil? # not a literal — defer to runtime
|
|
105
|
-
|
|
106
|
-
diagnostics << build_diagnostic(path, node, sym, states)
|
|
107
|
-
end
|
|
108
|
-
diagnostics
|
|
109
|
-
end
|
|
110
|
-
|
|
111
108
|
def build_diagnostic(path, node, sym, states)
|
|
112
109
|
if states.include?(sym)
|
|
113
110
|
diagnostic(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
node, path: path,
|
|
112
|
+
severity: :info,
|
|
113
|
+
rule: "known-state",
|
|
114
|
+
message: "#{@transition_method}(:#{sym}) — declared state"
|
|
118
115
|
)
|
|
119
116
|
else
|
|
120
|
-
hint =
|
|
117
|
+
hint = Rigor::Plugin::Base.suggest(sym, states)
|
|
121
118
|
message = "unknown state :#{sym}"
|
|
122
119
|
message += " (did you mean :#{hint}?)" if hint
|
|
123
|
-
diagnostic(
|
|
120
|
+
diagnostic(node, path: path, severity: :error, rule: "unknown-state", message: message)
|
|
124
121
|
end
|
|
125
122
|
end
|
|
126
123
|
|
|
@@ -143,64 +140,7 @@ module Rigor
|
|
|
143
140
|
end
|
|
144
141
|
|
|
145
142
|
def literal_symbol_arg(call, index)
|
|
146
|
-
|
|
147
|
-
return nil unless node.is_a?(Prism::SymbolNode)
|
|
148
|
-
|
|
149
|
-
node.unescaped.to_sym
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def walk(node, &)
|
|
153
|
-
return if node.nil?
|
|
154
|
-
|
|
155
|
-
yield node
|
|
156
|
-
node.compact_child_nodes.each { |child| walk(child, &) }
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def did_you_mean(name, states)
|
|
160
|
-
target = name.to_s
|
|
161
|
-
best = nil
|
|
162
|
-
best_distance = DID_YOU_MEAN_DISTANCE + 1
|
|
163
|
-
states.each do |state|
|
|
164
|
-
distance = levenshtein(target, state.to_s)
|
|
165
|
-
if distance < best_distance
|
|
166
|
-
best = state
|
|
167
|
-
best_distance = distance
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
best
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def levenshtein(a, b) # rubocop:disable Naming/MethodParameterName
|
|
174
|
-
return b.length if a.empty?
|
|
175
|
-
return a.length if b.empty?
|
|
176
|
-
|
|
177
|
-
rows = Array.new(a.length + 1) { |_i| Array.new(b.length + 1, 0) }
|
|
178
|
-
(0..a.length).each { |i| rows[i][0] = i }
|
|
179
|
-
(0..b.length).each { |j| rows[0][j] = j }
|
|
180
|
-
|
|
181
|
-
(1..a.length).each do |i|
|
|
182
|
-
(1..b.length).each do |j|
|
|
183
|
-
cost = a[i - 1] == b[j - 1] ? 0 : 1
|
|
184
|
-
rows[i][j] = [
|
|
185
|
-
rows[i - 1][j] + 1,
|
|
186
|
-
rows[i][j - 1] + 1,
|
|
187
|
-
rows[i - 1][j - 1] + cost
|
|
188
|
-
].min
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
rows[a.length][b.length]
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def diagnostic(path, node, severity:, rule:, message:)
|
|
195
|
-
location = node.location
|
|
196
|
-
Rigor::Analysis::Diagnostic.new(
|
|
197
|
-
path: path,
|
|
198
|
-
line: location.start_line,
|
|
199
|
-
column: location.start_column + 1,
|
|
200
|
-
message: message,
|
|
201
|
-
severity: severity,
|
|
202
|
-
rule: rule
|
|
203
|
-
)
|
|
143
|
+
Rigor::Source::Literals.symbol(call.arguments.arguments[index])
|
|
204
144
|
end
|
|
205
145
|
end
|
|
206
146
|
|
data/sig/rigor/cache.rbs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Rigor
|
|
2
|
+
module Cache
|
|
3
|
+
# Structural interface for the RBS-derived cache producers: a class
|
|
4
|
+
# object that responds to `.fetch(loader:, store:)` and yields the
|
|
5
|
+
# cached (or freshly computed) value. A structural *interface*
|
|
6
|
+
# (duck-typed), not a "protocol" (reserved for ADR-28). Satisfied by
|
|
7
|
+
# `RbsCacheProducer` and every subclass; matches the _Type and
|
|
8
|
+
# _DispatchTier interfaces declared elsewhere in sig/.
|
|
9
|
+
interface _CacheProducer
|
|
10
|
+
def fetch: (loader: untyped, store: untyped) -> untyped
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Base owning the shared producer `fetch` wiring; subclasses add a
|
|
14
|
+
# PRODUCER_ID constant and a private `self.compute(loader)`.
|
|
15
|
+
class RbsCacheProducer
|
|
16
|
+
def self.fetch: (loader: untyped, store: untyped) -> untyped
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/sig/rigor/inference.rbs
CHANGED
|
@@ -57,6 +57,28 @@ module Rigor
|
|
|
57
57
|
module MethodDispatcher
|
|
58
58
|
def self?.dispatch: (receiver_type: Type::t?, method_name: Symbol, arg_types: Array[Type::t], ?block_type: Type::t?, ?environment: Environment?) -> Type::t?
|
|
59
59
|
def self?.expected_block_param_types: (receiver_type: Type::t?, method_name: Symbol, arg_types: Array[Type::t], ?environment: Environment?) -> Array[Type::t]
|
|
60
|
+
|
|
61
|
+
# Immutable value object threaded through every dispatch tier.
|
|
62
|
+
class CallContext
|
|
63
|
+
attr_reader receiver: Type::t?
|
|
64
|
+
attr_reader method_name: Symbol
|
|
65
|
+
attr_reader args: Array[Type::t]
|
|
66
|
+
attr_reader block_type: Type::t?
|
|
67
|
+
attr_reader environment: Environment?
|
|
68
|
+
attr_reader call_node: untyped
|
|
69
|
+
attr_reader scope: untyped
|
|
70
|
+
attr_reader self_type_override: Type::t?
|
|
71
|
+
attr_reader public_only: bool
|
|
72
|
+
|
|
73
|
+
def self.build: (receiver: Type::t?, method_name: Symbol, args: Array[Type::t], ?block_type: Type::t?, ?environment: Environment?, ?call_node: untyped, ?scope: untyped, ?self_type_override: Type::t?, ?public_only: bool) -> CallContext
|
|
74
|
+
def with: (**untyped) -> CallContext
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The interface every dispatch tier observes: fold one call site,
|
|
78
|
+
# returning a `Type::t` or nil to defer to the next tier.
|
|
79
|
+
interface _DispatchTier
|
|
80
|
+
def try_dispatch: (CallContext) -> Type::t?
|
|
81
|
+
end
|
|
60
82
|
end
|
|
61
83
|
|
|
62
84
|
class MethodParameterBinder
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# `< StandardError` so inherited `Exception` methods (`message`,
|
|
2
|
+
# `backtrace`, …) resolve on a rescued instance.
|
|
3
|
+
class Rigor::Plugin::AccessDeniedError < StandardError
|
|
2
4
|
def initialize: (untyped, reason: untyped, ?resource: untyped) -> void
|
|
3
5
|
end
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
|
+
# The plugin contract every Rigor plugin subclasses. The signatures
|
|
2
|
+
# below are the RBS face of `lib/rigor/plugin/base.rb`; they let the
|
|
3
|
+
# `lib` Steep target check Base's own implementation against the
|
|
4
|
+
# declared surface and give plugin subclasses (once they enter a check
|
|
5
|
+
# target) method resolution for the DSL + hook surface.
|
|
6
|
+
#
|
|
7
|
+
# Collaborators that have no RBS yet (Diagnostic, Cache::Descriptor,
|
|
8
|
+
# FlowContribution, NodeContext, Prism nodes) are typed `untyped`; the
|
|
9
|
+
# precision ceiling is the sparse `sig/` tree, not this file.
|
|
1
10
|
class Rigor::Plugin::Base
|
|
2
|
-
|
|
11
|
+
# --- class-level declaration DSLs ---------------------------------
|
|
12
|
+
|
|
13
|
+
# Dual-purpose: `manifest(id:, version:, ...)` declares + caches,
|
|
14
|
+
# the no-arg `manifest` reads the cached value back.
|
|
15
|
+
def self.manifest: (**untyped fields) -> Rigor::Plugin::Manifest
|
|
16
|
+
|
|
17
|
+
def self.producer: (untyped id, ?serialize: untyped, ?deserialize: untyped) { (untyped params) -> untyped } -> Symbol
|
|
18
|
+
def self.producers: () -> Hash[Symbol, untyped]
|
|
19
|
+
|
|
20
|
+
def self.node_rule: (untyped node_type) { (*untyped) -> untyped } -> untyped
|
|
21
|
+
def self.node_rules: () -> Array[untyped]
|
|
22
|
+
|
|
23
|
+
def self.node_file_context: () { (untyped root, untyped scope) -> untyped } -> untyped
|
|
24
|
+
def self.node_file_context_block: () -> untyped
|
|
25
|
+
|
|
26
|
+
def self.dynamic_return: (receivers: Array[String]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
27
|
+
def self.dynamic_returns: () -> Array[untyped]
|
|
28
|
+
|
|
29
|
+
def self.type_specifier: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
|
+
def self.type_specifiers: () -> Array[untyped]
|
|
31
|
+
|
|
32
|
+
def self.suggest: (untyped name, untyped candidates) -> String?
|
|
33
|
+
|
|
34
|
+
# --- instance surface --------------------------------------------
|
|
35
|
+
|
|
36
|
+
attr_reader services: untyped
|
|
37
|
+
attr_reader config: untyped
|
|
38
|
+
|
|
3
39
|
def initialize: (services: untyped, ?config: untyped) -> void
|
|
4
|
-
|
|
5
|
-
|
|
40
|
+
|
|
41
|
+
# Overridable lifecycle / extension hooks (default no-ops).
|
|
42
|
+
def init: (untyped services) -> nil
|
|
43
|
+
def prepare: (untyped services) -> nil
|
|
44
|
+
def flow_contribution_for: (call_node: untyped, scope: untyped) -> untyped
|
|
45
|
+
def diagnostics_for_file: (path: untyped, scope: untyped, root: untyped) -> Array[untyped]
|
|
46
|
+
|
|
47
|
+
# Engine-executed dispatch over the declared DSLs.
|
|
48
|
+
def node_rule_diagnostics: (path: untyped, scope: untyped, root: untyped) -> Array[untyped]
|
|
49
|
+
def dynamic_return_type: (call_node: untyped, scope: untyped, receiver_type: untyped) -> untyped
|
|
50
|
+
def type_specifier_facts: (call_node: untyped, scope: untyped) -> Array[untyped]
|
|
51
|
+
|
|
52
|
+
# Authoring helpers.
|
|
53
|
+
def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> untyped
|
|
54
|
+
def manifest: () -> Rigor::Plugin::Manifest
|
|
55
|
+
def signature_paths: () -> Array[String]
|
|
56
|
+
def protocol_contracts: () -> untyped
|
|
57
|
+
def io_boundary: () -> Rigor::Plugin::IoBoundary
|
|
58
|
+
def cache_for: (untyped producer_id, ?params: untyped, ?descriptor: untyped) -> untyped
|
|
59
|
+
def glob_descriptor: (untyped roots, *untyped patterns) -> untyped
|
|
60
|
+
def plugin_entry: () -> untyped
|
|
6
61
|
end
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
class Rigor::Plugin::IoBoundary
|
|
2
2
|
def initialize: (policy: untyped, plugin_id: untyped, ?http_client: untyped) -> void
|
|
3
3
|
def read_file: (untyped) -> String
|
|
4
|
+
def open_url: (untyped url) -> String
|
|
5
|
+
# Returns a frozen `Rigor::Cache::Descriptor` (no RBS yet -> untyped).
|
|
6
|
+
def cache_descriptor: () -> untyped
|
|
4
7
|
end
|