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
data/lib/rigor/cli.rb
CHANGED
|
@@ -33,8 +33,11 @@ module Rigor
|
|
|
33
33
|
# actionable": `SystemStackError` from an ancestry walk that never terminates, `NoMemoryError` from a
|
|
34
34
|
# runaway allocation. Neither is a `StandardError`, so every rescue between here and the raise site
|
|
35
35
|
# declines them by design — the whole point of those rescues is to convert a per-file or per-plugin
|
|
36
|
-
# failure into a diagnostic
|
|
37
|
-
#
|
|
36
|
+
# failure into a diagnostic. Since #2455 the per-file phases (`Runner#analyze_file_body`, the worker session,
|
|
37
|
+
# the discovery loops) do contain a `SystemStackError` raised from Ruby code, as that file's own crash row;
|
|
38
|
+
# what still reaches here is one raised from C (`Prism.parse` of a source nested near Prism's cap, which has
|
|
39
|
+
# no backtrace and leaves no stack to recover on), one outside a per-file phase, and `NoMemoryError`. Left
|
|
40
|
+
# uncaught they abort with Ruby's own ~40-frame dump, which is non-zero (so a CI gate does catch it) but says
|
|
38
41
|
# neither that Rigor is at fault nor that the report on disk is empty. `Interrupt`, `SignalException`
|
|
39
42
|
# and `SystemExit` stay out: those are the user's answer, not a failure.
|
|
40
43
|
FATAL_ERRORS = [SystemStackError, NoMemoryError].freeze
|
data/lib/rigor/config_audit.rb
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
require "did_you_mean"
|
|
4
4
|
|
|
5
5
|
require_relative "config_path"
|
|
6
|
+
require_relative "environment/bundle_sig_discovery"
|
|
7
|
+
require_relative "environment/rbs_collection_discovery"
|
|
6
8
|
require_relative "flow_contribution/conflict"
|
|
9
|
+
require_relative "library_probe"
|
|
7
10
|
require_relative "signature_path_audit"
|
|
8
11
|
# ADR-87 WD4 — only the pure rule-id table (`ALL_RULES` / `RULE_FAMILIES`) is needed, so require the light
|
|
9
12
|
# `rule_ids.rb` rather than the engine-pulling `check_rules.rb`; keeps `config_audit` (loaded by every check)
|
|
@@ -37,8 +40,8 @@ module Rigor
|
|
|
37
40
|
module ConfigAudit
|
|
38
41
|
# One config-level finding. `kind` discriminates the source key (`:unknown_key`, `:signature_path`,
|
|
39
42
|
# `:bundled_plugin_signature_path`, `:library`, `:disabled_rule`, `:severity_override`,
|
|
40
|
-
# `:bundler_bundle_path`, `:bundler_lockfile`, `:rbs_collection_lockfile`); `fields`
|
|
41
|
-
# structured data merged into {#to_h} for JSON consumers.
|
|
43
|
+
# `:bundler_bundle_path`, `:bundler_lockfile`, `:rbs_collection_lockfile`, `:rbs_collection_library`); `fields`
|
|
44
|
+
# carries the kind-specific structured data merged into {#to_h} for JSON consumers.
|
|
42
45
|
Warning = Data.define(:kind, :message, :fields) do
|
|
43
46
|
def to_h
|
|
44
47
|
{ "kind" => kind.to_s, "message" => message }.merge(fields)
|
|
@@ -51,7 +54,7 @@ module Rigor
|
|
|
51
54
|
unknown_key_warnings(configuration) +
|
|
52
55
|
signature_path_warnings(configuration) +
|
|
53
56
|
bundled_plugin_signature_path_warnings(configuration) +
|
|
54
|
-
library_warnings(configuration) +
|
|
57
|
+
library_warnings(configuration, project_root) +
|
|
55
58
|
rule_token_warnings(configuration) +
|
|
56
59
|
explicit_path_warnings(configuration, project_root)
|
|
57
60
|
end
|
|
@@ -129,21 +132,32 @@ module Rigor
|
|
|
129
132
|
end
|
|
130
133
|
end
|
|
131
134
|
|
|
132
|
-
# `libraries:` entries RBS does not recognise
|
|
133
|
-
# `
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
135
|
+
# `libraries:` entries RBS does not recognise, and — issue #2946 — `source: { type: stdlib }` entries of
|
|
136
|
+
# the run's `rbs_collection.lock.yaml`, which load as libraries too ({Environment::BaseLibraries.resolve}). Uses the
|
|
137
|
+
# same `RBS::EnvironmentLoader#has_library?` answer the loader filters through
|
|
138
|
+
# ({Environment::RbsLoader} `library_loader`), so a flagged name is one the loader skipped. A
|
|
139
|
+
# {Environment::RequiredFeatures::VENDORED_DIRS} name (`prime`) under `libraries:` is not audited: the
|
|
140
|
+
# configuration turns it into a vendored feature token the loader never asks `has_library?` about. A
|
|
141
|
+
# lockfile entry of that name is not a token, so it is still audited. The `rbs` CLI warns on the same
|
|
142
|
+
# lockfile entry (`Collection::Sources::Stdlib#manifest_of`).
|
|
143
|
+
# Fails soft: if RBS itself raises, no warning rather than a crash.
|
|
144
|
+
def self.library_warnings(configuration, project_root = Dir.pwd)
|
|
145
|
+
lock = { lockfile_path: configuration.rbs_collection_lockfile, project_root: project_root,
|
|
146
|
+
auto_detect: configuration.rbs_collection_auto_detect }
|
|
147
|
+
lockfile = Environment::RbsCollectionDiscovery.stdlib_library_names(**lock)
|
|
148
|
+
# Issue #2971 — a `libraries:` name the collection lockfile provides loads from the collection, not as an
|
|
149
|
+
# RBS library, so it is not an unknown library.
|
|
150
|
+
provided = Environment::RbsCollectionDiscovery.collection_gem_names(**lock)
|
|
151
|
+
named = (LibraryProbe.auditable(configuration.libraries) - provided).map { |lib| [:library, "libraries:", lib] } +
|
|
152
|
+
lockfile.map { |name| [:rbs_collection_library, "rbs_collection.lock.yaml: stdlib entry", name] }
|
|
153
|
+
return [] if named.empty?
|
|
154
|
+
|
|
155
|
+
unavailable = LibraryProbe.predicate.then { |has| named.reject { |_, _, lib| has.call(lib) } }
|
|
156
|
+
# Issue #2987 — nor is a name whose own `sig/` the bundle loaded; read only once a name is unavailable.
|
|
157
|
+
bundled = unavailable.empty? ? [] : Environment::BundleSigDiscovery.loaded_names(configuration, project_root)
|
|
158
|
+
unavailable.reject { |kind, _, lib| kind == :library && bundled.include?(lib) }.map do |kind, source, lib|
|
|
159
|
+
Warning.new(kind: kind, fields: { "name" => lib },
|
|
160
|
+
message: "#{source} #{lib.inspect} is not an available RBS library (no signatures loaded from it)")
|
|
147
161
|
end
|
|
148
162
|
rescue StandardError
|
|
149
163
|
[]
|
|
@@ -86,6 +86,7 @@ module Rigor
|
|
|
86
86
|
# a config edit the author may deliberately not want.
|
|
87
87
|
"effect.annotations-unchecked" => :info,
|
|
88
88
|
"static.value-use.void" => :off,
|
|
89
|
+
"plugin.api-surface" => :off,
|
|
89
90
|
# Opt-in author assertion: you only see it if you wrote a
|
|
90
91
|
# `conforms-to` directive, so it stays a :warning even in
|
|
91
92
|
# lenient — it is never unsolicited noise.
|
|
@@ -131,6 +132,7 @@ module Rigor
|
|
|
131
132
|
"suppression.empty" => :warning,
|
|
132
133
|
"suppression.unknown-marker" => :warning,
|
|
133
134
|
"static.value-use.void" => :off,
|
|
135
|
+
"plugin.api-surface" => :off,
|
|
134
136
|
"effect.envelope-exceeded" => :warning,
|
|
135
137
|
"effect.liskov-widened" => :warning,
|
|
136
138
|
"effect.unknown-label" => :info,
|
|
@@ -173,6 +175,7 @@ module Rigor
|
|
|
173
175
|
"suppression.unknown-marker" => :warning,
|
|
174
176
|
# `:off` even under strict: the gate is `bleeding_edge:`, not the profile (ADR-50 WD1 / ADR-100).
|
|
175
177
|
"static.value-use.void" => :off,
|
|
178
|
+
"plugin.api-surface" => :off,
|
|
176
179
|
"effect.envelope-exceeded" => :error,
|
|
177
180
|
"effect.liskov-widened" => :error,
|
|
178
181
|
"effect.unknown-label" => :warning,
|
|
@@ -234,11 +237,17 @@ module Rigor
|
|
|
234
237
|
|
|
235
238
|
private_class_method :row_override
|
|
236
239
|
|
|
240
|
+
# Rule ids a dot-free family key does not reach. `plugin.api-surface` is the one built-in id under `plugin.`,
|
|
241
|
+
# the prefix of plugin rows, so a `plugin` key must not turn the bleeding-edge rule on (#2822); `disable:
|
|
242
|
+
# [plugin]` does not reach it either. A key naming the id itself still does, in either direction.
|
|
243
|
+
FAMILY_FALLBACK_EXEMPT = ["plugin.api-surface"].freeze
|
|
244
|
+
|
|
237
245
|
# A family name has no `.` in it, so only a dot-free key can match one. `resolve` runs for every
|
|
238
246
|
# diagnostic in a run's stream, and most configurations carry no such key (usually no overrides at all),
|
|
239
247
|
# so the split below is skipped for them rather than allocating a name that nothing can match.
|
|
240
248
|
def family_override(rule, overrides)
|
|
241
249
|
return nil unless overrides.any? { |key, _| !key.to_s.include?(".") }
|
|
250
|
+
return nil if FAMILY_FALLBACK_EXEMPT.include?(rule)
|
|
242
251
|
|
|
243
252
|
family = rule.split(".").first
|
|
244
253
|
return nil if family.nil?
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -212,8 +212,8 @@ module Rigor
|
|
|
212
212
|
# set up with `rbs collection install`, the resulting `rbs_collection.lock.yaml` carries the resolved
|
|
213
213
|
# (gem, version, source) triples and `.gem_rbs_collection/` holds the downloaded `.rbs` files. Rigor
|
|
214
214
|
# parses the lockfile and auto-feeds each gem's `<collection_root>/<name>/<version>/` directory into
|
|
215
|
-
# `RbsLoader`'s `signature_paths:`. Sources of type `stdlib`
|
|
216
|
-
# `DEFAULT_LIBRARIES`
|
|
215
|
+
# `RbsLoader`'s `signature_paths:`. Sources of type `stdlib` install no directory; issue #2946 — they
|
|
216
|
+
# load as RBS libraries, appended to `DEFAULT_LIBRARIES + libraries:`, as the `rbs` CLI loads them.
|
|
217
217
|
#
|
|
218
218
|
# `lockfile:` (String, optional): explicit path to `rbs_collection.lock.yaml`. Resolved relative to
|
|
219
219
|
# the project root when relative.
|
|
@@ -361,7 +361,8 @@ module Rigor
|
|
|
361
361
|
# [PHPStan](https://phpstan.org/config-reference#paths).
|
|
362
362
|
def self.load(path = nil)
|
|
363
363
|
resolved = path || discover
|
|
364
|
-
|
|
364
|
+
loaded = resolved && File.exist?(resolved) ? resolved : nil
|
|
365
|
+
if loaded.nil?
|
|
365
366
|
data = DEFAULTS
|
|
366
367
|
effects_key_present = false
|
|
367
368
|
target_ruby_key_present = false
|
|
@@ -370,13 +371,14 @@ module Rigor
|
|
|
370
371
|
# `DEFAULTS` itself carries an `"effects" => false` entry: once merged, "the file never wrote
|
|
371
372
|
# `effects:`" and "the file wrote `effects: false`" collapse to the same value and become
|
|
372
373
|
# indistinguishable to {#initialize}. This is the only place that distinction still exists.
|
|
373
|
-
raw = load_with_includes(
|
|
374
|
+
raw = load_with_includes(loaded)
|
|
374
375
|
effects_key_present = raw.key?("effects")
|
|
375
376
|
target_ruby_key_present = raw.key?("target_ruby")
|
|
376
377
|
data = DEFAULTS.merge(raw)
|
|
377
378
|
end
|
|
378
379
|
new(autowire_default_plugins(data), effects_key_present, target_ruby_key_present || false)
|
|
379
|
-
.with_exclude_base(
|
|
380
|
+
.with_exclude_base(loaded && File.dirname(loaded))
|
|
381
|
+
.with_config_path(loaded)
|
|
380
382
|
end
|
|
381
383
|
|
|
382
384
|
# #1576 — the directory relative `exclude:` patterns are matched against: the directory of the config file in
|
|
@@ -385,6 +387,17 @@ module Rigor
|
|
|
385
387
|
@base_dir || Dir.pwd
|
|
386
388
|
end
|
|
387
389
|
|
|
390
|
+
# Issue #2742 — the path config-anchored diagnostics are positioned at: the loaded config file's path
|
|
391
|
+
# relative to the working directory (the project-root convention {Analysis::Baseline} uses), or
|
|
392
|
+
# `.rigor.yml` when no file was loaded. A `--config=config/rigor.yml` run names `config/rigor.yml`.
|
|
393
|
+
def config_display_path
|
|
394
|
+
return DEFAULT_PATH if @config_path.nil?
|
|
395
|
+
|
|
396
|
+
Pathname.new(@config_path).relative_path_from(Pathname.new(Dir.pwd)).to_s
|
|
397
|
+
rescue ArgumentError
|
|
398
|
+
@config_path
|
|
399
|
+
end
|
|
400
|
+
|
|
388
401
|
# ADR-93 WD2 — the one bundled plugin default-wired without a `plugins:` entry. `rigor-rbs-inline` is the
|
|
389
402
|
# gem; `rbs-inline` is its manifest id (the loader raises on a duplicate id, so both forms count as
|
|
390
403
|
# "already listed").
|
|
@@ -675,6 +688,10 @@ module Rigor
|
|
|
675
688
|
def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
676
689
|
{
|
|
677
690
|
"target_ruby" => target_ruby,
|
|
691
|
+
# Issue #2742 — the loaded file's display path is part of the resolved configuration: two files
|
|
692
|
+
# with identical bodies still anchor their config diagnostics differently, so the run-cache and
|
|
693
|
+
# incremental fingerprints must tell them apart.
|
|
694
|
+
"config_path" => config_display_path,
|
|
678
695
|
# A stated `target_ruby` turns on the deprecation rules a defaulted one leaves off (#1692), so the run cache
|
|
679
696
|
# key and the incremental fingerprint must tell the two apart even where the value matches.
|
|
680
697
|
"target_ruby_explicit" => target_ruby_explicit?,
|
|
@@ -784,6 +801,16 @@ module Rigor
|
|
|
784
801
|
copy.freeze
|
|
785
802
|
end
|
|
786
803
|
|
|
804
|
+
# Issue #2742 — a sibling Configuration recording which file {Configuration.load} read, so the
|
|
805
|
+
# diagnostics anchored at "the configuration" name it ({#config_display_path}). `path` is the resolved
|
|
806
|
+
# config path; nil — no file loaded, or a bare {Configuration.new} — keeps the `.rigor.yml` default.
|
|
807
|
+
# Same frozen `dup` shape as {#with_exclude_base}.
|
|
808
|
+
def with_config_path(path)
|
|
809
|
+
copy = dup
|
|
810
|
+
copy.instance_variable_set(:@config_path, path && File.absolute_path(path).freeze)
|
|
811
|
+
copy.freeze
|
|
812
|
+
end
|
|
813
|
+
|
|
787
814
|
# #1777 — a sibling Configuration with only `baseline:` unset, every other field (including the
|
|
788
815
|
# explicitness of `target_ruby`) shared with the receiver. `rigor baseline generate` analyses with
|
|
789
816
|
# this so the generating run sees exactly the configuration `rigor check` will, minus the filter it
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../type"
|
|
4
4
|
require_relative "../inference/origin_lookup"
|
|
5
|
+
require_relative "../typing_state"
|
|
5
6
|
require_relative "attribution"
|
|
6
7
|
require_relative "envelope_index"
|
|
7
8
|
require_relative "file_collection"
|
|
@@ -24,8 +25,26 @@ module Rigor
|
|
|
24
25
|
# `dynamic_origins` cause the typer already recorded. With no `effects:` block nothing calls
|
|
25
26
|
# {collect_for}, `@active_count` stays zero, and `rigor check` is byte-identical.
|
|
26
27
|
module Collector
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
# ADR-129 WD1 — the installed accumulator is a Frame whose contents are a Resource; only {record_root} touches it
|
|
29
|
+
# now, and the call-site decisions are the bound Table {CALLS}. The activation count below is a process switch,
|
|
30
|
+
# outside the typing state (WD5).
|
|
31
|
+
ACCUMULATOR = TypingState.frame(
|
|
32
|
+
:effects_collector,
|
|
33
|
+
contents: :resource, reason: "the accumulator a file's collection installs; each call site records into it",
|
|
34
|
+
# G1′ witness: `Collector.record_call` reads it during typing, so it is not a never-reads switch.
|
|
35
|
+
witness: "spec/fixtures/typing_state_witnesses/effects_collector.rb"
|
|
36
|
+
)
|
|
37
|
+
private_constant :ACCUMULATOR
|
|
38
|
+
|
|
39
|
+
# ADR-129 WD1 — the per-file call-site decisions are a bound Table: each installed accumulator's `calls` is one of
|
|
40
|
+
# its stores, journaled while a capture is open so a later `type_of` memo can validate the reads and replay the
|
|
41
|
+
# writes. The root ({record_root}) stays a touch of the accumulator: it is set once per file, before typing.
|
|
42
|
+
CALLS = TypingState.table(
|
|
43
|
+
:effects_calls,
|
|
44
|
+
bound: true,
|
|
45
|
+
reason: "node => CallRecord per file accumulator; first write wins, and a re-query reads the pinned decision"
|
|
46
|
+
)
|
|
47
|
+
private_constant :CALLS
|
|
29
48
|
|
|
30
49
|
# What one call site decided, as the typer saw it.
|
|
31
50
|
#
|
|
@@ -97,17 +116,18 @@ module Rigor
|
|
|
97
116
|
# site imports must not depend on which process typed the file.
|
|
98
117
|
def collect_for(path, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
|
|
99
118
|
plugin_facts: PluginFacts.empty, unit_key: nil, unit_owner: nil)
|
|
100
|
-
previous = Thread.current[KEY]
|
|
101
119
|
accumulator = Accumulator.new(path.to_s, attribution: attribution, envelopes: envelopes,
|
|
102
120
|
plugin_facts: plugin_facts, unit_key: unit_key,
|
|
103
121
|
unit_owner: unit_owner)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
TypingState.current.with(ACCUMULATOR, accumulator) do
|
|
123
|
+
@mutex.synchronize { @active_count += 1 }
|
|
124
|
+
begin
|
|
125
|
+
yield
|
|
126
|
+
build(accumulator)
|
|
127
|
+
ensure
|
|
128
|
+
@mutex.synchronize { @active_count -= 1 }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
111
131
|
end
|
|
112
132
|
|
|
113
133
|
def active?
|
|
@@ -117,8 +137,12 @@ module Rigor
|
|
|
117
137
|
# The parsed root of the file being analyzed. Recorded by the analysis body once the parse is known
|
|
118
138
|
# to have succeeded, so the scan runs over exactly what the typer saw.
|
|
119
139
|
def record_root(root)
|
|
120
|
-
|
|
121
|
-
accumulator
|
|
140
|
+
state = TypingState.current
|
|
141
|
+
accumulator = state[ACCUMULATOR]
|
|
142
|
+
return unless accumulator
|
|
143
|
+
|
|
144
|
+
state.touch(ACCUMULATOR)
|
|
145
|
+
accumulator.root = root if accumulator.root.nil?
|
|
122
146
|
end
|
|
123
147
|
|
|
124
148
|
# One call site's dispatch decision. `receiver` is the type the typer computed for the receiver and
|
|
@@ -127,30 +151,38 @@ module Rigor
|
|
|
127
151
|
# The `scope.source_path` guard matters: inter-procedural inference re-types a *callee's* body while
|
|
128
152
|
# the caller's file is being analyzed, and those nodes belong to the callee's own file collection.
|
|
129
153
|
def record_call(node, receiver, scope)
|
|
130
|
-
|
|
154
|
+
state = TypingState.current
|
|
155
|
+
accumulator = state[ACCUMULATOR]
|
|
131
156
|
return if accumulator.nil? || accumulator.path != scope.source_path
|
|
157
|
+
|
|
132
158
|
# First write wins, so a re-query has nothing to add — and asking here rather than inside
|
|
133
159
|
# {Accumulator#record} is what stops it paying for the `Data` and the origin lookup below.
|
|
160
|
+
calls = accumulator.calls
|
|
161
|
+
state.log_bound_read(CALLS, calls, node, calls.fetch(node, TypingState::ABSENT)) if state.capturing?
|
|
134
162
|
return if accumulator.recorded?(node)
|
|
135
163
|
|
|
136
164
|
dynamic = dynamic_receiver?(receiver)
|
|
137
165
|
class_name, kind = descriptor_for(receiver)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
cause: dynamic ? Inference::OriginLookup.origin_for(scope, node.receiver)&.to_s : nil,
|
|
143
|
-
resolved: true
|
|
144
|
-
)
|
|
166
|
+
call_record = CallRecord.new(
|
|
167
|
+
receiver_class: class_name, kind: kind, dynamic: dynamic,
|
|
168
|
+
cause: dynamic ? Inference::OriginLookup.origin_for(scope, node.receiver)&.to_s : nil,
|
|
169
|
+
resolved: true
|
|
145
170
|
)
|
|
171
|
+
accumulator.record(node, call_record)
|
|
172
|
+
state.log_bound_write(CALLS, calls, node, call_record, TypingState::ABSENT)
|
|
146
173
|
end
|
|
147
174
|
|
|
148
175
|
# The typer's fallback verdict: no tier resolved this call.
|
|
149
176
|
def record_unresolved(node, source_path)
|
|
150
|
-
|
|
177
|
+
state = TypingState.current
|
|
178
|
+
accumulator = state[ACCUMULATOR]
|
|
151
179
|
return if accumulator.nil? || accumulator.path != source_path
|
|
152
180
|
|
|
181
|
+
calls = accumulator.calls
|
|
182
|
+
existing = calls.fetch(node, TypingState::ABSENT)
|
|
183
|
+
state.log_bound_read(CALLS, calls, node, existing)
|
|
153
184
|
accumulator.mark_unresolved(node)
|
|
185
|
+
state.log_bound_write(CALLS, calls, node, calls[node], existing) unless TypingState::ABSENT.equal?(existing)
|
|
154
186
|
end
|
|
155
187
|
|
|
156
188
|
# The class a receiver type projects to, as `[class_name, kind]`. A deliberately small mirror of the
|
|
@@ -4,6 +4,7 @@ require "yaml"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../config_path"
|
|
6
6
|
require_relative "../rooted_glob"
|
|
7
|
+
require_relative "lockfile_resolver"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
9
10
|
class Environment
|
|
@@ -49,6 +50,10 @@ module Rigor
|
|
|
49
50
|
"bundler", "rubygems"
|
|
50
51
|
].freeze
|
|
51
52
|
|
|
53
|
+
# The two bundle layouts {.discover} walks, relative to the resolved bundle root. Also the listing rows the
|
|
54
|
+
# run cache records (issue #2987), so a gem gaining or losing `sig/` invalidates it.
|
|
55
|
+
SIG_GLOBS = %w[ruby/*/gems/*/sig ruby/*/bundler/gems/*/sig].freeze
|
|
56
|
+
|
|
52
57
|
# @param bundle_path — explicit path to the bundler install root. When `nil`,
|
|
53
58
|
# falls back to `auto_detect` if `auto_detect:` is true.
|
|
54
59
|
# @param project_root — resolution base for relative `bundle_path:` and the auto-detect search.
|
|
@@ -92,45 +97,75 @@ module Rigor
|
|
|
92
97
|
# all (`Bundler::Source::Path#path` is the user's own `path:` directory, used in place), so there is
|
|
93
98
|
# no bundle-relative location to walk. A `path:`-sourced gem's `sig/` is only reachable by adding it
|
|
94
99
|
# directly to the project's `signature_paths:` — which already works today and needs no discovery.
|
|
95
|
-
all =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
all = SIG_GLOBS.flat_map { |pattern| RootedGlob.glob(resolved, pattern) }.map { |d| Pathname.new(d) }
|
|
101
|
+
if locked_gems.nil? || locked_gems.empty?
|
|
102
|
+
return all.reject { |sig_dir| skip_gems.include?(gem_name_from_sig_path(sig_dir)) }
|
|
103
|
+
end
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
# Issue #2978 — with a lockfile, a directory's gem name is the locked entry it matches, not a parse of the
|
|
106
|
+
# directory name, which reads `foo-2fa-1.0` as `foo`.
|
|
107
|
+
names = locked_dir_names(locked_gems)
|
|
108
|
+
all.select do |sig_dir|
|
|
109
|
+
name = locked_name_of(sig_dir, locked_gems, names)
|
|
110
|
+
name && !skip_gems.include?(name)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The locked gem name `sig_dir` belongs to, or nil when it matches no locked entry. A git install's directory
|
|
115
|
+
# carries no version, so only the gem NAME can be checked. Name alone is not enough: a gem that moved from
|
|
116
|
+
# `git:` to a released version keeps its stale `bundler/gems/` checkout until `bundle clean`, under a name the
|
|
117
|
+
# lockfile still lists, so `locked_gems` must say this name is CURRENTLY git-sourced, not merely present.
|
|
118
|
+
#
|
|
119
|
+
# @param names — {.locked_dir_names} of `locked_gems`.
|
|
120
|
+
def self.locked_name_of(sig_dir, locked_gems, names)
|
|
121
|
+
if git_sourced_layout?(sig_dir)
|
|
122
|
+
name = gem_name_from_sig_path(sig_dir)
|
|
123
|
+
locked_gems[name]&.git_source ? name : nil
|
|
124
|
+
else
|
|
125
|
+
names[sig_dir.parent.basename.to_s]
|
|
112
126
|
end
|
|
113
127
|
end
|
|
128
|
+
private_class_method :locked_name_of
|
|
129
|
+
|
|
130
|
+
# Issue #2971 — the locked gem names `sig_dirs` (a {.discover} result) belong to, keyed the way {.discover}
|
|
131
|
+
# filters: a rubygems-layout directory by `<name>-<version>[-<platform>]` against the locked variants, a git
|
|
132
|
+
# layout by a git-sourced locked entry of that name. Unlike {.gem_name_from_sig_path} it never truncates a
|
|
133
|
+
# name that holds a `-<digit>` (`foo-2fa-1.0` is `foo-2fa`, not `foo`).
|
|
134
|
+
def self.locked_names_of(sig_dirs, locked_gems)
|
|
135
|
+
return [] if locked_gems.nil? || locked_gems.empty?
|
|
136
|
+
|
|
137
|
+
names = locked_dir_names(locked_gems)
|
|
138
|
+
sig_dirs.filter_map { |dir| locked_name_of(dir, locked_gems, names) }.uniq
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Issue #2987 — the locked gem names whose own `sig/` {.discover} loads for a configuration's bundle and
|
|
142
|
+
# lockfile: a pure file read ({LockfileResolver}), so the `libraries:` audit can ask without loading `rbs`.
|
|
143
|
+
def self.loaded_names(configuration, project_root)
|
|
144
|
+
auto_detect = configuration.bundler_auto_detect
|
|
145
|
+
locked = LockfileResolver.locked_gems(lockfile_path: configuration.bundler_lockfile,
|
|
146
|
+
project_root: project_root, auto_detect: auto_detect)
|
|
147
|
+
dirs = discover(bundle_path: configuration.bundler_bundle_path, project_root: project_root,
|
|
148
|
+
auto_detect: auto_detect, locked_gems: locked.empty? ? nil : locked)
|
|
149
|
+
locked_names_of(dirs, locked)
|
|
150
|
+
end
|
|
114
151
|
|
|
115
|
-
# `{name => LockedGem}` →
|
|
152
|
+
# `{name => LockedGem}` → `{canonical bundler gem directory basename => name}`. Pure-Ruby gems install as
|
|
116
153
|
# `<name>-<version>`; platform-specific gems install as `<name>-<version>-<platform>` (e.g.
|
|
117
154
|
# `ffi-1.17.4-aarch64-linux-gnu`). Lockfile platform `"ruby"` is the pure-Ruby case; any other value is
|
|
118
155
|
# treated as a platform tag. Every locked variant contributes a directory (#1844): the set only selects
|
|
119
156
|
# among directories that are actually installed, so admitting another platform's name costs nothing,
|
|
120
|
-
# while dropping the host's would discard its `sig/`.
|
|
121
|
-
|
|
122
|
-
|
|
157
|
+
# while dropping the host's would discard its `sig/`. Issue #2978 — the name comes from the lockfile, so a name
|
|
158
|
+
# holding `-<digit>` is read whole.
|
|
159
|
+
def self.locked_dir_names(locked_gems)
|
|
160
|
+
locked_gems.each_value.with_object({}) do |locked, names|
|
|
123
161
|
locked.variants.each do |version, platform|
|
|
124
162
|
base = "#{locked.name}-#{version}"
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
else
|
|
128
|
-
"#{base}-#{platform}"
|
|
129
|
-
end
|
|
163
|
+
dir = platform == "ruby" || platform.empty? ? base : "#{base}-#{platform}"
|
|
164
|
+
names[dir] = locked.name
|
|
130
165
|
end
|
|
131
166
|
end
|
|
132
167
|
end
|
|
133
|
-
private_class_method :
|
|
168
|
+
private_class_method :locked_dir_names
|
|
134
169
|
|
|
135
170
|
# `<bundle>/ruby/X.Y.Z/gems/<name>-<ver>/sig` → `<name>`, or
|
|
136
171
|
# `<bundle>/ruby/X.Y.Z/bundler/gems/<repo>-<12-hex-revision>/sig` → `<repo>`. The two bundler-install
|
|
@@ -145,8 +180,8 @@ module Rigor
|
|
|
145
180
|
# different repo name, a monorepo). Strip the fixed-width hex suffix instead; best-effort only, since
|
|
146
181
|
# the true gem name isn't recoverable from the filesystem without running Bundler.
|
|
147
182
|
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
183
|
+
# Only for a directory no lockfile names: with one, {.locked_names_of} reads the name off the locked entry
|
|
184
|
+
# instead (issue #2978), since a name holding `-<digit>` (`foo-2fa-1.0`) truncates here to `foo`.
|
|
150
185
|
def self.gem_name_from_sig_path(sig_dir)
|
|
151
186
|
gem_dir = sig_dir.parent.basename.to_s
|
|
152
187
|
if git_sourced_layout?(sig_dir)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
class Environment
|
|
5
7
|
# Per-key memoization container for {Environment#constant_for_name}. Held by {Environment} so the
|
|
@@ -22,6 +24,9 @@ module Rigor
|
|
|
22
24
|
# reader path. If a future caller introduces a multi-threaded reader against one Environment, the
|
|
23
25
|
# synchronisation belongs at that caller's seam, not here.
|
|
24
26
|
class ConstantTypeCacheHolder
|
|
27
|
+
TypingState.memo_field(self, :@cache, reason: "keyed on the constant name; a pure function of the name for " \
|
|
28
|
+
"the environment's lifetime (Environment#constant_for_name)")
|
|
29
|
+
|
|
25
30
|
def initialize
|
|
26
31
|
@cache = {}
|
|
27
32
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "rbs_collection_discovery"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
class Environment
|
|
5
7
|
# Stdlib libraries that `Environment.for_project` loads on top of RBS core
|
|
@@ -32,5 +34,20 @@ module Rigor
|
|
|
32
34
|
strscan
|
|
33
35
|
prism rbs
|
|
34
36
|
].freeze
|
|
37
|
+
|
|
38
|
+
# The library list a run loads before its `RequiredFeatures` tokens: {DEFAULT_LIBRARIES}, then the
|
|
39
|
+
# configured `libraries:`, then — issue #2946 — the `source: { type: stdlib }` entries of the run's
|
|
40
|
+
# `rbs_collection.lock.yaml` ({RbsCollectionDiscovery.stdlib_library_names}), de-duplicated in that order.
|
|
41
|
+
# `Environment.for_project` and the boot-slimming probe's `RunCacheKey.libraries_config_entries` both build
|
|
42
|
+
# it here, so the cache key's `rbs.libraries` slot names the lockfile's libraries on both paths. A module of
|
|
43
|
+
# its own, not an `Environment` class method, so the plugin-facing `Environment` surface does not grow.
|
|
44
|
+
module BaseLibraries
|
|
45
|
+
def self.resolve(libraries, rbs_collection_lockfile:, rbs_collection_auto_detect:, root: Dir.pwd)
|
|
46
|
+
collection_stdlib = RbsCollectionDiscovery.stdlib_library_names(
|
|
47
|
+
lockfile_path: rbs_collection_lockfile, project_root: root, auto_detect: rbs_collection_auto_detect
|
|
48
|
+
)
|
|
49
|
+
(DEFAULT_LIBRARIES + libraries.map(&:to_s) + collection_stdlib).uniq
|
|
50
|
+
end
|
|
51
|
+
end
|
|
35
52
|
end
|
|
36
53
|
end
|