rigortype 0.4.4 → 0.4.6
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 +5 -5
- data/docs/handbook/11-sig-gen.md +100 -18
- data/docs/manual/02-cli-reference.md +85 -16
- data/docs/manual/03-configuration.md +12 -9
- data/docs/manual/04-diagnostics.md +17 -6
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +2 -1
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +17 -2
- data/docs/manual/12-caching.md +8 -1
- data/docs/manual/18-removing-dead-code.md +18 -7
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +30 -0
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +102 -1
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +30 -9
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +142 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +115 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +253 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +64 -4
- data/lib/rigor/analysis/check_rules.rb +319 -21
- data/lib/rigor/analysis/dependency_recorder.rb +11 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/edit_replay.rb +67 -0
- data/lib/rigor/analysis/incremental.rb +86 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +336 -46
- data/lib/rigor/analysis/path_expansion.rb +44 -1
- data/lib/rigor/analysis/reachability/graph.rb +90 -13
- data/lib/rigor/analysis/reachability/scan.rb +279 -39
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +22 -5
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +11 -3
- data/lib/rigor/analysis/runner/pool_coordinator.rb +82 -16
- data/lib/rigor/analysis/runner/project_pre_passes.rb +45 -3
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner.rb +222 -111
- data/lib/rigor/analysis/severity_stamp.rb +11 -0
- data/lib/rigor/analysis/worker_session.rb +24 -1
- data/lib/rigor/cache/descriptor.rb +40 -7
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +49 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/check_command.rb +93 -4
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/diff_command.rb +2 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/lens_command.rb +5 -5
- data/lib/rigor/cli/mcp_command.rb +2 -1
- data/lib/rigor/cli/options.rb +20 -3
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +7 -1
- data/lib/rigor/cli/plugins_renderer.rb +24 -2
- data/lib/rigor/cli/show_bleedingedge_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +73 -13
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/type_of_project_scope.rb +27 -0
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +72 -16
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +43 -7
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +136 -7
- data/lib/rigor/environment/required_features.rb +136 -29
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment.rb +4 -2
- data/lib/rigor/inference/block_call_timing.rb +7 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/callee_mutation_chain.rb +115 -0
- data/lib/rigor/inference/captured_locals.rb +44 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/deep_erasure.rb +138 -0
- data/lib/rigor/inference/def_return_typer.rb +159 -9
- data/lib/rigor/inference/definer_resolution.rb +101 -20
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +97 -18
- data/lib/rigor/inference/global_write_census.rb +36 -1
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/ivar_writer_census.rb +195 -0
- data/lib/rigor/inference/loop_value_record.rb +53 -0
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +278 -42
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
- data/lib/rigor/inference/method_dispatcher.rb +113 -19
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +149 -42
- data/lib/rigor/inference/mutation_widening.rb +5 -2
- data/lib/rigor/inference/narrowing.rb +116 -11
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +121 -0
- data/lib/rigor/inference/project_yielder.rb +128 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +6 -6
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +162 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +1144 -197
- data/lib/rigor/inference/self_call_ivar_writes.rb +29 -12
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +1088 -213
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +72 -12
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/language_server/buffer_table.rb +6 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +20 -6
- data/lib/rigor/language_server/project_context.rb +59 -3
- data/lib/rigor/language_server/server.rb +5 -3
- data/lib/rigor/lens/contract_reader.rb +45 -2
- data/lib/rigor/lens/document.rb +2 -1
- data/lib/rigor/lens/file_reader.rb +12 -2
- data/lib/rigor/lens/members.rb +16 -2
- data/lib/rigor/lens/run.rb +34 -7
- data/lib/rigor/mcp/server.rb +11 -3
- data/lib/rigor/plugin/base.rb +29 -13
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/plugin/manifest.rb +29 -3
- data/lib/rigor/plugin/registry.rb +38 -0
- data/lib/rigor/protection/discovery_seed.rb +64 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +36 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +59 -7
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +465 -49
- data/lib/rigor/sig_gen/layout_index.rb +5 -2
- data/lib/rigor/sig_gen/observation_collector.rb +141 -12
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +16 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +5 -3
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/combinator.rb +4 -3
- data/lib/rigor/type/constant.rb +54 -3
- data/lib/rigor/type/hash_shape.rb +14 -9
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +61 -44
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -2
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +3 -2
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +6 -0
- data/sig/rigor/plugin/additional_initializer.rbs +5 -5
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +10 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +34 -52
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +32 -1
data/lib/rigor/configuration.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "configuration/dependencies"
|
|
|
8
8
|
require_relative "configuration/severity_profile"
|
|
9
9
|
require_relative "config_path"
|
|
10
10
|
require_relative "configuration_error"
|
|
11
|
+
require_relative "rooted_glob"
|
|
11
12
|
require_relative "effects/entry_points"
|
|
12
13
|
require_relative "effects/label"
|
|
13
14
|
require_relative "effects/method_key"
|
|
@@ -320,12 +321,22 @@ module Rigor
|
|
|
320
321
|
|
|
321
322
|
# The test roots a run uses: the declared `test_paths:` as written (already resolved against the
|
|
322
323
|
# config file's directory by {.load}), or — when the key is unset — whichever of
|
|
323
|
-
# {CONVENTIONAL_TEST_PATHS} is a directory under
|
|
324
|
-
# the
|
|
325
|
-
|
|
324
|
+
# {CONVENTIONAL_TEST_PATHS} is a directory under a root, as absolute paths. The roots, unless `root` names
|
|
325
|
+
# one, are the directory of the config file in use, the one `paths:` and a declared `test_paths:` resolve
|
|
326
|
+
# against, and then the working directory, read only when the first holds neither. Auto-detecting against
|
|
327
|
+
# the working directory alone found nothing for a run from a subdirectory, or for the MCP server started
|
|
328
|
+
# elsewhere, though both name the config file (#2078); a config file kept in a subdirectory
|
|
329
|
+
# (`--config=config/rigor.yml`) still finds the project's `spec/` from the project root.
|
|
330
|
+
def resolved_test_paths(root: nil)
|
|
326
331
|
return test_paths unless test_paths.nil?
|
|
327
332
|
|
|
328
|
-
|
|
333
|
+
roots = root ? [root] : [exclude_base, Dir.pwd].uniq
|
|
334
|
+
roots.each do |dir|
|
|
335
|
+
found = CONVENTIONAL_TEST_PATHS.map { |test_dir| File.absolute_path(test_dir, dir) }
|
|
336
|
+
.select { File.directory?(it) }
|
|
337
|
+
return found unless found.empty?
|
|
338
|
+
end
|
|
339
|
+
[]
|
|
329
340
|
end
|
|
330
341
|
|
|
331
342
|
# Loads a configuration file.
|
|
@@ -995,9 +1006,34 @@ module Rigor
|
|
|
995
1006
|
# this entry" so a templated `**` pattern in a fresh project doesn't generate an error per match-less
|
|
996
1007
|
# pattern.
|
|
997
1008
|
def expand_pre_eval_entries(entries)
|
|
998
|
-
entries.flat_map
|
|
999
|
-
|
|
1000
|
-
|
|
1009
|
+
entries.flat_map { |entry| expand_pre_eval_entry(entry) }.uniq.freeze
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
# #1956 — a relative entry arrives joined onto the config file's directory, so the metacharacters the
|
|
1013
|
+
# check above sees may be that directory's own (`ds [x]/`). The leading run of segments that name existing
|
|
1014
|
+
# directories is read literally and only the rest is globbed under it: a literal entry under such a
|
|
1015
|
+
# directory stays a literal path, and a pattern matches what it matches under a plain directory.
|
|
1016
|
+
def expand_pre_eval_entry(entry)
|
|
1017
|
+
return [entry] unless glob_pattern?(entry)
|
|
1018
|
+
|
|
1019
|
+
base, pattern = literal_directory_prefix(entry)
|
|
1020
|
+
return [entry] unless glob_pattern?(pattern)
|
|
1021
|
+
|
|
1022
|
+
base ? RootedGlob.glob(base, pattern) : Dir.glob(entry, sort: true)
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
def literal_directory_prefix(entry)
|
|
1026
|
+
segments = entry.split("/")
|
|
1027
|
+
count = (1...segments.size).take_while { |n| File.directory?(join_segments(segments.take(n))) }.size
|
|
1028
|
+
return [nil, entry] if count.zero?
|
|
1029
|
+
|
|
1030
|
+
[join_segments(segments.take(count)), segments.drop(count).join("/")]
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
# `"/a".split("/")` leads with an empty segment, which spells the filesystem root.
|
|
1034
|
+
def join_segments(segments)
|
|
1035
|
+
path = segments.join("/")
|
|
1036
|
+
path.empty? ? "/" : path
|
|
1001
1037
|
end
|
|
1002
1038
|
|
|
1003
1039
|
def glob_pattern?(path)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../rooted_glob"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Effects
|
|
5
7
|
# Everything an effect envelope may be written in, as `[buffer name, RBS source]` pairs
|
|
@@ -55,7 +57,7 @@ module Rigor
|
|
|
55
57
|
# @param signature_paths — the configured roots; empty / nil takes the default.
|
|
56
58
|
# @param virtual_rbs — the loader's virtual entries.
|
|
57
59
|
def collect(signature_paths:, virtual_rbs: nil)
|
|
58
|
-
files = roots(signature_paths).flat_map { |root|
|
|
60
|
+
files = roots(signature_paths).flat_map { |root| RootedGlob.glob(root, "**/*.rbs").sort }
|
|
59
61
|
sources = files.filter_map do |path|
|
|
60
62
|
[path, File.read(path)]
|
|
61
63
|
rescue StandardError
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
|
|
5
5
|
require_relative "../config_path"
|
|
6
|
+
require_relative "../rooted_glob"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
class Environment
|
|
@@ -91,8 +92,8 @@ module Rigor
|
|
|
91
92
|
# all (`Bundler::Source::Path#path` is the user's own `path:` directory, used in place), so there is
|
|
92
93
|
# no bundle-relative location to walk. A `path:`-sourced gem's `sig/` is only reachable by adding it
|
|
93
94
|
# directly to the project's `signature_paths:` — which already works today and needs no discovery.
|
|
94
|
-
all = (
|
|
95
|
-
|
|
95
|
+
all = (RootedGlob.glob(resolved, "ruby/*/gems/*/sig") +
|
|
96
|
+
RootedGlob.glob(resolved, "ruby/*/bundler/gems/*/sig")).map { |d| Pathname.new(d) }
|
|
96
97
|
filtered = all.reject { |sig_dir| skip_gems.include?(gem_name_from_sig_path(sig_dir)) }
|
|
97
98
|
return filtered if locked_gems.nil? || locked_gems.empty?
|
|
98
99
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rbs"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class Environment
|
|
@@ -15,7 +16,7 @@ module Rigor
|
|
|
15
16
|
# @param dir — the vendored directory (Pathname).
|
|
16
17
|
# @return the absolute-less names (`"Prime::PseudoPrimeGenerator"`) of every class and module it declares.
|
|
17
18
|
def type_names(dir)
|
|
18
|
-
|
|
19
|
+
RootedGlob.glob(dir, "*.rbs").flat_map do |file|
|
|
19
20
|
_, _, decls = RBS::Parser.parse_signature(File.read(file))
|
|
20
21
|
names(decls, nil)
|
|
21
22
|
rescue RBS::BaseError, SystemCallError
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rbs"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class Environment
|
|
@@ -132,7 +133,7 @@ module Rigor
|
|
|
132
133
|
end
|
|
133
134
|
|
|
134
135
|
def signature_files(roots)
|
|
135
|
-
roots.flat_map { |root|
|
|
136
|
+
roots.flat_map { |root| RootedGlob.glob(root, "*.rbs").sort }
|
|
136
137
|
end
|
|
137
138
|
|
|
138
139
|
def shell_buffer
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "lockfile_resolver"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class Environment
|
|
@@ -47,7 +48,7 @@ module Rigor
|
|
|
47
48
|
base = Pathname.new(bundle_path)
|
|
48
49
|
return {} unless base.directory?
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
RootedGlob.glob(base, "ruby/*/gems/*").each_with_object({}) do |dir, acc|
|
|
51
52
|
name, version, platform = split_install_dirname(File.basename(dir))
|
|
52
53
|
next unless name
|
|
53
54
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class Environment
|
|
@@ -71,7 +72,7 @@ module Rigor
|
|
|
71
72
|
base = Pathname.new(bundle_path)
|
|
72
73
|
return {} unless base.directory?
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
RootedGlob.glob(base, "ruby/*/gems/*").each_with_object({}) do |dir, acc|
|
|
75
76
|
acc[File.basename(dir)] ||= dir
|
|
76
77
|
end
|
|
77
78
|
end
|
|
@@ -114,7 +115,7 @@ module Rigor
|
|
|
114
115
|
base = Pathname.new(bundle_path)
|
|
115
116
|
return {} unless base.directory?
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
RootedGlob.glob(base, "ruby/*/bundler/gems/*").each_with_object({}) do |dir, acc|
|
|
118
119
|
name = gemspec_gem_name(dir)
|
|
119
120
|
acc[name] ||= dir if name
|
|
120
121
|
end
|
|
@@ -124,7 +125,7 @@ module Rigor
|
|
|
124
125
|
# exactly one. Bundler requires a git-sourced gem to ship one, so "not exactly one" means this is not
|
|
125
126
|
# the shape we think it is — and a guess from the directory basename is what this exists to avoid.
|
|
126
127
|
def gemspec_gem_name(dir)
|
|
127
|
-
specs =
|
|
128
|
+
specs = RootedGlob.glob(dir, "*.gemspec")
|
|
128
129
|
return nil unless specs.size == 1
|
|
129
130
|
|
|
130
131
|
File.basename(specs.first, ".gemspec")
|
|
@@ -10,6 +10,8 @@ require_relative "member_consistency"
|
|
|
10
10
|
require_relative "required_features"
|
|
11
11
|
require_relative "gated_signature_guard"
|
|
12
12
|
require_relative "gated_signature_plan"
|
|
13
|
+
require_relative "ruby_library_roots"
|
|
14
|
+
require_relative "../rooted_glob"
|
|
13
15
|
|
|
14
16
|
module Rigor
|
|
15
17
|
class Environment
|
|
@@ -549,7 +551,7 @@ module Rigor
|
|
|
549
551
|
path = Pathname(path) unless path.is_a?(Pathname)
|
|
550
552
|
next [] unless path.directory?
|
|
551
553
|
|
|
552
|
-
|
|
554
|
+
RootedGlob.glob(path, "**/*.rbs").map { |p| File.expand_path(p) }
|
|
553
555
|
end.to_set
|
|
554
556
|
end
|
|
555
557
|
|
|
@@ -1652,9 +1654,10 @@ module Rigor
|
|
|
1652
1654
|
# arity. Read back by the env-cache producer, which rebuilds the env from the loader's readers on
|
|
1653
1655
|
# a miss — a list it cannot read is a stand-down that runs only on a `--no-cache` build.
|
|
1654
1656
|
def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: [],
|
|
1655
|
-
deferred_signature_paths: [])
|
|
1657
|
+
deferred_signature_paths: [], project_signature_paths: [], required_features: [])
|
|
1656
1658
|
@libraries = libraries.map(&:to_s).freeze
|
|
1657
1659
|
@signature_paths = signature_paths.map { |p| Pathname(p) }.freeze
|
|
1660
|
+
assign_reopen_inputs(project_signature_paths, required_features)
|
|
1658
1661
|
# Issue #610 — the subset of `signature_paths:` a bundled PLUGIN contributed. Loaded last and
|
|
1659
1662
|
# allowed to stand down against a colliding generic arity ({.add_deferred_signatures}). Every entry
|
|
1660
1663
|
# is also in `@signature_paths`, so the env cache descriptor already digests these files.
|
|
@@ -1763,6 +1766,9 @@ module Rigor
|
|
|
1763
1766
|
# a partial sidecar as authoritative made `sig-gen --write` on redmine report 5.9x more
|
|
1764
1767
|
# `call.undefined-method` than the same project with no `sig/` at all.
|
|
1765
1768
|
#
|
|
1769
|
+
# A class declared only by RBS synthesized from the project's own source joins the set too
|
|
1770
|
+
# ({#inline_only_entry?}, issue #1934).
|
|
1771
|
+
#
|
|
1766
1772
|
# Memoised per loader: one pass over `class_decls` (~1-2k entries), and every consumer is a check
|
|
1767
1773
|
# rule on the analysis hot path. Attribution is by buffer NAME, which survives the ADR-54 environment
|
|
1768
1774
|
# cache since #725 — an environment blob written before that carries the `<cached>` sentinel instead,
|
|
@@ -1776,10 +1782,14 @@ module Rigor
|
|
|
1776
1782
|
return Set.new.freeze if environment.nil?
|
|
1777
1783
|
|
|
1778
1784
|
project_files = self.class.project_sig_files(@signature_paths)
|
|
1779
|
-
|
|
1785
|
+
virtual_names = @virtual_rbs.to_set(&:first)
|
|
1786
|
+
return Set.new.freeze if project_files.empty? && virtual_names.empty?
|
|
1780
1787
|
|
|
1781
1788
|
names = environment.class_decls.each_with_object(Set.new) do |(rbs_name, entry), acc|
|
|
1782
|
-
|
|
1789
|
+
next unless self.class.project_entry?(entry, project_files) ||
|
|
1790
|
+
inline_only_entry?(entry, virtual_names, project_files)
|
|
1791
|
+
|
|
1792
|
+
acc << rbs_name.to_s.delete_prefix("::")
|
|
1783
1793
|
end
|
|
1784
1794
|
names.freeze
|
|
1785
1795
|
rescue ::RBS::BaseError
|
|
@@ -1787,6 +1797,92 @@ module Rigor
|
|
|
1787
1797
|
end
|
|
1788
1798
|
private :build_project_declared_classes
|
|
1789
1799
|
|
|
1800
|
+
# Issue #1934 — a class whose every declaration a source RBS synthesizer (`rigor-rbs-inline`) wrote from
|
|
1801
|
+
# the project's own `.rb` files is the project's as much as a `sig/` sidecar is: the declaration is the
|
|
1802
|
+
# inline comments of the source under analysis, so a method another project file adds to the class (a
|
|
1803
|
+
# plain `def`, or an `alias_method` whose target the inline declaration names) is the second file of an
|
|
1804
|
+
# ordinary class, not an ADR-17 monkey-patch. Reading the synthesized RBS as bundled reported
|
|
1805
|
+
# `alias_method :title, :label` from another file while the `alias` keyword, which upstream translates,
|
|
1806
|
+
# read clean.
|
|
1807
|
+
#
|
|
1808
|
+
# EVERY declaration, not only the primary one, and only virtual or `sig/` buffers: a core, stdlib or gem
|
|
1809
|
+
# class an inline-annotated file reopens (`class String` with a `#:` method) keeps its bundled
|
|
1810
|
+
# declaration, and a project `def` on it stays the monkey-patch ADR-17 reports. A declaration whose
|
|
1811
|
+
# buffer cannot be attributed (the pre-#725 `<cached>` sentinel) fails the test, which keeps the
|
|
1812
|
+
# pre-#1934 reading.
|
|
1813
|
+
def inline_only_entry?(entry, virtual_names, project_files)
|
|
1814
|
+
return false if virtual_names.empty?
|
|
1815
|
+
|
|
1816
|
+
project_authored_entry?(entry, virtual_names, project_files)
|
|
1817
|
+
end
|
|
1818
|
+
private :inline_only_entry?
|
|
1819
|
+
|
|
1820
|
+
# Every declaration of `entry` sits in a project `sig/` file or in RBS synthesized from the project's
|
|
1821
|
+
# source; a declaration whose buffer cannot be attributed fails the test.
|
|
1822
|
+
def project_authored_entry?(entry, virtual_names, project_files)
|
|
1823
|
+
buffers = self.class.entry_declarations(entry).map { |decl| self.class.declaration_buffer_name(decl) }
|
|
1824
|
+
return false if buffers.empty? || buffers.any?(&:nil?)
|
|
1825
|
+
|
|
1826
|
+
buffers.all? { |name| virtual_names.include?(name) || project_files.include?(File.absolute_path(name)) }
|
|
1827
|
+
end
|
|
1828
|
+
private :project_authored_entry?
|
|
1829
|
+
|
|
1830
|
+
# Issue #2174 — true when every RBS declaration of `class_name` is the project's own (rbs-inline `#:`
|
|
1831
|
+
# annotations, or a file under the configured `signature_paths:`) and its root constant belongs to a library
|
|
1832
|
+
# Ruby ships ({RubyLibraryRoots}) that the project's source requires ({RequiredFeatures.features_of}). Such
|
|
1833
|
+
# a declaration reopens a library class whose real signatures did not load (`class Matrix` with one
|
|
1834
|
+
# annotated method beside `require "matrix"`, and no `matrix` RBS), so it lists only the methods the
|
|
1835
|
+
# project added: the class's surface is not enumerable from it, and a call it omits is not evidence of an
|
|
1836
|
+
# undefined method. A library whose RBS did load contributes a declaration of its own (core, stdlib, a gem's
|
|
1837
|
+
# `sig/`, an rbs collection, a plugin's `sig/`), which takes the class out of this predicate. A project
|
|
1838
|
+
# class that shares a required library's root name declines too; that is the accepted cost.
|
|
1839
|
+
def ruby_library_reopen?(class_name)
|
|
1840
|
+
name = class_name.to_s.delete_prefix("::")
|
|
1841
|
+
return false if @required_features.empty? || !project_authored_classes.include?(name)
|
|
1842
|
+
|
|
1843
|
+
segments = ruby_library_roots[name.split("::").first]
|
|
1844
|
+
!segments.nil? && @required_features.any? { |feature| segments.include?(feature.split("/").first) }
|
|
1845
|
+
end
|
|
1846
|
+
|
|
1847
|
+
def ruby_library_roots
|
|
1848
|
+
@state[:ruby_library_roots] ||= RubyLibraryRoots.shared
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
# Issue #2174 — the subset of `signature_paths:` the user configured (every entry is also in
|
|
1852
|
+
# `@signature_paths`), and the features the project's source requires: read only by
|
|
1853
|
+
# {#ruby_library_reopen?}.
|
|
1854
|
+
def assign_reopen_inputs(project_signature_paths, required_features)
|
|
1855
|
+
@project_signature_paths = project_signature_paths.map { |p| Pathname(p) }.freeze
|
|
1856
|
+
@required_features = required_features.map { |feature| feature.to_s.dup.freeze }.freeze
|
|
1857
|
+
end
|
|
1858
|
+
private :assign_reopen_inputs
|
|
1859
|
+
private :ruby_library_roots
|
|
1860
|
+
|
|
1861
|
+
# The class / module names every one of whose declarations is the project's own
|
|
1862
|
+
# ({#project_authored_entry?}, which {#inline_only_entry?} asks behind its inline-only gate).
|
|
1863
|
+
def project_authored_classes
|
|
1864
|
+
@state[:project_authored_classes] ||= build_project_authored_classes
|
|
1865
|
+
end
|
|
1866
|
+
private :project_authored_classes
|
|
1867
|
+
|
|
1868
|
+
def build_project_authored_classes
|
|
1869
|
+
environment = env
|
|
1870
|
+
return Set.new.freeze if environment.nil?
|
|
1871
|
+
|
|
1872
|
+
# Only the user's configured `signature_paths:`: `@signature_paths` also holds plugin, gem-shipped,
|
|
1873
|
+
# rbs-collection and overlay directories, whose declarations are a library's own.
|
|
1874
|
+
project_files = self.class.project_sig_files(@project_signature_paths)
|
|
1875
|
+
virtual_names = @virtual_rbs.to_set(&:first)
|
|
1876
|
+
return Set.new.freeze if project_files.empty? && virtual_names.empty?
|
|
1877
|
+
|
|
1878
|
+
environment.class_decls.each_with_object(Set.new) do |(rbs_name, entry), acc|
|
|
1879
|
+
acc << rbs_name.to_s.delete_prefix("::") if project_authored_entry?(entry, virtual_names, project_files)
|
|
1880
|
+
end.freeze
|
|
1881
|
+
rescue ::RBS::BaseError
|
|
1882
|
+
Set.new.freeze
|
|
1883
|
+
end
|
|
1884
|
+
private :build_project_authored_classes
|
|
1885
|
+
|
|
1790
1886
|
# Issue #527 — the class / module names whose PRIMARY declaration lives in the `rbs` gem's own
|
|
1791
1887
|
# `core/` or `stdlib/` tree, top-level prefix stripped.
|
|
1792
1888
|
#
|
|
@@ -1807,9 +1903,23 @@ module Rigor
|
|
|
1807
1903
|
@state[:core_stdlib_classes] ||= build_core_stdlib_classes
|
|
1808
1904
|
end
|
|
1809
1905
|
|
|
1810
|
-
|
|
1906
|
+
# Issue #1154 — the subset of {#core_stdlib_classes} whose primary declaration is in `core/` alone: the classes
|
|
1907
|
+
# the interpreter implements, as opposed to a stdlib library written in Ruby (`Logger` rewrites `@level` in
|
|
1908
|
+
# its `initialize`). Same attribution, same cache behaviour.
|
|
1909
|
+
def core_class?(class_name)
|
|
1910
|
+
(@state[:core_classes] ||= build_core_stdlib_classes(CORE_ROOTS)).include?(class_name.to_s.delete_prefix("::"))
|
|
1911
|
+
end
|
|
1912
|
+
|
|
1913
|
+
CORE_ROOTS = begin
|
|
1914
|
+
[::RBS::EnvironmentLoader::DEFAULT_CORE_ROOT].compact.map { |root| "#{root}/".freeze }.freeze
|
|
1915
|
+
rescue StandardError
|
|
1916
|
+
[].freeze
|
|
1917
|
+
end
|
|
1918
|
+
private_constant :CORE_ROOTS
|
|
1919
|
+
|
|
1920
|
+
def build_core_stdlib_classes(roots = CORE_STDLIB_ROOTS)
|
|
1811
1921
|
environment = env
|
|
1812
|
-
return Set.new.freeze if environment.nil? ||
|
|
1922
|
+
return Set.new.freeze if environment.nil? || roots.empty?
|
|
1813
1923
|
|
|
1814
1924
|
names = environment.class_decls.each_with_object(Set.new) do |(rbs_name, entry), acc|
|
|
1815
1925
|
decl = self.class.primary_decl_for(entry)
|
|
@@ -1818,7 +1928,7 @@ module Rigor
|
|
|
1818
1928
|
path = self.class.declaration_buffer_name(decl)
|
|
1819
1929
|
next if path.nil?
|
|
1820
1930
|
|
|
1821
|
-
acc << rbs_name.to_s.delete_prefix("::") if
|
|
1931
|
+
acc << rbs_name.to_s.delete_prefix("::") if roots.any? { |root| path.start_with?(root) }
|
|
1822
1932
|
end
|
|
1823
1933
|
names.freeze
|
|
1824
1934
|
rescue ::RBS::BaseError
|
|
@@ -1916,6 +2026,25 @@ module Rigor
|
|
|
1916
2026
|
(@state[:definition_build_failures] || []).dup.freeze
|
|
1917
2027
|
end
|
|
1918
2028
|
|
|
2029
|
+
# Issue #1929 — re-ask for `class_name`'s definition on behalf of a PREVIOUS run's analysis, which
|
|
2030
|
+
# demanded it and found it failing. An `--incremental` recheck replays that run's failures, and one
|
|
2031
|
+
# whose conflicting buffer is a file the recheck re-synthesized cannot be replayed as it stands (the
|
|
2032
|
+
# edit may have fixed it), while the file that demanded the class may sit outside the recheck's
|
|
2033
|
+
# closure and never ask again. Re-asking here re-derives the answer over the current signatures: a
|
|
2034
|
+
# class that still fails is reported with its current tuple, one that now builds is not.
|
|
2035
|
+
#
|
|
2036
|
+
# A real demand, not {#during_internal_demand}: it stands in for the analysis's own, which is what
|
|
2037
|
+
# #696 binds the reported set to. Both sides are asked, since the replayed tuple does not record which
|
|
2038
|
+
# side the analysis demanded ({#report_definition_build_failure} builds both anyway).
|
|
2039
|
+
#
|
|
2040
|
+
# @return the class's current tuple, or nil when it now builds.
|
|
2041
|
+
def redemand_definition(class_name)
|
|
2042
|
+
key = class_name.to_s.delete_prefix("::")
|
|
2043
|
+
instance_definition(key)
|
|
2044
|
+
singleton_definition(key)
|
|
2045
|
+
definition_build_failures.find { |failure| failure.first == key }
|
|
2046
|
+
end
|
|
2047
|
+
|
|
1919
2048
|
# Virtual (inline-synthesized) contributions dropped by the collision quarantine
|
|
1920
2049
|
# ({.resolve_quarantining_virtual_collisions}): buffer names absent from the built env even though the
|
|
1921
2050
|
# entry's content is non-empty and parses (a parse failure is the synthesizer's own WD6 skip, reported
|