rigortype 0.1.16 → 0.1.18
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/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +18 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +67 -0
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +226 -0
- data/lib/rigor/analysis/check_rules.rb +180 -73
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +18 -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/diagnostic_aggregator.rb +580 -0
- data/lib/rigor/analysis/runner/pool_coordinator.rb +569 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +318 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +46 -0
- data/lib/rigor/analysis/runner.rb +477 -1110
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/analysis/worker_session.rb +47 -8
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +50 -49
- data/lib/rigor/cache/incremental_snapshot.rb +153 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +34 -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_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +145 -14
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/check_command.rb +705 -0
- data/lib/rigor/cli/ci_detector.rb +94 -0
- 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/diagnostic_formats.rb +345 -0
- 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 +2 -7
- data/lib/rigor/cli/prism_colorizer.rb +10 -3
- 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/trace_command.rb +143 -0
- data/lib/rigor/cli/trace_renderer.rb +310 -0
- 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 +15 -532
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +16 -3
- data/lib/rigor/environment/rbs_loader.rb +129 -71
- data/lib/rigor/environment.rb +1 -1
- data/lib/rigor/inference/acceptance.rb +10 -0
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- 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 -63
- data/lib/rigor/inference/flow_tracer.rb +180 -0
- data/lib/rigor/inference/macro_block_self_type.rb +10 -11
- 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 +33 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
- 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 +185 -84
- data/lib/rigor/inference/narrowing.rb +262 -5
- data/lib/rigor/inference/scope_indexer.rb +208 -21
- data/lib/rigor/inference/statement_evaluator.rb +110 -48
- 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 +61 -38
- data/lib/rigor/plugin/base.rb +302 -45
- data/lib/rigor/plugin/node_rule_walk.rb +147 -0
- data/lib/rigor/plugin/registry.rb +281 -15
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/conformance_checker.rb +293 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope/discovery_index.rb +58 -0
- data/lib/rigor/scope.rb +150 -167
- data/lib/rigor/sig_gen/observation_collector.rb +6 -6
- data/lib/rigor/source/literals.rb +14 -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 +22 -0
- 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 +5 -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 +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +2 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +70 -32
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -3
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +15 -21
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +35 -18
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +8 -29
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +17 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +83 -36
- data/sig/rigor/cache.rbs +19 -0
- data/sig/rigor/environment.rbs +0 -2
- data/sig/rigor/inference.rbs +27 -0
- data/sig/rigor/plugin/base.rbs +1 -2
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +42 -25
- data/sig/rigor/source.rbs +1 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +6 -1
- data/skills/rigor-ci-setup/SKILL.md +319 -0
- metadata +36 -2
- data/lib/rigor/cache/rbs_instance_definitions.rb +0 -79
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../plugin"
|
|
4
|
+
require_relative "../../reflection"
|
|
5
|
+
require_relative "../../type/combinator"
|
|
6
|
+
require_relative "../../inference/scope_indexer"
|
|
7
|
+
require_relative "../../inference/synthetic_method_scanner"
|
|
8
|
+
require_relative "../../inference/project_patched_scanner"
|
|
9
|
+
require_relative "../dependency_source_inference"
|
|
10
|
+
require_relative "../project_scan"
|
|
11
|
+
|
|
12
|
+
module Rigor
|
|
13
|
+
module Analysis
|
|
14
|
+
class Runner
|
|
15
|
+
# Owns every project-wide pre-pass that runs once per run before
|
|
16
|
+
# per-file analysis: plugin load + `#prepare`, the ADR-10
|
|
17
|
+
# dependency-source index, the ADR-16 synthetic-method scanner,
|
|
18
|
+
# the ADR-17 project-patched (`pre_eval:`) scanner, cross-file
|
|
19
|
+
# class discovery, and the ADR-24 def-node + visibility index.
|
|
20
|
+
# Also owns the {ProjectScan} snapshot adopt / build paths the LSP
|
|
21
|
+
# uses.
|
|
22
|
+
#
|
|
23
|
+
# The collaborator produces a frozen {Result} bundle; the {Runner}
|
|
24
|
+
# assigns each slot onto its own ivar surface, preserving the exact
|
|
25
|
+
# assignment order and timing the inline body had. The `pool_mode`
|
|
26
|
+
# predicate is injected (it keys on `@workers` / `@buffer`, owned by
|
|
27
|
+
# the Runner) so this collaborator never calls back into it.
|
|
28
|
+
class ProjectPrePasses
|
|
29
|
+
# Frozen bundle of the project-wide state the pre-passes produce.
|
|
30
|
+
# Mirrors the ivars `run_project_pre_passes` / `adopt_prebuilt`
|
|
31
|
+
# set on the {Runner}, in the same order they were assigned.
|
|
32
|
+
Result = Data.define(
|
|
33
|
+
:plugin_registry,
|
|
34
|
+
:dependency_source_index,
|
|
35
|
+
:cached_plugin_prepare_diagnostics,
|
|
36
|
+
:synthetic_method_index,
|
|
37
|
+
:project_patched_methods,
|
|
38
|
+
:pre_eval_diagnostics_from_scanner,
|
|
39
|
+
:discovered_classes,
|
|
40
|
+
:discovered_def_nodes,
|
|
41
|
+
:discovered_def_sources,
|
|
42
|
+
:discovered_superclasses,
|
|
43
|
+
:discovered_includes,
|
|
44
|
+
:discovered_class_sources,
|
|
45
|
+
:discovered_method_visibilities,
|
|
46
|
+
:discovered_methods,
|
|
47
|
+
:data_member_layouts
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# @param configuration [Rigor::Configuration]
|
|
51
|
+
# @param cache_store [Rigor::Cache::Store, nil]
|
|
52
|
+
# @param buffer [BufferBinding, nil]
|
|
53
|
+
# @param plugin_requirer [#call, nil]
|
|
54
|
+
# @param pool_mode [#call] reader returning the pool-mode flag.
|
|
55
|
+
def initialize(configuration:, cache_store:, buffer:, plugin_requirer:, pool_mode:)
|
|
56
|
+
@configuration = configuration
|
|
57
|
+
@cache_store = cache_store
|
|
58
|
+
@buffer = buffer
|
|
59
|
+
@plugin_requirer = plugin_requirer
|
|
60
|
+
@pool_mode_reader = pool_mode
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Internal: drives every project-wide pre-pass and returns the
|
|
64
|
+
# results bundled in {Result} in the order the downstream `#run`
|
|
65
|
+
# body expects. Extracted so `#prepare_project_scan` and the
|
|
66
|
+
# prebuilt-less `#run` path share one implementation.
|
|
67
|
+
def run(expansion:) # rubocop:disable Metrics/MethodLength
|
|
68
|
+
plugin_registry = load_plugins
|
|
69
|
+
dependency_source_index = DependencySourceInference::Builder.build(@configuration.dependencies)
|
|
70
|
+
# ADR-18 slice 3 — plugin prepare MUST run before the
|
|
71
|
+
# synthetic-method scanner so cross-plugin facts
|
|
72
|
+
# (`:dry_type_aliases` etc.) are already published when
|
|
73
|
+
# the scanner resolves Tier C `returns_from_arg:`
|
|
74
|
+
# lookups. The diagnostics produced by prepare are
|
|
75
|
+
# captured here so `pre_file_diagnostics` can re-emit
|
|
76
|
+
# them in the existing order without invoking prepare
|
|
77
|
+
# twice. Pool mode still re-runs prepare per worker
|
|
78
|
+
# (workers don't see this early invocation), preserving
|
|
79
|
+
# the existing Phase 4b contract.
|
|
80
|
+
cached_plugin_prepare_diagnostics =
|
|
81
|
+
pool_mode? ? [] : plugin_prepare_diagnostics(plugin_registry)
|
|
82
|
+
# ADR-16 slice 2b — Tier C pre-pass. Built once per run
|
|
83
|
+
# against the resolved file set + the loaded plugin
|
|
84
|
+
# registry's `heredoc_templates` so synthetic methods are
|
|
85
|
+
# visible cross-file when per-file inference dispatches.
|
|
86
|
+
synthetic_method_index = Inference::SyntheticMethodScanner.scan(
|
|
87
|
+
plugin_registry: plugin_registry,
|
|
88
|
+
paths: expansion.fetch(:files),
|
|
89
|
+
environment: nil,
|
|
90
|
+
fact_store: shared_fact_store(plugin_registry),
|
|
91
|
+
buffer: @buffer
|
|
92
|
+
)
|
|
93
|
+
# ADR-17 slice 2 — pre-eval pre-pass. Built once per run
|
|
94
|
+
# from the `pre_eval:` entries that exist on disk
|
|
95
|
+
# (slice-1's `pre-eval.file-not-found` `:error` already
|
|
96
|
+
# surfaced any missing entries; the scanner skips them
|
|
97
|
+
# here). The resulting {ProjectPatchedMethods} registry
|
|
98
|
+
# is consulted by the dispatcher tier between plugins
|
|
99
|
+
# and dependency-source inference so project-side
|
|
100
|
+
# patches resolve cross-file.
|
|
101
|
+
existing_pre_eval = @configuration.pre_eval.select { |path| File.file?(path) }
|
|
102
|
+
pre_eval_outcome = Inference::ProjectPatchedScanner.scan(existing_pre_eval, buffer: @buffer)
|
|
103
|
+
project_patched_methods = pre_eval_outcome.registry
|
|
104
|
+
pre_eval_diagnostics_from_scanner = pre_eval_outcome.diagnostics
|
|
105
|
+
# Cross-file class discovery — walks every project file
|
|
106
|
+
# for `class Foo` / `module Bar` declarations so a
|
|
107
|
+
# `Foo.method_call` receiver in one file resolves a
|
|
108
|
+
# `class Foo` declared in a sibling file. Without this
|
|
109
|
+
# pre-pass each file's `discovered_classes` was per-file
|
|
110
|
+
# only, and lexical lookup fell back to stdlib `::Foo`
|
|
111
|
+
# for any user class shadowing a stdlib name (e.g.
|
|
112
|
+
# `Google::Cloud::Storage::File`). Cost is one extra
|
|
113
|
+
# parse pass over the project; small projects pay
|
|
114
|
+
# tens of ms, larger projects ~1s. Future optimisation
|
|
115
|
+
# can share parses with the existing scanner passes.
|
|
116
|
+
discovered_classes =
|
|
117
|
+
Inference::ScopeIndexer.discovered_classes_for_paths(expansion.fetch(:files), buffer: @buffer)
|
|
118
|
+
# ADR-24 slice 2 — cross-file def-node + class->superclass
|
|
119
|
+
# index so an implicit-self call inside a subclass
|
|
120
|
+
# resolves a superclass `def` declared in a sibling
|
|
121
|
+
# file. One extra parse pass over the project; shares
|
|
122
|
+
# the cost profile of the class-discovery pass above.
|
|
123
|
+
def_index =
|
|
124
|
+
Inference::ScopeIndexer.discovered_def_index_for_paths(expansion.fetch(:files), buffer: @buffer)
|
|
125
|
+
Result.new(
|
|
126
|
+
plugin_registry: plugin_registry,
|
|
127
|
+
dependency_source_index: dependency_source_index,
|
|
128
|
+
cached_plugin_prepare_diagnostics: cached_plugin_prepare_diagnostics,
|
|
129
|
+
synthetic_method_index: synthetic_method_index,
|
|
130
|
+
project_patched_methods: project_patched_methods,
|
|
131
|
+
pre_eval_diagnostics_from_scanner: pre_eval_diagnostics_from_scanner,
|
|
132
|
+
discovered_classes: discovered_classes,
|
|
133
|
+
discovered_def_nodes: def_index.fetch(:def_nodes),
|
|
134
|
+
discovered_def_sources: def_index.fetch(:def_sources),
|
|
135
|
+
discovered_superclasses: def_index.fetch(:superclasses),
|
|
136
|
+
discovered_includes: def_index.fetch(:includes),
|
|
137
|
+
discovered_class_sources: def_index.fetch(:class_sources),
|
|
138
|
+
discovered_method_visibilities: def_index.fetch(:method_visibilities),
|
|
139
|
+
discovered_methods: def_index.fetch(:methods),
|
|
140
|
+
data_member_layouts: def_index.fetch(:data_member_layouts)
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Builds the LSP-facing {ProjectScan} snapshot from a fresh
|
|
145
|
+
# pre-pass run. The runner adopts `result` onto its ivars first
|
|
146
|
+
# so the same registry object that ran `#prepare` (and so the
|
|
147
|
+
# populated `services.fact_store`) is the one the snapshot
|
|
148
|
+
# carries.
|
|
149
|
+
def build_project_scan(result)
|
|
150
|
+
ProjectScan.new(
|
|
151
|
+
plugin_registry: result.plugin_registry,
|
|
152
|
+
dependency_source_index: result.dependency_source_index,
|
|
153
|
+
synthetic_method_index: result.synthetic_method_index,
|
|
154
|
+
project_patched_methods: result.project_patched_methods,
|
|
155
|
+
plugin_prepare_diagnostics: result.cached_plugin_prepare_diagnostics.dup.freeze,
|
|
156
|
+
pre_eval_diagnostics: result.pre_eval_diagnostics_from_scanner.dup.freeze
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Translates a prebuilt {ProjectScan} snapshot supplied to
|
|
161
|
+
# `Runner.new(prebuilt: ...)` into a {Result} the runner adopts
|
|
162
|
+
# the same way it adopts a fresh pre-pass run. The discovery
|
|
163
|
+
# tables are not part of the snapshot (the LSP path seeds an
|
|
164
|
+
# empty project scope), so they stay at their frozen-empty
|
|
165
|
+
# constructor defaults.
|
|
166
|
+
def adopt_prebuilt(scan)
|
|
167
|
+
Result.new(
|
|
168
|
+
plugin_registry: scan.plugin_registry,
|
|
169
|
+
dependency_source_index: scan.dependency_source_index,
|
|
170
|
+
cached_plugin_prepare_diagnostics: scan.plugin_prepare_diagnostics,
|
|
171
|
+
synthetic_method_index: scan.synthetic_method_index,
|
|
172
|
+
project_patched_methods: scan.project_patched_methods,
|
|
173
|
+
pre_eval_diagnostics_from_scanner: scan.pre_eval_diagnostics,
|
|
174
|
+
discovered_classes: nil,
|
|
175
|
+
discovered_def_nodes: nil,
|
|
176
|
+
discovered_def_sources: nil,
|
|
177
|
+
discovered_superclasses: nil,
|
|
178
|
+
discovered_includes: nil,
|
|
179
|
+
discovered_class_sources: nil,
|
|
180
|
+
discovered_method_visibilities: nil,
|
|
181
|
+
discovered_methods: nil,
|
|
182
|
+
data_member_layouts: nil
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Returns the per-run shared `Plugin::FactStore` instance.
|
|
187
|
+
# All loaded plugins share this store through their
|
|
188
|
+
# respective `Plugin::Services` (the same instance is
|
|
189
|
+
# threaded by `Plugin::Loader.load`). Returns `nil` when
|
|
190
|
+
# no plugins are loaded.
|
|
191
|
+
def shared_fact_store(plugin_registry)
|
|
192
|
+
return nil if plugin_registry.nil? || plugin_registry.empty?
|
|
193
|
+
|
|
194
|
+
plugin_registry.plugins.first&.services&.fact_store
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def pool_mode?
|
|
200
|
+
@pool_mode_reader.call
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Loads project-configured plugins through {Rigor::Plugin::Loader}
|
|
204
|
+
# and returns the resulting {Rigor::Plugin::Registry}. Loader
|
|
205
|
+
# failures are isolated: each surfaces as a `:plugin_loader`
|
|
206
|
+
# diagnostic on the run's `Result` rather than aborting the
|
|
207
|
+
# analysis. Plugins that load successfully but contribute no
|
|
208
|
+
# protocol hooks are inert in slice 1; later v0.1.0 slices
|
|
209
|
+
# wire the contribution merger through this registry.
|
|
210
|
+
def load_plugins
|
|
211
|
+
return Plugin::Registry::EMPTY if @configuration.plugins.empty?
|
|
212
|
+
|
|
213
|
+
services = Plugin::Services.new(
|
|
214
|
+
reflection: Reflection,
|
|
215
|
+
type: Type::Combinator,
|
|
216
|
+
configuration: @configuration,
|
|
217
|
+
cache_store: @cache_store,
|
|
218
|
+
trust_policy: build_trust_policy
|
|
219
|
+
)
|
|
220
|
+
if @plugin_requirer
|
|
221
|
+
Plugin::Loader.load(configuration: @configuration, services: services, requirer: @plugin_requirer)
|
|
222
|
+
else
|
|
223
|
+
Plugin::Loader.load(configuration: @configuration, services: services)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Builds the {Rigor::Plugin::TrustPolicy} for this run. Trusted
|
|
228
|
+
# gems are the gem-name half of every entry in
|
|
229
|
+
# `Configuration#plugins`. Allowed read roots default to the
|
|
230
|
+
# project root (CWD), the project's signature_paths, and each
|
|
231
|
+
# trusted gem's `Gem::Specification#full_gem_path`, plus any
|
|
232
|
+
# extras the user listed under `plugins_io.allowed_paths`.
|
|
233
|
+
# Slice 2 keeps `network_policy` `:disabled` — the only value
|
|
234
|
+
# the configuration accepts today.
|
|
235
|
+
def build_trust_policy
|
|
236
|
+
trusted_gems = @configuration.plugins.map { |entry| trusted_gem_name(entry) }.uniq
|
|
237
|
+
roots = [Dir.pwd]
|
|
238
|
+
Array(@configuration.signature_paths).each { |sp| roots << File.expand_path(sp) }
|
|
239
|
+
trusted_gems.each do |gem_name|
|
|
240
|
+
path = trusted_gem_root(gem_name)
|
|
241
|
+
roots << path if path
|
|
242
|
+
end
|
|
243
|
+
@configuration.plugins_io_allowed_paths.each { |p| roots << File.expand_path(p) }
|
|
244
|
+
|
|
245
|
+
Plugin::TrustPolicy.new(
|
|
246
|
+
trusted_gems: trusted_gems,
|
|
247
|
+
allowed_read_roots: roots,
|
|
248
|
+
network_policy: @configuration.plugins_io_network,
|
|
249
|
+
allowed_url_hosts: @configuration.plugins_io_allowed_url_hosts
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def trusted_gem_name(entry)
|
|
254
|
+
case entry
|
|
255
|
+
when String then entry
|
|
256
|
+
when Hash then entry["gem"] || entry["id"]
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def trusted_gem_root(gem_name)
|
|
261
|
+
return nil if gem_name.nil? || gem_name.empty?
|
|
262
|
+
|
|
263
|
+
spec = Gem.loaded_specs[gem_name]
|
|
264
|
+
spec&.full_gem_path # rigor:disable undefined-method
|
|
265
|
+
rescue StandardError
|
|
266
|
+
nil
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# ADR-9 slice 3 — invokes every loaded plugin's `#prepare`
|
|
270
|
+
# hook once per run, after the loader's `#init` pass and
|
|
271
|
+
# before per-file iteration. Plugins publish facts here
|
|
272
|
+
# for cross-plugin consumption via the shared
|
|
273
|
+
# `services.fact_store`. Failures isolate as
|
|
274
|
+
# `:plugin_loader runtime-error` diagnostics, mirroring the
|
|
275
|
+
# `#diagnostics_for_file` raise envelope in
|
|
276
|
+
# `plugin_runtime_error_diagnostic`.
|
|
277
|
+
#
|
|
278
|
+
# Slice 3 visits plugins in registration order. Slice 5
|
|
279
|
+
# introduces topological ordering by `manifest(consumes:)`
|
|
280
|
+
# so producers always run before consumers; until then,
|
|
281
|
+
# `Configuration#plugins` order MUST be producer-first if
|
|
282
|
+
# cross-plugin dependencies exist.
|
|
283
|
+
def plugin_prepare_diagnostics(plugin_registry)
|
|
284
|
+
return [] if plugin_registry.empty?
|
|
285
|
+
|
|
286
|
+
plugin_registry.plugins.flat_map { |plugin| invoke_plugin_prepare(plugin) }
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def invoke_plugin_prepare(plugin)
|
|
290
|
+
plugin.prepare(plugin.services)
|
|
291
|
+
[]
|
|
292
|
+
rescue StandardError => e
|
|
293
|
+
[plugin_prepare_error_diagnostic(plugin, e)]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def plugin_prepare_error_diagnostic(plugin, error)
|
|
297
|
+
plugin_id = safe_plugin_id(plugin)
|
|
298
|
+
Diagnostic.new(
|
|
299
|
+
path: ".rigor.yml",
|
|
300
|
+
line: 1,
|
|
301
|
+
column: 1,
|
|
302
|
+
message: "plugin #{plugin_id.inspect} raised during prepare: " \
|
|
303
|
+
"#{error.class}: #{error.message}",
|
|
304
|
+
severity: :error,
|
|
305
|
+
rule: "runtime-error",
|
|
306
|
+
source_family: :plugin_loader
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def safe_plugin_id(plugin)
|
|
311
|
+
plugin.manifest.id
|
|
312
|
+
rescue StandardError
|
|
313
|
+
plugin.class.to_s
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Analysis
|
|
5
|
+
class Runner
|
|
6
|
+
# Mutable per-run holder for the four end-of-pass snapshots that
|
|
7
|
+
# the analysis paths compute as a side effect and the rest of the
|
|
8
|
+
# run reads back: the RBS `class_decl_paths` / `signature_paths`
|
|
9
|
+
# tables (consumed by {RunStats}), the synthesized-namespace name
|
|
10
|
+
# list, and the `conforms-to` scan results (consumed by the
|
|
11
|
+
# diagnostic aggregator).
|
|
12
|
+
#
|
|
13
|
+
# The snapshots are written by whichever analysis path ran
|
|
14
|
+
# ({PoolCoordinator} sequential / fork-pool / fallback) and read by
|
|
15
|
+
# the {Runner} and {DiagnosticAggregator}. A shared mutable holder
|
|
16
|
+
# keeps the write/read timing bit-for-bit with the original inline
|
|
17
|
+
# ivars (the same pattern the reporter accumulators use) while
|
|
18
|
+
# letting the writer and readers live in separate objects without a
|
|
19
|
+
# back-reference cycle.
|
|
20
|
+
class RunSnapshots
|
|
21
|
+
attr_accessor :class_decl_paths, :signature_paths,
|
|
22
|
+
:synthesized_namespaces, :conformance_results
|
|
23
|
+
|
|
24
|
+
# Constructor defaults match the {Runner} constructor: the
|
|
25
|
+
# pre-seed values `build_run_stats` / `pre_file_diagnostics` read
|
|
26
|
+
# before the first analysis path runs are frozen empties.
|
|
27
|
+
def initialize
|
|
28
|
+
@class_decl_paths = {}.freeze
|
|
29
|
+
@signature_paths = [].freeze
|
|
30
|
+
@synthesized_namespaces = [].freeze
|
|
31
|
+
@conformance_results = [].freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Per-`#run` reset. Mirrors the original `#run` body, which reset
|
|
35
|
+
# these to NON-frozen empties (distinct from the frozen
|
|
36
|
+
# constructor defaults) at the top of each run.
|
|
37
|
+
def reset_for_run
|
|
38
|
+
@class_decl_paths = {}.freeze
|
|
39
|
+
@signature_paths = []
|
|
40
|
+
@synthesized_namespaces = []
|
|
41
|
+
@conformance_results = []
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|