rigortype 0.4.7 → 0.4.8
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 +3 -1
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/docs/handbook/01-getting-started.md +1 -1
- data/docs/handbook/11-sig-gen.md +35 -0
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +307 -91
- data/docs/manual/03-configuration.md +41 -3
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +8 -0
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +15 -6
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +598 -99
- data/lib/rigor/analysis/crash_signature.rb +43 -1
- data/lib/rigor/analysis/dependency_recorder.rb +233 -76
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +279 -37
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +37 -1
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner.rb +222 -39
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
- data/lib/rigor/analysis/worker_session.rb +42 -41
- data/lib/rigor/bleeding_edge.rb +19 -4
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +66 -7
- data/lib/rigor/cache/rbs_descriptor.rb +37 -2
- data/lib/rigor/cache/rbs_environment.rb +5 -1
- data/lib/rigor/cli/baseline_command.rb +15 -2
- data/lib/rigor/cli/check_command.rb +4 -2
- data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
- data/lib/rigor/cli/lens_command.rb +17 -2
- data/lib/rigor/cli/sig_gen_command.rb +8 -0
- data/lib/rigor/cli/skill_describe.rb +1 -0
- data/lib/rigor/cli.rb +5 -2
- data/lib/rigor/config_audit.rb +32 -18
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/configuration.rb +32 -5
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/default_libraries.rb +17 -0
- data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
- data/lib/rigor/environment/rbs_loader.rb +312 -63
- data/lib/rigor/environment.rb +131 -35
- data/lib/rigor/inference/acceptance.rb +50 -15
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_run_count.rb +9 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +56 -15
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/def_node_resolver.rb +32 -14
- data/lib/rigor/inference/def_return_typer.rb +1 -1
- data/lib/rigor/inference/definer_resolution.rb +15 -7
- data/lib/rigor/inference/definers_index.rb +72 -16
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1459 -287
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +8 -6
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +57 -8
- data/lib/rigor/inference/ivar_writer_census.rb +106 -11
- data/lib/rigor/inference/key_presence_guard.rb +51 -25
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +63 -15
- data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
- data/lib/rigor/inference/match_rebinding.rb +21 -8
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher.rb +66 -18
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +13 -2
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_widening.rb +44 -4
- data/lib/rigor/inference/narrowing.rb +201 -27
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +133 -8
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +28 -10
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +76 -22
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2079 -183
- data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +970 -250
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
- data/lib/rigor/inference/toplevel_def_census.rb +14 -6
- data/lib/rigor/inference/unknown_store_widening.rb +1 -1
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -10
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/io_boundary.rb +25 -3
- data/lib/rigor/plugin/manifest.rb +7 -3
- data/lib/rigor/plugin/registry.rb +13 -2
- data/lib/rigor/plugin/trust_policy.rb +46 -0
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/reflection/constant_ancestors.rb +79 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +18 -1
- data/lib/rigor/run_scoped_slots.rb +20 -20
- data/lib/rigor/scope/discovery_index.rb +149 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +900 -55
- data/lib/rigor/scope.rb +307 -34
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +14 -1
- data/lib/rigor/sig_gen/generator.rb +222 -13
- data/lib/rigor/sig_gen/method_candidate.rb +14 -0
- data/lib/rigor/sig_gen/observation_collector.rb +11 -2
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
- data/lib/rigor/sig_gen/writer.rb +21 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/combinator.rb +164 -8
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -3
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/plugin/base.rbs +53 -19
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -3
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +22 -3
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +48 -2
- data/lib/rigor/inference/memo_audit.rb +0 -55
|
@@ -169,6 +169,14 @@ module Rigor
|
|
|
169
169
|
# worker captures the file's cross-file reads for {#drain_dependencies}; the recorder's own disabled
|
|
170
170
|
# fast path makes the unwrapped case free.
|
|
171
171
|
def analyze(path)
|
|
172
|
+
diagnostics = analyze_attributed(path)
|
|
173
|
+
return diagnostics unless Inference::MemoAudit.enabled?
|
|
174
|
+
|
|
175
|
+
# ADR-129 WD5 (#2785) — the audit's mismatches this file raised, as `Runner#analyze_file` reports them.
|
|
176
|
+
CrashSignature.with_swallowed(path, diagnostics, Inference::MemoAudit.drain)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def analyze_attributed(path)
|
|
172
180
|
return analyze_with_effects(path) unless @record_dependencies
|
|
173
181
|
|
|
174
182
|
diagnostics = nil
|
|
@@ -204,6 +212,7 @@ module Rigor
|
|
|
204
212
|
diagnostics
|
|
205
213
|
end
|
|
206
214
|
private :analyze_with_effects
|
|
215
|
+
private :analyze_attributed
|
|
207
216
|
|
|
208
217
|
# ADR-103 WD6 / #386 — the worker-side mirror of `Runner#effect_envelope_index`. Derived from the
|
|
209
218
|
# same configuration and the session's own environment, so a worker's `≤` lane is the one the
|
|
@@ -227,6 +236,20 @@ module Rigor
|
|
|
227
236
|
end
|
|
228
237
|
private :effect_envelope_index
|
|
229
238
|
|
|
239
|
+
# The scope a file's analysis starts from: the project seed, with its template units.
|
|
240
|
+
def seeded_scope(path)
|
|
241
|
+
@template_units.seed(seed_project_scope(Scope.empty(environment: @environment, source_path: path)), path)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# The main pass over `root`, which the ADR-129 stage-3 shadow ({TypeOfMemo}) counts apart from the rule walk
|
|
245
|
+
# that follows it.
|
|
246
|
+
def index_main_pass(root, scope)
|
|
247
|
+
TypeOfMemo.begin_file
|
|
248
|
+
index = Inference::ScopeIndexer.index(root, default_scope: scope, widen_clauses: true)
|
|
249
|
+
TypeOfMemo.phase!(:rule)
|
|
250
|
+
index
|
|
251
|
+
end
|
|
252
|
+
|
|
230
253
|
def analyze_body(path)
|
|
231
254
|
parse_result = parse_source(path)
|
|
232
255
|
unless parse_result.errors.empty?
|
|
@@ -237,12 +260,10 @@ module Rigor
|
|
|
237
260
|
|
|
238
261
|
root = parse_result.value
|
|
239
262
|
Effects::Collector.record_root(root)
|
|
240
|
-
scope =
|
|
241
|
-
|
|
242
|
-
)
|
|
243
|
-
index = Inference::ScopeIndexer.index(root, default_scope: scope, widen_clauses: true)
|
|
263
|
+
scope = seeded_scope(path)
|
|
264
|
+
index = index_main_pass(root, scope)
|
|
244
265
|
# ADR-53 B4 — built-in collectors + plugin node rules share one walk.
|
|
245
|
-
node_collectors =
|
|
266
|
+
node_collectors = built_in_collectors(path, index, root)
|
|
246
267
|
node_results = node_rule_results_by_plugin(path, root, scope, node_collectors, index)
|
|
247
268
|
diagnostics = CheckRules.diagnose(
|
|
248
269
|
path: path,
|
|
@@ -258,11 +279,21 @@ module Rigor
|
|
|
258
279
|
diagnostics + explain_diagnostics(path, parse_result.value, scope)
|
|
259
280
|
rescue Errno::ENOENT => e
|
|
260
281
|
[analyzer_error(path, e.message)]
|
|
261
|
-
rescue StandardError => e
|
|
262
|
-
[analyzer_error(path, CrashSignature.
|
|
282
|
+
rescue StandardError, SystemStackError => e
|
|
283
|
+
[analyzer_error(path, CrashSignature.contained_message!(e, parse_result))]
|
|
284
|
+
ensure
|
|
285
|
+
TypeOfMemo.end_file
|
|
263
286
|
end
|
|
264
287
|
private :analyze_body
|
|
265
288
|
|
|
289
|
+
# The built-in collectors for one file; `plugin.api-surface`'s joins only when the rule can report.
|
|
290
|
+
def built_in_collectors(path, index, root)
|
|
291
|
+
CheckRules.build_node_collectors(
|
|
292
|
+
path, index, root, plugin_api_surface: CheckRules::PluginApiSurface.active?(@configuration)
|
|
293
|
+
)
|
|
294
|
+
end
|
|
295
|
+
private :built_in_collectors
|
|
296
|
+
|
|
266
297
|
# Read-once snapshot of the per-worker reporters so the caller (or the eventual Phase 4b pool
|
|
267
298
|
# aggregator) can merge into a single coordinator-side reporter. Both reporters dedupe at write time,
|
|
268
299
|
# so a post-hoc concat + de-dup at the entry-key level is sound.
|
|
@@ -419,40 +450,10 @@ module Rigor
|
|
|
419
450
|
Prism.parse(File.read(physical), filepath: path, version: @configuration.target_ruby)
|
|
420
451
|
end
|
|
421
452
|
|
|
422
|
-
#
|
|
423
|
-
#
|
|
453
|
+
# {Rigor::Plugin::TrustPolicy.for_configuration} is the single derivation `rigor check`, pooled
|
|
454
|
+
# workers and `rigor unused`'s plugin roots share (#2586); configuration is already Ractor-shareable.
|
|
424
455
|
def build_trust_policy
|
|
425
|
-
|
|
426
|
-
roots = [Dir.pwd]
|
|
427
|
-
Array(@configuration.signature_paths).each { |sp| roots << File.absolute_path(sp) }
|
|
428
|
-
trusted_gems.each do |gem_name|
|
|
429
|
-
path = trusted_gem_root(gem_name)
|
|
430
|
-
roots << path if path
|
|
431
|
-
end
|
|
432
|
-
@configuration.plugins_io_allowed_paths.each { |p| roots << File.absolute_path(p) }
|
|
433
|
-
|
|
434
|
-
Plugin::TrustPolicy.new(
|
|
435
|
-
trusted_gems: trusted_gems,
|
|
436
|
-
allowed_read_roots: roots,
|
|
437
|
-
network_policy: @configuration.plugins_io_network,
|
|
438
|
-
allowed_url_hosts: @configuration.plugins_io_allowed_url_hosts
|
|
439
|
-
)
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
def trusted_gem_name(entry)
|
|
443
|
-
case entry
|
|
444
|
-
when String then entry
|
|
445
|
-
when Hash then entry["gem"] || entry["id"]
|
|
446
|
-
end
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
def trusted_gem_root(gem_name)
|
|
450
|
-
return nil if gem_name.nil? || gem_name.empty?
|
|
451
|
-
|
|
452
|
-
spec = Gem.loaded_specs[gem_name]
|
|
453
|
-
spec&.full_gem_path
|
|
454
|
-
rescue StandardError
|
|
455
|
-
nil
|
|
456
|
+
Plugin::TrustPolicy.for_configuration(@configuration)
|
|
456
457
|
end
|
|
457
458
|
|
|
458
459
|
def run_plugin_prepare
|
|
@@ -473,7 +474,7 @@ module Rigor
|
|
|
473
474
|
def plugin_prepare_error_diagnostic(plugin, error)
|
|
474
475
|
plugin_id = safe_plugin_id(plugin)
|
|
475
476
|
Diagnostic.new(
|
|
476
|
-
path:
|
|
477
|
+
path: @configuration.config_display_path,
|
|
477
478
|
line: 1,
|
|
478
479
|
column: 1,
|
|
479
480
|
message: "plugin #{plugin_id.inspect} raised during prepare: " \
|
data/lib/rigor/bleeding_edge.rb
CHANGED
|
@@ -126,6 +126,20 @@ module Rigor
|
|
|
126
126
|
"static.value-use.void" => :warning
|
|
127
127
|
}.freeze
|
|
128
128
|
),
|
|
129
|
+
# ADR-128 WD6 / ADR-130 slice 2 (#2686). Off by default because a new required diagnostic is an ADR-50
|
|
130
|
+
# Decision 3 new discipline; it graduates at the next hard break.
|
|
131
|
+
Feature.new(
|
|
132
|
+
id: "plugin-api-surface",
|
|
133
|
+
kind: :severity,
|
|
134
|
+
summary: "In plugin source (code inside a class that inherits `Rigor::Plugin::Base`), a call on a value " \
|
|
135
|
+
"typed by a `Rigor::Plugin` interface (`_Scope`, `_Environment`, `_Services`, ...) to a member " \
|
|
136
|
+
"the interface does not declare becomes a `:warning` (`plugin.api-surface`). The engine object " \
|
|
137
|
+
"answers such a call today, so it is not `call.undefined-method`; it is outside the plugin API " \
|
|
138
|
+
"Rigor promises to keep (ADR-128).",
|
|
139
|
+
severity_overrides: {
|
|
140
|
+
"plugin.api-surface" => :warning
|
|
141
|
+
}.freeze
|
|
142
|
+
),
|
|
129
143
|
Feature.new(
|
|
130
144
|
id: "discovery-seeded-mutation-sites",
|
|
131
145
|
kind: :behaviour,
|
|
@@ -156,7 +170,7 @@ module Rigor
|
|
|
156
170
|
"kills — every surviving breakage there is one the analyzer reports nowhere at all, not one " \
|
|
157
171
|
"it reports in a caller."
|
|
158
172
|
),
|
|
159
|
-
# ADR-103 WD15 — the owner-ruled preview of the v0.
|
|
173
|
+
# ADR-103 WD15 — the owner-ruled preview of the v0.5.0 default. `:behaviour` because it moves no rule's
|
|
160
174
|
# severity; safe under the header comment's cache-identity rule for a reason specific to this feature
|
|
161
175
|
# rather than the generic WD13 argument, so it is spelled out here: `effects:` collection is
|
|
162
176
|
# OBSERVATIONAL (ADR-103 WD13) and the `effects:` block itself is deliberately absent from the
|
|
@@ -174,9 +188,10 @@ module Rigor
|
|
|
174
188
|
"it had written `effects: {}` — effect collection, the `rigor effects` verbs' cache sharing, " \
|
|
175
189
|
"and `effects.check` all turn on with every sub-key at its default. Writing `effects: false` " \
|
|
176
190
|
"explicitly still opts out, and a project that writes its own `effects:` block keeps that " \
|
|
177
|
-
"block's shape untouched either way. Graduates to default-on at v0.
|
|
178
|
-
"2026-08-17): ADR-50 § WD7 reads \"at a major\" as
|
|
179
|
-
"evaluation line. See ADR-103 § WD15
|
|
191
|
+
"block's shape untouched either way. Graduates to default-on at v0.5.0 (owner ruling, " \
|
|
192
|
+
"2026-10-26, moving the v0.4.0 date of 2026-08-17): ADR-50 § WD7 reads \"at a major\" as the " \
|
|
193
|
+
"version that ruling names, for this feature on the 0.x evaluation line. See ADR-103 § WD15 " \
|
|
194
|
+
"for the preconditions that gate the flip and § WD18 for the move."
|
|
180
195
|
)
|
|
181
196
|
].freeze
|
|
182
197
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
|
+
require_relative "../typing_state"
|
|
6
|
+
|
|
5
7
|
module Rigor
|
|
6
8
|
module Cache
|
|
7
9
|
# Per-run content-digest memo AND the ADR-87 stat-then-digest freshness choke point. Within a single
|
|
@@ -20,20 +22,38 @@ module Rigor
|
|
|
20
22
|
# zero SHA-256 bytes. The SHA-256 digest remains the sole change AUTHORITY — the stat tier only decides
|
|
21
23
|
# whether the digest needs recomputing.
|
|
22
24
|
#
|
|
23
|
-
# Soundness: the memo + recording-instant + strict flag are process-local (thread
|
|
24
|
-
# and never crossing a fork boundary), never persisted to disk, and scoped to exactly one
|
|
25
|
-
# {.with_run} (which installs a fresh table and restores the previous one on exit — a second run, or a run
|
|
25
|
+
# Soundness: the memo + recording-instant + strict flag are process-local (a per-thread {TypingState}
|
|
26
|
+
# Frame, so Ractor-safe and never crossing a fork boundary), never persisted to disk, and scoped to exactly one
|
|
27
|
+
# run via {.with_run} (which installs a fresh table and restores the previous one on exit — a second run, or a run
|
|
26
28
|
# that re-reads a file the user edited between runs, gets a clean table). Within a single run the analyzer
|
|
27
29
|
# already assumes the filesystem is stable — {Descriptor#fresh?} validates the whole dependency set against
|
|
28
30
|
# the live tree in one pass and never re-checks mid-run — so caching a coherent per-run snapshot changes
|
|
29
31
|
# nothing about what a run observes. Outside a run scope (no active table) every call digests directly,
|
|
30
32
|
# identical to a bare `Digest::SHA256.file`.
|
|
31
33
|
module FileDigest
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
# One run's state: the digest table, the stat table, the recording instant and the strict flag, installed
|
|
35
|
+
# together by {.with_run}.
|
|
36
|
+
Run = Data.define(:digests, :stats, :instant, :strict)
|
|
37
|
+
private_constant :Run
|
|
38
|
+
|
|
39
|
+
# ADR-129 WD1 — a Frame: {.with_run} installs the run and restores the previous one on the way out, and whether
|
|
40
|
+
# one is installed changes answers (outside a run every call digests or stats directly, the recording instant
|
|
41
|
+
# is the moment of the call, and validation is not strict). The installed {Run} is frozen; its two tables are
|
|
42
|
+
# extensional caches its owner fills in place without a touch (Memos by kind, whose entry is fixed once
|
|
43
|
+
# written): `digests` is keyed on the path and reads the file's bytes, `stats` is keyed on the path and reads
|
|
44
|
+
# its `File::Stat`, both under the run's premise that the filesystem is stable. The instant and the flag are
|
|
45
|
+
# read at install time.
|
|
46
|
+
RUN = TypingState.frame(
|
|
47
|
+
:file_digest_run,
|
|
48
|
+
memos: %i[digests stats],
|
|
49
|
+
reason: "installed per run by with_run; the digest and stat tables are extensional caches keyed on the path, " \
|
|
50
|
+
"filled in place, both reading the filesystem under the run's stable-filesystem premise, and the " \
|
|
51
|
+
"recording instant and strict flag are fixed at install",
|
|
52
|
+
# G1′: the run wraps the typing of every file, but the digest and stat lookups are the cache layer's, made while
|
|
53
|
+
# the RBS environment is prepared and its producers fetched, before anything types (#2883).
|
|
54
|
+
typing_never_reads: true
|
|
55
|
+
)
|
|
56
|
+
private_constant :RUN
|
|
37
57
|
|
|
38
58
|
# Set in the environment to force the strict digest-always validation path for a single run, regardless
|
|
39
59
|
# of the `cache.validation` config setting (the env wins). The escape hatch for a filesystem whose stat
|
|
@@ -46,21 +66,9 @@ module Rigor
|
|
|
46
66
|
# captured at run start — a file whose recorded mtime is not strictly older than it is treated as racy
|
|
47
67
|
# (always re-hashed), catching a file edited during the analysis window. `strict:` forces the
|
|
48
68
|
# digest-always path for `cache.validation: digest`.
|
|
49
|
-
def self.with_run(strict: false)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
previous_instant = Thread.current[INSTANT_KEY]
|
|
53
|
-
previous_strict = Thread.current[STRICT_KEY]
|
|
54
|
-
Thread.current[MEMO_KEY] = {}
|
|
55
|
-
Thread.current[STAT_KEY] = {}
|
|
56
|
-
Thread.current[INSTANT_KEY] = now_ns
|
|
57
|
-
Thread.current[STRICT_KEY] = strict
|
|
58
|
-
yield
|
|
59
|
-
ensure
|
|
60
|
-
Thread.current[MEMO_KEY] = previous_memo
|
|
61
|
-
Thread.current[STAT_KEY] = previous_stat
|
|
62
|
-
Thread.current[INSTANT_KEY] = previous_instant
|
|
63
|
-
Thread.current[STRICT_KEY] = previous_strict
|
|
69
|
+
def self.with_run(strict: false, &)
|
|
70
|
+
run = Run.new(digests: {}, stats: {}, instant: now_ns, strict: strict)
|
|
71
|
+
TypingState.current.with(RUN, run, &)
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
# The SHA-256 hex digest of `path`'s current content. Served from the active per-run table when one is
|
|
@@ -68,10 +76,10 @@ module Rigor
|
|
|
68
76
|
# read failure (missing / unreadable file) propagates exactly as `Digest::SHA256.file` would and is NOT
|
|
69
77
|
# memoised, so a caller's own rescue path is unchanged.
|
|
70
78
|
def self.hexdigest(path)
|
|
71
|
-
|
|
72
|
-
return Digest::SHA256.file(path).hexdigest if
|
|
79
|
+
run = TypingState.current[RUN]
|
|
80
|
+
return Digest::SHA256.file(path).hexdigest if run.nil?
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
run.digests[path] ||= Digest::SHA256.file(path).hexdigest
|
|
75
83
|
end
|
|
76
84
|
|
|
77
85
|
# @return whether digest-always validation is forced for this run. The env var wins over the
|
|
@@ -79,7 +87,7 @@ module Rigor
|
|
|
79
87
|
def self.strict_validation?
|
|
80
88
|
return true if ENV[STRICT_ENV] == "1"
|
|
81
89
|
|
|
82
|
-
|
|
90
|
+
TypingState.current[RUN]&.strict == true
|
|
83
91
|
end
|
|
84
92
|
|
|
85
93
|
# ADR-87 WD1 — packs the `:stat` FileEntry value: `"<digest> <size> <mtime_ns> <ctime_ns> <inode>
|
|
@@ -156,14 +164,14 @@ module Rigor
|
|
|
156
164
|
# describe that moment rather than an earlier probe's. A stat failure propagates un-memoised, exactly
|
|
157
165
|
# as {.hexdigest} treats a read failure.
|
|
158
166
|
def self.validation_stat(path)
|
|
159
|
-
|
|
160
|
-
return File.stat(path) if
|
|
167
|
+
run = TypingState.current[RUN]
|
|
168
|
+
return File.stat(path) if run.nil?
|
|
161
169
|
|
|
162
|
-
|
|
170
|
+
run.stats[path] ||= File.stat(path)
|
|
163
171
|
end
|
|
164
172
|
|
|
165
173
|
def self.recording_instant_ns
|
|
166
|
-
|
|
174
|
+
TypingState.current[RUN]&.instant || now_ns
|
|
167
175
|
end
|
|
168
176
|
|
|
169
177
|
# value layout: [digest, size, mtime_ns, ctime_ns, inode, recording_instant_ns]
|
|
@@ -202,7 +202,24 @@ module Rigor
|
|
|
202
202
|
# 47: issues #2079 / #2170 — each seed bundle's `global_write_census` also records each instance-side ivar
|
|
203
203
|
# write's coarse value (`[:own_write_type, class, name, value, path]`), which the class-ivar census joins across
|
|
204
204
|
# files and along the chain; a pre-47 bundle carries none, so a warm run would keep a seed a cold run widens.
|
|
205
|
-
|
|
205
|
+
# 48: issue #1237 — a `return_summaries` key may carry a third element, `true`, for a call key a splat call
|
|
206
|
+
# observed, which the gate re-drives with no positional correspondence. A pre-48 summary holds such a call under a
|
|
207
|
+
# positional key the engine bound as one argument, so a warm run would re-drive it the old way.
|
|
208
|
+
# 49: issue #3116 — a `return_summaries` entry for a def observed under more keys than the per-def cap is
|
|
209
|
+
# `{ keys: [], returns: [], effects:, overflow: true }`. A pre-49 summary holds the first eight keys of such a
|
|
210
|
+
# def with no marker, so a warm run would skip the reader of a key it never recorded.
|
|
211
|
+
# 50: issue #3160 — a `return_summaries` entry holds every key under which a current reader observes the def,
|
|
212
|
+
# merged across the runs that observed them, and a def whose keys cannot all be listed has no entry (the
|
|
213
|
+
# `overflow` form of 49 is gone). A pre-50 summary holds only what the last run that analysed the def's file
|
|
214
|
+
# saw, so a warm run would skip a reader added, or left out of that run, since.
|
|
215
|
+
# 51: issue #3180 — a reader the ADR-89 WD2 gate skipped holds the dependency edges the gate's probe recorded
|
|
216
|
+
# for the def it passed, and the summaries hold the keys that probe observed. A pre-51 snapshot's skipped
|
|
217
|
+
# readers lack the edges and keys of whatever a gate-passed def came to reach, so a warm run would skip them.
|
|
218
|
+
# 52: issues #3186 / #3187 — a `return_summaries` entry also holds `guard_constants`, the published-constant
|
|
219
|
+
# spellings the truthiness rule's one hop reads in the def's body, and a gate-skipped reader holds the plugin
|
|
220
|
+
# reads and run-scoped registrations the gate's probe made. A pre-52 snapshot's skipped readers lack those
|
|
221
|
+
# plugin rows, so a warm run would serve them past an edit of what the plugin read.
|
|
222
|
+
SCHEMA = 52
|
|
206
223
|
|
|
207
224
|
# The persisted per-file state.
|
|
208
225
|
# `cache` maps an analyzed file to its diagnostics.
|
|
@@ -233,10 +250,15 @@ module Rigor
|
|
|
233
250
|
# fingerprint cannot see.
|
|
234
251
|
# ADR-89 WD2:
|
|
235
252
|
# `return_summaries` maps a `[path, "Class#method" | "Class.method"]` to the callee's persisted
|
|
236
|
-
# behavioural surface `{ keys:, returns:, effects: }` — the observed `[receiver, arg_types]`
|
|
237
|
-
# (
|
|
238
|
-
#
|
|
239
|
-
#
|
|
253
|
+
# behavioural surface `{ keys:, returns:, effects:, guard_constants: }` — the observed `[receiver, arg_types]`
|
|
254
|
+
# call keys (issue #1237: `[receiver, arg_types, true]` for a key a splat call observed)
|
|
255
|
+
# (Marshal-clean type tuples), their `describe(:short)` return descriptors, the content-mutated
|
|
256
|
+
# parameter positions, and (issue #3186) the published-constant spellings the body reads.
|
|
257
|
+
# A recheck re-evaluates a declaration-stable changed callee at these keys and,
|
|
258
|
+
# when every return, the effects and the spellings are unchanged, skips its symbol dependents.
|
|
259
|
+
# Issues #3116 / #3160 — an entry
|
|
260
|
+
# holds every key under which a current symbol dependent observes the def; a def whose keys cannot all be
|
|
261
|
+
# listed (more than the per-def cap, among others) has no entry, and the recheck never skips its dependents.
|
|
240
262
|
# ADR-67 WD6c lift:
|
|
241
263
|
# `param_table` is the `parameter_inference:` seed table (`[class, method, kind] => {param => Type}`)
|
|
242
264
|
# the run that wrote the snapshot seeded its analysis from — `{}` when the gate is off. A recheck
|
|
@@ -333,8 +355,7 @@ module Rigor
|
|
|
333
355
|
"sig:#{digest_signature_paths(project_sig_roots(configuration))}",
|
|
334
356
|
"pre_eval:#{digest_pre_eval(configuration.pre_eval)}"
|
|
335
357
|
]
|
|
336
|
-
parts.concat(
|
|
337
|
-
parts.concat(dependency_source_parts(configuration))
|
|
358
|
+
parts.concat(project_input_parts(configuration))
|
|
338
359
|
identity = EngineSource.process_identity
|
|
339
360
|
parts << "engine-source:#{identity}" if identity
|
|
340
361
|
# Issue #1700 — the gated vendored signatures the run's source activates. A file that gains or drops a
|
|
@@ -360,12 +381,34 @@ module Rigor
|
|
|
360
381
|
# Issue #2517 — a selected core is read like the project's own `sig/`, so an edit in place to one of its files
|
|
361
382
|
# must drop the snapshot (and the language server's session, which keys on this fingerprint). No part for the
|
|
362
383
|
# default core, so every other project's fingerprint is as it was.
|
|
384
|
+
def self.extra_signature_parts(configuration)
|
|
385
|
+
core_root_parts(configuration) + bundle_sig_parts(configuration)
|
|
386
|
+
end
|
|
387
|
+
private_class_method :extra_signature_parts
|
|
388
|
+
|
|
363
389
|
def self.core_root_parts(configuration)
|
|
364
390
|
core_root = configuration.core_root
|
|
365
391
|
core_root.nil? ? [] : ["core:#{digest_signature_paths([core_root])}"]
|
|
366
392
|
end
|
|
367
393
|
private_class_method :core_root_parts
|
|
368
394
|
|
|
395
|
+
# Issue #2987 — the bundle gems' own `sig/` directories the environment loads: which gems have one and what
|
|
396
|
+
# their `.rbs` files hold. The lockfile digest above covers a gem's version, not a `sig/` appearing under an
|
|
397
|
+
# unchanged version, nor an edit in place to one. Shared with the language server's session key. No part for a
|
|
398
|
+
# project whose bundle holds none, so its fingerprint is as it was.
|
|
399
|
+
def self.bundle_sig_parts(configuration)
|
|
400
|
+
locked = Environment::LockfileResolver.locked_gems(
|
|
401
|
+
lockfile_path: configuration.bundler_lockfile, project_root: Dir.pwd,
|
|
402
|
+
auto_detect: configuration.bundler_auto_detect
|
|
403
|
+
)
|
|
404
|
+
dirs = Environment::BundleSigDiscovery.discover(
|
|
405
|
+
bundle_path: configuration.bundler_bundle_path, project_root: Dir.pwd,
|
|
406
|
+
auto_detect: configuration.bundler_auto_detect, locked_gems: locked.empty? ? nil : locked
|
|
407
|
+
).map(&:to_s).sort
|
|
408
|
+
dirs.empty? ? [] : ["bundle-sig:#{dirs.join("\n")}:#{digest_signature_paths(dirs)}"]
|
|
409
|
+
end
|
|
410
|
+
private_class_method :bundle_sig_parts
|
|
411
|
+
|
|
369
412
|
def self.required_features(configuration, roots)
|
|
370
413
|
files = Analysis::PathExpansion.ruby_files(roots, configuration.exclude_patterns,
|
|
371
414
|
base: configuration.exclude_base)
|
|
@@ -383,6 +426,22 @@ module Rigor
|
|
|
383
426
|
end
|
|
384
427
|
private_class_method :dependency_source_parts
|
|
385
428
|
|
|
429
|
+
# The parts read off the project beside its configuration, in the order the fingerprint has always joined them:
|
|
430
|
+
# the extra signature roots, the `dependencies.source_inference` gems, and (#3030) the collection setup hint.
|
|
431
|
+
def self.project_input_parts(configuration)
|
|
432
|
+
extra_signature_parts(configuration) + dependency_source_parts(configuration) +
|
|
433
|
+
collection_setup_hint_parts(configuration)
|
|
434
|
+
end
|
|
435
|
+
private_class_method :project_input_parts
|
|
436
|
+
|
|
437
|
+
# Issue #3030 — the `rbs.coverage.collection-not-installed` hint, a run-level row read off the working directory
|
|
438
|
+
# that no other part covers. No part without a hint, so every other project's fingerprint is as it was.
|
|
439
|
+
def self.collection_setup_hint_parts(configuration)
|
|
440
|
+
hint = Analysis::RunCacheKey.collection_setup_hint(configuration)
|
|
441
|
+
hint ? ["rbs-collection-hint:#{hint}"] : []
|
|
442
|
+
end
|
|
443
|
+
private_class_method :collection_setup_hint_parts
|
|
444
|
+
|
|
386
445
|
def self.digest_file_if_present(path)
|
|
387
446
|
path = path&.to_s
|
|
388
447
|
path && File.file?(path) ? Digest::SHA256.file(path).hexdigest : "absent"
|
|
@@ -77,7 +77,8 @@ module Rigor
|
|
|
77
77
|
# reconstructs {.config_entries} without loading a plugin, so an entry there that it cannot rebuild
|
|
78
78
|
# would turn the warm fast lane into a permanent miss on every project that enables a bundled plugin.
|
|
79
79
|
def self.env_only_config_entries(loader)
|
|
80
|
-
[deferred_signature_paths_entry(loader), project_signature_paths_entry(loader), core_root_entry(loader)
|
|
80
|
+
[deferred_signature_paths_entry(loader), project_signature_paths_entry(loader), core_root_entry(loader),
|
|
81
|
+
subordinate_signature_paths_entry(loader), collection_signature_paths_entry(loader)].compact
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
# Issue #2517 — which directory the env loaded core from. The selected core's files are digested by
|
|
@@ -142,6 +143,40 @@ module Rigor
|
|
|
142
143
|
|
|
143
144
|
private_class_method :deferred_signature_paths_entry
|
|
144
145
|
|
|
146
|
+
# Issue #2978 — which of the loader's `signature_paths:` stand down member by member to the rest (a bundle gem's
|
|
147
|
+
# collection copy behind its own `sig/`) changes the env built from byte-identical files, as the deferred
|
|
148
|
+
# partition does. Absent when there is none, so every other project keeps its key.
|
|
149
|
+
def self.subordinate_signature_paths_entry(loader)
|
|
150
|
+
return nil unless loader.respond_to?(:subordinate_signature_paths)
|
|
151
|
+
|
|
152
|
+
subordinate = Array(loader.subordinate_signature_paths).map(&:to_s).sort
|
|
153
|
+
return nil if subordinate.empty?
|
|
154
|
+
|
|
155
|
+
Descriptor::ConfigEntry.new(
|
|
156
|
+
key: "rbs.subordinate_signature_paths",
|
|
157
|
+
value_hash: Digest::SHA256.hexdigest(subordinate.join("\n"))
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private_class_method :subordinate_signature_paths_entry
|
|
162
|
+
|
|
163
|
+
# Issue #3030 — which of the loader's `signature_paths:` are `rbs collection` gem directories decides whether
|
|
164
|
+
# their `_`-prefixed directories load, so it changes the env built from byte-identical files. Absent when
|
|
165
|
+
# there is none, so every project without a collection lockfile keeps its key.
|
|
166
|
+
def self.collection_signature_paths_entry(loader)
|
|
167
|
+
return nil unless loader.respond_to?(:collection_signature_paths)
|
|
168
|
+
|
|
169
|
+
collection = Array(loader.collection_signature_paths).map(&:to_s).sort
|
|
170
|
+
return nil if collection.empty?
|
|
171
|
+
|
|
172
|
+
Descriptor::ConfigEntry.new(
|
|
173
|
+
key: "rbs.collection_signature_paths",
|
|
174
|
+
value_hash: Digest::SHA256.hexdigest(collection.join("\n"))
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private_class_method :collection_signature_paths_entry
|
|
179
|
+
|
|
145
180
|
# Public (ADR-87 WD4) so the boot-slimming run-cache probe reconstructs the identical `gems` +
|
|
146
181
|
# `rbs.libraries` key slots the runner writes, from the config-derived library list, without a loader.
|
|
147
182
|
def self.rbs_gem_entry
|
|
@@ -208,7 +243,7 @@ module Rigor
|
|
|
208
243
|
end
|
|
209
244
|
|
|
210
245
|
# @param library_names — the loader's merged library list (or, on the WD4 probe
|
|
211
|
-
# path, `Environment::
|
|
246
|
+
# path, `Environment::BaseLibraries.resolve` reconstructed without a loader).
|
|
212
247
|
def self.libraries_entry(library_names)
|
|
213
248
|
sorted = library_names.map(&:to_s).sort
|
|
214
249
|
Descriptor::ConfigEntry.new(
|
|
@@ -36,7 +36,11 @@ module Rigor
|
|
|
36
36
|
loader.respond_to?(:deferred_signature_paths) ? loader.deferred_signature_paths : [],
|
|
37
37
|
project_signature_paths:
|
|
38
38
|
loader.respond_to?(:project_signature_paths) ? loader.project_signature_paths : [],
|
|
39
|
-
core_root: loader.respond_to?(:core_root) ? loader.core_root : nil
|
|
39
|
+
core_root: loader.respond_to?(:core_root) ? loader.core_root : nil,
|
|
40
|
+
subordinate_signature_paths:
|
|
41
|
+
loader.respond_to?(:subordinate_signature_paths) ? loader.subordinate_signature_paths : [],
|
|
42
|
+
collection_signature_paths:
|
|
43
|
+
loader.respond_to?(:collection_signature_paths) ? loader.collection_signature_paths : []
|
|
40
44
|
)
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -176,7 +176,7 @@ module Rigor
|
|
|
176
176
|
|
|
177
177
|
baseline = Analysis::Baseline.from_diagnostics(diagnostics, match_mode: options.fetch(:match_mode),
|
|
178
178
|
project_root: Dir.pwd)
|
|
179
|
-
|
|
179
|
+
return EXIT_USAGE unless write_baseline_file(path, baseline.to_yaml)
|
|
180
180
|
|
|
181
181
|
covered = baseline.buckets.sum(&:count)
|
|
182
182
|
@err.puts(
|
|
@@ -415,7 +415,8 @@ module Rigor
|
|
|
415
415
|
return 0 if options.fetch(:dry_run)
|
|
416
416
|
|
|
417
417
|
pruned = baseline.without(cleared.map(&:bucket))
|
|
418
|
-
|
|
418
|
+
return EXIT_USAGE unless write_baseline_file(options.fetch(:baseline), pruned.to_yaml)
|
|
419
|
+
|
|
419
420
|
@err.puts("rigor: pruned #{cleared.size} bucket(s); baseline now has #{pruned.size} entries.")
|
|
420
421
|
0
|
|
421
422
|
end
|
|
@@ -439,6 +440,18 @@ module Rigor
|
|
|
439
440
|
|
|
440
441
|
# ---- shared helpers ----------------------------------------
|
|
441
442
|
|
|
443
|
+
# #2576 — an output path that cannot be written is a usage error, the same rule `rigor init
|
|
444
|
+
# --path` follows (#2562): one `rigor baseline: cannot write PATH: REASON` line on stderr and
|
|
445
|
+
# exit 64, with no backtrace and no directory created on the way. The bare Errno backtrace read
|
|
446
|
+
# as an internal crash.
|
|
447
|
+
def write_baseline_file(path, content)
|
|
448
|
+
File.write(path, content)
|
|
449
|
+
true
|
|
450
|
+
rescue SystemCallError => e
|
|
451
|
+
@err.puts("rigor baseline: cannot write #{path}: #{e.message.sub(/ @ .*\z/m, '')}")
|
|
452
|
+
false
|
|
453
|
+
end
|
|
454
|
+
|
|
442
455
|
def load_baseline_or_exit(path)
|
|
443
456
|
unless File.exist?(path)
|
|
444
457
|
@err.puts("rigor: baseline file not found: #{path}")
|
|
@@ -866,7 +866,8 @@ module Rigor
|
|
|
866
866
|
return Configuration.load(options.fetch(:config)) unless options.fetch(:treat_all_as_inline_rbs)
|
|
867
867
|
|
|
868
868
|
path = options.fetch(:config) || Configuration.discover
|
|
869
|
-
|
|
869
|
+
config_file = path && File.exist?(path) ? path : nil
|
|
870
|
+
data = config_file ? Configuration.load_with_includes(config_file) : {}
|
|
870
871
|
# ADR-103 WD15 — captured before `DEFAULTS.merge` below folds "no `effects:` key" and "`effects:
|
|
871
872
|
# false`" into the same value; see `Configuration.load`'s identical capture.
|
|
872
873
|
effects_key_present = data.key?("effects")
|
|
@@ -874,7 +875,8 @@ module Rigor
|
|
|
874
875
|
data = data.dup
|
|
875
876
|
data["plugins"] = inject_treat_all_as_inline_rbs(Array(data["plugins"]))
|
|
876
877
|
Configuration.new(Configuration::DEFAULTS.merge(data), effects_key_present, target_ruby_key_present)
|
|
877
|
-
.with_exclude_base(
|
|
878
|
+
.with_exclude_base(config_file && File.dirname(config_file))
|
|
879
|
+
.with_config_path(config_file)
|
|
878
880
|
end
|
|
879
881
|
|
|
880
882
|
# ADR-50 § WD2 — applies the `--bleeding-edge[=ids]` / `--no-bleeding-edge` CLI selection over the configured
|
|
@@ -91,7 +91,14 @@ module Rigor
|
|
|
91
91
|
# exists" hazard: it records observations, so overwriting it is the whole point of running it.
|
|
92
92
|
def write(configuration, snapshot)
|
|
93
93
|
path = configuration.effects_snapshot_path
|
|
94
|
-
|
|
94
|
+
begin
|
|
95
|
+
File.write(path, snapshot.to_yaml)
|
|
96
|
+
rescue SystemCallError => e
|
|
97
|
+
# #2576 — an `effects.snapshot.path:` that cannot be written is a usage error, the same rule
|
|
98
|
+
# `rigor init --path` follows (#2562): one line on stderr and exit 64, no backtrace.
|
|
99
|
+
@err.puts("rigor effects: cannot write #{path}: #{e.message.sub(/ @ .*\z/m, '')}")
|
|
100
|
+
return CLI::EXIT_USAGE
|
|
101
|
+
end
|
|
95
102
|
@err.puts("rigor: wrote #{path} (#{snapshot.methods.size} method(s), #{snapshot.reach.size} reach entr" \
|
|
96
103
|
"#{snapshot.reach.size == 1 ? 'y' : 'ies'})")
|
|
97
104
|
# #436 — the note names what to write, not only what is missing: the same per-project preset
|
|
@@ -8,13 +8,14 @@ require_relative "options"
|
|
|
8
8
|
require_relative "check_runner_factory"
|
|
9
9
|
require_relative "../configuration"
|
|
10
10
|
require_relative "../lens"
|
|
11
|
+
require_relative "../lens/claims"
|
|
11
12
|
|
|
12
13
|
module Rigor
|
|
13
14
|
class CLI
|
|
14
15
|
# `rigor lens FILE...` — a declaration map of the files with type provenance, computed as `rigor check`
|
|
15
16
|
# computes them (ADR-113; output contract docs/internal-spec/lens.md). Experimental until `lens/v1`.
|
|
16
17
|
class LensCommand < Command
|
|
17
|
-
USAGE = "Usage: rigor lens [--format=text|json] [--config=PATH] [--workers=N] FILE..."
|
|
18
|
+
USAGE = "Usage: rigor lens [--format=text|json] [--claims=REQUEST] [--config=PATH] [--workers=N] FILE..."
|
|
18
19
|
|
|
19
20
|
def run
|
|
20
21
|
options = parse_options
|
|
@@ -25,13 +26,19 @@ module Rigor
|
|
|
25
26
|
config_path = options.fetch(:config) || Configuration.discover
|
|
26
27
|
# The working directory: Rigor auto-detects sig/ and the lockfiles there (lens.md § Serialization).
|
|
27
28
|
root = Dir.pwd
|
|
29
|
+
request = options[:claims] && Lens::Claims::Request.read(options[:claims], root: root, arguments: @argv)
|
|
28
30
|
workers = CheckRunnerFactory.resolve_workers(options, configuration)
|
|
29
|
-
|
|
31
|
+
run = Lens::Run.new(configuration: configuration, root: root, arguments: @argv, workers: workers)
|
|
32
|
+
outcome = run.call
|
|
30
33
|
document = Lens::Document.new(outcome: outcome, configuration: configuration, root: root,
|
|
31
34
|
config_path: config_path).to_h
|
|
35
|
+
attach_claims(document, request, outcome, run)
|
|
32
36
|
@err.puts("rigor lens: #{outcome.warm ? 'warm' : 'cold'} — #{@argv.size} target(s)")
|
|
33
37
|
render(document, options.fetch(:format))
|
|
34
38
|
outcome.targets.any? { |target| target.status == "ok" } ? 0 : 1
|
|
39
|
+
rescue Lens::Claims::IntegrityError => e
|
|
40
|
+
usage_error(e.message)
|
|
41
|
+
CLI::EXIT_USAGE
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
@@ -42,15 +49,23 @@ module Rigor
|
|
|
42
49
|
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
43
50
|
opts.on("--workers=N", Integer, "Worker processes for a cold run") { |value| options[:workers] = value }
|
|
44
51
|
Options.add_config(opts, options)
|
|
52
|
+
opts.on("--claims=REQUEST", "Check documentation claims (requires --format=json)") do |value|
|
|
53
|
+
options[:claims] = value
|
|
54
|
+
end
|
|
45
55
|
end
|
|
46
56
|
end
|
|
47
57
|
|
|
48
58
|
private
|
|
49
59
|
|
|
60
|
+
def attach_claims(document, request, outcome, run)
|
|
61
|
+
document["claims"] = Lens::Claims.new(request: request, outcome: outcome, run: run).call if request
|
|
62
|
+
end
|
|
63
|
+
|
|
50
64
|
def parse_options
|
|
51
65
|
options = { format: "text", config: nil, workers: nil }
|
|
52
66
|
option_parser(options).parse!(@argv)
|
|
53
67
|
return usage_error("no FILE given") if @argv.empty?
|
|
68
|
+
return usage_error("--claims requires --format=json") if options[:claims] && options[:format] != "json"
|
|
54
69
|
|
|
55
70
|
options
|
|
56
71
|
rescue OptionParser::ParseError => e
|
|
@@ -308,6 +308,14 @@ module Rigor
|
|
|
308
308
|
# user asked for a write and did not get one, so the command must not report success — a green
|
|
309
309
|
# `sig-gen --write` in CI would otherwise mean nothing.
|
|
310
310
|
results.any? { |result| TARGET_REFUSALS.include?(result.action) } || !refused.empty? ? 1 : 0
|
|
311
|
+
rescue SigGen::Writer::WriteError => e
|
|
312
|
+
# #2576 — a target that cannot be written at all (permission denial, a directory where the
|
|
313
|
+
# `.rbs` belongs, a parent path that is a regular file) is a usage error, the same rule
|
|
314
|
+
# `rigor init --path` follows (#2562):
|
|
315
|
+
# one line on stderr and exit 64. The run stops at the first failure; targets already written
|
|
316
|
+
# stay written.
|
|
317
|
+
@err.puts("rigor sig-gen: #{e.message}")
|
|
318
|
+
CLI::EXIT_USAGE
|
|
311
319
|
end
|
|
312
320
|
|
|
313
321
|
# ADR-112 WD4 — the methods whose inline and `sig/` declarations disagree, refused because the run did not
|