rigortype 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -121,7 +121,7 @@ module Rigor
|
|
|
121
121
|
# syntax: this is the argument a shell just tab-completed, and `rigor effects app/models` meaning
|
|
122
122
|
# "that directory" is the only reading a reader would guess.
|
|
123
123
|
def self.normalize_scope(scope)
|
|
124
|
-
Array(scope).map { |path| File.
|
|
124
|
+
Array(scope).map { |path| File.absolute_path(path.to_s.chomp("/")) }.freeze
|
|
125
125
|
end
|
|
126
126
|
private_class_method :normalize_scope
|
|
127
127
|
|
|
@@ -130,7 +130,7 @@ module Rigor
|
|
|
130
130
|
return false if paths.nil? || paths.empty?
|
|
131
131
|
|
|
132
132
|
paths.any? do |path|
|
|
133
|
-
absolute = File.
|
|
133
|
+
absolute = File.absolute_path(path)
|
|
134
134
|
roots.any? { |root| absolute == root || absolute.start_with?("#{root}/") }
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -69,7 +69,12 @@ module Rigor
|
|
|
69
69
|
def execute(file:, options:)
|
|
70
70
|
configuration = Configuration.load(options.fetch(:config))
|
|
71
71
|
source = File.read(file, encoding: Encoding::UTF_8)
|
|
72
|
+
# Prism parses the ORIGINAL bytes, so a `# -*- coding: -*-` file is read in its own encoding. Only the text the
|
|
73
|
+
# renderer sees is scrubbed: the colouriser and line splitter raise on a byte invalid in UTF-8 (a comment,
|
|
74
|
+
# `__END__` data, a non-UTF-8 file).
|
|
75
|
+
# Scrubbing is byte-for-byte (`?` per bad byte), so Prism's positions still address it.
|
|
72
76
|
parse_result = Prism.parse(source, filepath: file, version: configuration.target_ruby)
|
|
77
|
+
source = source.scrub { |bytes| "?" * bytes.bytesize } unless source.valid_encoding?
|
|
73
78
|
return 1 if parse_errors?(parse_result, file)
|
|
74
79
|
|
|
75
80
|
events = record_events(parse_result.value, file, configuration)
|
|
@@ -87,7 +87,7 @@ module Rigor
|
|
|
87
87
|
# file is parsed and scope-indexed once, however many positions land in it.
|
|
88
88
|
def execute(targets:, options:, buffer: nil)
|
|
89
89
|
configuration = Configuration.load(options.fetch(:config))
|
|
90
|
-
environment = project_environment(targets.map(&:file).uniq, configuration)
|
|
90
|
+
environment = project_environment(targets.map(&:file).uniq, configuration, buffer)
|
|
91
91
|
base_scope = Scope.empty(environment: environment)
|
|
92
92
|
|
|
93
93
|
results_by_target = Array.new(targets.length)
|
|
@@ -218,8 +218,10 @@ module Rigor
|
|
|
218
218
|
# closes). The probed file is threaded as the synthesizer's `source_files:`. Project-RBS auto-detection
|
|
219
219
|
# roots at CWD today; future work will walk parent directories to find the enclosing `Gemfile`/`*.gemspec`
|
|
220
220
|
# so probes against files outside the current process's CWD still see the right `sig/` tree.
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
# Issue #1700 — an editor buffer is scanned for required features in place of the file it stands for.
|
|
222
|
+
def project_environment(files, configuration, buffer = nil)
|
|
223
|
+
ProjectEnvironment.build(configuration: configuration, source_files: files,
|
|
224
|
+
feature_sources: ProjectEnvironment.buffer_sources(buffer))
|
|
223
225
|
end
|
|
224
226
|
|
|
225
227
|
def file_exists?(file)
|
|
@@ -51,7 +51,7 @@ module Rigor
|
|
|
51
51
|
paths = @argv.empty? ? configuration.paths : @argv
|
|
52
52
|
scan_cache = Analysis::Reachability::ScanCache.open(configuration.cache_path,
|
|
53
53
|
target_ruby: configuration.target_ruby)
|
|
54
|
-
declarations, references, dynamic_uses = scan(paths, configuration, scan_cache)
|
|
54
|
+
declarations, references, dynamic_uses, shadows = scan(paths, configuration, scan_cache)
|
|
55
55
|
references.concat(signature_references(configuration))
|
|
56
56
|
dynamic_uses.concat(template_mentions(declarations, scan_cache))
|
|
57
57
|
|
|
@@ -59,7 +59,8 @@ module Rigor
|
|
|
59
59
|
cache_store: cache_store(configuration))
|
|
60
60
|
references.concat(plugin_references(contribution.references))
|
|
61
61
|
graph = build_graph(configuration, options, contribution,
|
|
62
|
-
declarations: declarations, references: references, dynamic_uses: dynamic_uses
|
|
62
|
+
declarations: declarations, references: references, dynamic_uses: dynamic_uses,
|
|
63
|
+
shadows: shadows)
|
|
63
64
|
emit(graph.report, options, supply: root_supply(contribution.roots, declarations))
|
|
64
65
|
scan_cache.save
|
|
65
66
|
0
|
|
@@ -67,9 +68,9 @@ module Rigor
|
|
|
67
68
|
|
|
68
69
|
private
|
|
69
70
|
|
|
70
|
-
def build_graph(configuration, options, contribution, declarations:, references:, dynamic_uses:)
|
|
71
|
+
def build_graph(configuration, options, contribution, declarations:, references:, dynamic_uses:, shadows:)
|
|
71
72
|
Analysis::Reachability::Graph.new(
|
|
72
|
-
declarations: declarations, references: references, dynamic_uses: dynamic_uses,
|
|
73
|
+
declarations: declarations, references: references, dynamic_uses: dynamic_uses, shadows: shadows,
|
|
73
74
|
root_fqns: root_fqns(declarations, options.fetch(:entry_points)) + contribution.roots,
|
|
74
75
|
foreign: foreign_predicate(configuration)
|
|
75
76
|
)
|
|
@@ -106,22 +107,25 @@ module Rigor
|
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
# Declarations come from the analysed paths; references additionally from the wider corpus (WD7).
|
|
110
|
+
# A wider-corpus file's own declarations come back as shadows: never nodes, only what that file's own
|
|
111
|
+
# references resolve to first (#1732).
|
|
109
112
|
# Every file is consulted every run — an unchanged one contributes its cached scan, so the
|
|
110
113
|
# whole-project completeness the `--incremental` refusal protects is unaffected.
|
|
111
114
|
def scan(paths, configuration, cache)
|
|
112
115
|
declaration_files = Analysis::PathExpansion.ruby_files(paths, configuration.exclude_patterns).to_set
|
|
113
116
|
declarations = []
|
|
117
|
+
shadows = []
|
|
114
118
|
references = []
|
|
115
119
|
dynamic_uses = []
|
|
116
120
|
(declaration_files + reference_files(paths, configuration)).sort.each do |file|
|
|
117
121
|
result = cache.serve(:scan, file) { read_and_scan(file, configuration) }
|
|
118
122
|
next if result.nil?
|
|
119
123
|
|
|
120
|
-
declarations.concat(result.declarations)
|
|
124
|
+
(declaration_files.include?(file) ? declarations : shadows).concat(result.declarations)
|
|
121
125
|
references.concat(result.references)
|
|
122
126
|
dynamic_uses.concat(result.dynamic_uses)
|
|
123
127
|
end
|
|
124
|
-
[declarations, references, dynamic_uses]
|
|
128
|
+
[declarations, references, dynamic_uses, shadows]
|
|
125
129
|
end
|
|
126
130
|
|
|
127
131
|
# The reference corpus is the PROJECT, not the analysed paths. A constant declared in `lib/` is commonly
|
|
@@ -131,13 +135,14 @@ module Rigor
|
|
|
131
135
|
# ADR-102 WD2), which is exactly why the two corpora are separated rather than both widened.
|
|
132
136
|
def reference_files(_paths, configuration)
|
|
133
137
|
relative = Analysis::Reachability::ProjectFiles.own(Dir.glob(REFERENCE_GLOB, base: Dir.pwd), Dir.pwd)
|
|
134
|
-
absolute = relative.map { |rel| File.
|
|
138
|
+
absolute = relative.map { |rel| File.absolute_path(rel) }
|
|
135
139
|
Analysis::PathExpansion.reject_excluded(absolute, configuration.exclude_patterns).to_set
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
def read_and_scan(file, configuration)
|
|
139
143
|
Analysis::Reachability::Scan.call(path: file, source: File.read(file),
|
|
140
|
-
target_ruby: configuration.target_ruby
|
|
144
|
+
target_ruby: configuration.target_ruby,
|
|
145
|
+
root: File.absolute_path(Dir.pwd))
|
|
141
146
|
rescue SystemCallError
|
|
142
147
|
nil
|
|
143
148
|
end
|
|
@@ -177,7 +182,7 @@ module Rigor
|
|
|
177
182
|
return [] if names.empty?
|
|
178
183
|
|
|
179
184
|
Analysis::Reachability::ProjectFiles.own(Dir.glob(TEMPLATE_GLOB, base: Dir.pwd), Dir.pwd).flat_map do |rel|
|
|
180
|
-
absolute = File.
|
|
185
|
+
absolute = File.absolute_path(rel)
|
|
181
186
|
haystack = cache.serve(:runs, absolute) { constant_bearing_text(File.read(absolute).scrub) }
|
|
182
187
|
names.filter_map do |fqn|
|
|
183
188
|
next unless haystack.include?(fqn)
|
|
@@ -208,7 +213,7 @@ module Rigor
|
|
|
208
213
|
return [] if globs.empty?
|
|
209
214
|
|
|
210
215
|
matcher = Analysis::Reachability::ProjectFiles.method(:entry_point_match?)
|
|
211
|
-
cwd = "#{File.
|
|
216
|
+
cwd = "#{File.absolute_path(Dir.pwd)}/"
|
|
212
217
|
declarations.filter_map do |d|
|
|
213
218
|
relative = d.path.delete_prefix(cwd)
|
|
214
219
|
d.fqn if globs.any? { |g| matcher.call(g, d.path) || matcher.call(g, relative) }
|
|
@@ -235,6 +240,7 @@ module Rigor
|
|
|
235
240
|
def foreign_predicate(configuration)
|
|
236
241
|
env = Environment.for_project(libraries: configuration.libraries, signature_paths: [],
|
|
237
242
|
cache_store: cache_store(configuration),
|
|
243
|
+
required_features: ProjectEnvironment.required_features(configuration),
|
|
238
244
|
**ProjectEnvironment.dependency_discovery_options(configuration))
|
|
239
245
|
->(fqn) { !env.singleton_for_name(fqn).nil? }
|
|
240
246
|
rescue StandardError
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -281,6 +281,10 @@ module Rigor
|
|
|
281
281
|
# tempfile, uri, logger, date, prism, rbs). Adding a
|
|
282
282
|
# `sig/<gem>.rbs` file under `sig/` is the simplest way
|
|
283
283
|
# to extend type coverage today.
|
|
284
|
+
#
|
|
285
|
+
# `target_ruby` is left commented: the default only picks the syntax Rigor parses. Set it to the lowest
|
|
286
|
+
# Ruby your project runs on, and Rigor also reports what that Ruby deprecates.
|
|
287
|
+
# target_ruby: #{Configuration::DEFAULTS.fetch('target_ruby').dump}
|
|
284
288
|
#{YAML.dump(init_settings(path)).sub(/\A---\n/, '')}
|
|
285
289
|
YAML
|
|
286
290
|
end
|
|
@@ -288,13 +292,17 @@ module Rigor
|
|
|
288
292
|
# The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
|
|
289
293
|
# the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
|
|
290
294
|
# relative to the config file's directory, which is what {Configuration.load} resolves them against.
|
|
295
|
+
#
|
|
296
|
+
# `target_ruby` is left out (the template writes it commented): a written key is a statement about the runtime
|
|
297
|
+
# (#1692), and the starter must not make one the user never chose.
|
|
291
298
|
def init_settings(path)
|
|
299
|
+
settings = Configuration::DEFAULTS.except("target_ruby")
|
|
292
300
|
test_paths = Configuration.new.resolved_test_paths
|
|
293
|
-
return
|
|
301
|
+
return settings if test_paths.empty?
|
|
294
302
|
|
|
295
|
-
config_dir = Pathname(File.
|
|
303
|
+
config_dir = Pathname(File.absolute_path(File.dirname(path)))
|
|
296
304
|
relative = test_paths.map { |root| Pathname(root).relative_path_from(config_dir).to_s }
|
|
297
|
-
|
|
305
|
+
settings.merge("test_paths" => relative)
|
|
298
306
|
end
|
|
299
307
|
|
|
300
308
|
def run_annotate
|
data/lib/rigor/config_audit.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "did_you_mean"
|
|
4
4
|
|
|
5
|
+
require_relative "config_path"
|
|
5
6
|
require_relative "signature_path_audit"
|
|
6
7
|
# ADR-87 WD4 — only the pure rule-id table (`ALL_RULES` / `RULE_FAMILIES`) is needed, so require the light
|
|
7
8
|
# `rule_ids.rb` rather than the engine-pulling `check_rules.rb`; keeps `config_audit` (loaded by every check)
|
|
@@ -199,14 +200,14 @@ module Rigor
|
|
|
199
200
|
end
|
|
200
201
|
|
|
201
202
|
def self.add_missing_dir(warnings, path, project_root, kind, key)
|
|
202
|
-
return if path.nil? || File.directory?(
|
|
203
|
+
return if path.nil? || File.directory?(ConfigPath.absolute(path, project_root))
|
|
203
204
|
|
|
204
205
|
warnings << Warning.new(kind: kind, message: "#{key}: #{path.inspect} is not a directory",
|
|
205
206
|
fields: { "path" => path })
|
|
206
207
|
end
|
|
207
208
|
|
|
208
209
|
def self.add_missing_file(warnings, path, project_root, kind, key)
|
|
209
|
-
return if path.nil? || File.file?(
|
|
210
|
+
return if path.nil? || File.file?(ConfigPath.absolute(path, project_root))
|
|
210
211
|
|
|
211
212
|
warnings << Warning.new(kind: kind, message: "#{key}: #{path.inspect} does not exist", fields: { "path" => path })
|
|
212
213
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# Resolves a path-valued setting that a person wrote into `.rigor.yml`, or that Bundler wrote into its
|
|
5
|
+
# own config (`BUNDLE_PATH: "~/gems"`), to an absolute path.
|
|
6
|
+
#
|
|
7
|
+
# A leading `~/` and a bare `~` expand to the invoking user's home directory — exactly what
|
|
8
|
+
# `File.expand_path` does for those two spellings, and what Bundler itself does for `BUNDLE_PATH`. Every
|
|
9
|
+
# other spelling goes through `File.absolute_path`, so `~drafts/x` stays a directory named `~drafts`
|
|
10
|
+
# instead of `File.expand_path`'s home-of-user-`drafts` reading, which raises `ArgumentError` for a user
|
|
11
|
+
# that does not exist (#1510). It never raises on a `~`.
|
|
12
|
+
#
|
|
13
|
+
# Paths the analysis is *given to analyse* (CLI path arguments, template units, the runner's file sets)
|
|
14
|
+
# do not use this: the shell has already expanded a `~` there, so a surviving one is a directory name.
|
|
15
|
+
module ConfigPath
|
|
16
|
+
HOME_PREFIX = %r{\A~(?=/|\z)}
|
|
17
|
+
|
|
18
|
+
# `base_dir` anchors a relative result and defaults to the working directory.
|
|
19
|
+
def self.absolute(path, base_dir = nil)
|
|
20
|
+
text = path.to_s
|
|
21
|
+
return File.absolute_path(text, base_dir) unless text.match?(HOME_PREFIX)
|
|
22
|
+
|
|
23
|
+
home = Dir.home
|
|
24
|
+
File.absolute_path(text.sub(HOME_PREFIX, home), base_dir)
|
|
25
|
+
rescue ArgumentError
|
|
26
|
+
File.absolute_path(text, base_dir)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -38,6 +38,10 @@ module Rigor
|
|
|
38
38
|
"call.argument-type-mismatch" => :warning,
|
|
39
39
|
"call.possible-nil-receiver" => :warning,
|
|
40
40
|
"call.raise-non-exception" => :warning,
|
|
41
|
+
# Issue #1692 — the code runs on the stated Ruby (it warns, and breaks only at the removal version), so the
|
|
42
|
+
# rule takes the slot of the rules that report working-but-wrong code: `flow.unreachable-branch`'s
|
|
43
|
+
# info / warning / error. It runs only under an explicit `target_ruby` of 4.1 or later.
|
|
44
|
+
"call.deprecated-ruby2-keywords" => :info,
|
|
41
45
|
"flow.always-raises" => :warning,
|
|
42
46
|
"flow.unreachable-branch" => :info,
|
|
43
47
|
"flow.dead-assignment" => :info,
|
|
@@ -100,6 +104,7 @@ module Rigor
|
|
|
100
104
|
"call.argument-type-mismatch" => :error,
|
|
101
105
|
"call.possible-nil-receiver" => :error,
|
|
102
106
|
"call.raise-non-exception" => :error,
|
|
107
|
+
"call.deprecated-ruby2-keywords" => :warning,
|
|
103
108
|
"flow.always-raises" => :error,
|
|
104
109
|
"flow.unreachable-branch" => :warning,
|
|
105
110
|
"flow.dead-assignment" => :warning,
|
|
@@ -141,6 +146,7 @@ module Rigor
|
|
|
141
146
|
"call.argument-type-mismatch" => :error,
|
|
142
147
|
"call.possible-nil-receiver" => :error,
|
|
143
148
|
"call.raise-non-exception" => :error,
|
|
149
|
+
"call.deprecated-ruby2-keywords" => :error,
|
|
144
150
|
"flow.always-raises" => :error,
|
|
145
151
|
"flow.unreachable-branch" => :error,
|
|
146
152
|
"flow.dead-assignment" => :error,
|
|
@@ -210,7 +216,12 @@ module Rigor
|
|
|
210
216
|
profile_table.fetch(rule, authored_severity)
|
|
211
217
|
end
|
|
212
218
|
|
|
219
|
+
# A family name has no `.` in it, so only a dot-free key can match one. `resolve` runs for every
|
|
220
|
+
# diagnostic in a run's stream, and most configurations carry no such key (usually no overrides at all),
|
|
221
|
+
# so the split below is skipped for them rather than allocating a name that nothing can match.
|
|
213
222
|
def family_override(rule, overrides)
|
|
223
|
+
return nil unless overrides.any? { |key, _| !key.to_s.include?(".") }
|
|
224
|
+
|
|
214
225
|
family = rule.split(".").first
|
|
215
226
|
return nil if family.nil?
|
|
216
227
|
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "bleeding_edge"
|
|
|
6
6
|
require_relative "ci_detector"
|
|
7
7
|
require_relative "configuration/dependencies"
|
|
8
8
|
require_relative "configuration/severity_profile"
|
|
9
|
+
require_relative "config_path"
|
|
9
10
|
require_relative "configuration_error"
|
|
10
11
|
require_relative "effects/entry_points"
|
|
11
12
|
require_relative "effects/label"
|
|
@@ -282,6 +283,23 @@ module Rigor
|
|
|
282
283
|
:effects_snapshot_path, :effects_snapshot_reach, :effects_snapshot_gate, :effects_tolerated,
|
|
283
284
|
:effects_labels, :effects_attribution, :effects_envelopes
|
|
284
285
|
|
|
286
|
+
# ADR-47 WD5 amendment (#1692) — whether `.rigor.yml` (or its `includes:` chain) set `target_ruby:` itself.
|
|
287
|
+
# The default `"4.0"` is a parse version, not something the user said about the Ruby the project runs on.
|
|
288
|
+
def target_ruby_explicit?
|
|
289
|
+
@target_ruby_explicit
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# ADR-47 WD5 amendment (#1692) — `target_ruby` read as a user statement about the runtime: the configured
|
|
293
|
+
# version when the user set it explicitly, nil when it is the default or `"latest"`. `"latest"` names the
|
|
294
|
+
# newest syntax this Prism build parses, which moves with a Rigor upgrade, so it states no runtime. Only the
|
|
295
|
+
# Ruby-deprecation rules read this; `Inference::VersionGuard` keeps the analyzer's own `RUBY_VERSION`.
|
|
296
|
+
def stated_runtime_ruby
|
|
297
|
+
return nil unless target_ruby_explicit?
|
|
298
|
+
return nil if target_ruby == "latest"
|
|
299
|
+
|
|
300
|
+
target_ruby
|
|
301
|
+
end
|
|
302
|
+
|
|
285
303
|
# ADR-103 WD13 — whether effect collection runs. True exactly when the loaded configuration carried an
|
|
286
304
|
# `effects:` block, whatever its body; `rigor effects` enables it for its own run by loading an
|
|
287
305
|
# implicit `effects: {}` instead of by consulting anything else. Nothing else — no annotation, no
|
|
@@ -307,7 +325,7 @@ module Rigor
|
|
|
307
325
|
def resolved_test_paths(root: Dir.pwd)
|
|
308
326
|
return test_paths unless test_paths.nil?
|
|
309
327
|
|
|
310
|
-
CONVENTIONAL_TEST_PATHS.map { |dir| File.
|
|
328
|
+
CONVENTIONAL_TEST_PATHS.map { |dir| File.absolute_path(dir, root) }.select { |dir| File.directory?(dir) }
|
|
311
329
|
end
|
|
312
330
|
|
|
313
331
|
# Loads a configuration file.
|
|
@@ -326,6 +344,7 @@ module Rigor
|
|
|
326
344
|
if resolved.nil? || !File.exist?(resolved)
|
|
327
345
|
data = DEFAULTS
|
|
328
346
|
effects_key_present = false
|
|
347
|
+
target_ruby_key_present = false
|
|
329
348
|
else
|
|
330
349
|
# ADR-103 WD15 — captured from the RAW, pre-`DEFAULTS.merge` file (+ its `includes:` chain) because
|
|
331
350
|
# `DEFAULTS` itself carries an `"effects" => false` entry: once merged, "the file never wrote
|
|
@@ -333,9 +352,10 @@ module Rigor
|
|
|
333
352
|
# indistinguishable to {#initialize}. This is the only place that distinction still exists.
|
|
334
353
|
raw = load_with_includes(resolved)
|
|
335
354
|
effects_key_present = raw.key?("effects")
|
|
355
|
+
target_ruby_key_present = raw.key?("target_ruby")
|
|
336
356
|
data = DEFAULTS.merge(raw)
|
|
337
357
|
end
|
|
338
|
-
new(autowire_default_plugins(data), effects_key_present)
|
|
358
|
+
new(autowire_default_plugins(data), effects_key_present, target_ruby_key_present || false)
|
|
339
359
|
end
|
|
340
360
|
|
|
341
361
|
# ADR-93 WD2 — the one bundled plugin default-wired without a `plugins:` entry. `rigor-rbs-inline` is the
|
|
@@ -404,7 +424,7 @@ module Rigor
|
|
|
404
424
|
# the include-aware load before applying `--treat-all-as-inline-rbs`'s plugin injection (see
|
|
405
425
|
# {CLI::CheckCommand#load_check_configuration}).
|
|
406
426
|
def self.load_with_includes(path, visited: Set.new)
|
|
407
|
-
absolute = File.
|
|
427
|
+
absolute = File.absolute_path(path)
|
|
408
428
|
raise ConfigurationError, "circular include: #{absolute}" if visited.include?(absolute)
|
|
409
429
|
|
|
410
430
|
raw = read_yaml(absolute)
|
|
@@ -433,7 +453,7 @@ module Rigor
|
|
|
433
453
|
|
|
434
454
|
accumulated = {}
|
|
435
455
|
includes.each do |inc|
|
|
436
|
-
inc_path =
|
|
456
|
+
inc_path = ConfigPath.absolute(inc.to_s, base_dir)
|
|
437
457
|
unless File.exist?(inc_path)
|
|
438
458
|
raise ConfigurationError, "include not found: #{inc.inspect} (referenced from #{base_dir})"
|
|
439
459
|
end
|
|
@@ -459,7 +479,7 @@ module Rigor
|
|
|
459
479
|
def self.resolve_path_key!(out, key, base_dir)
|
|
460
480
|
return unless out.key?(key) && !out[key].nil?
|
|
461
481
|
|
|
462
|
-
out[key] = Array(out[key]).map { |p|
|
|
482
|
+
out[key] = Array(out[key]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
|
|
463
483
|
end
|
|
464
484
|
|
|
465
485
|
def self.resolve_plugins_io_paths!(out, base_dir)
|
|
@@ -467,7 +487,7 @@ module Rigor
|
|
|
467
487
|
return unless plugins_io.is_a?(Hash) && plugins_io["allowed_paths"]
|
|
468
488
|
|
|
469
489
|
duped = plugins_io.dup
|
|
470
|
-
duped["allowed_paths"] = Array(plugins_io["allowed_paths"]).map { |p|
|
|
490
|
+
duped["allowed_paths"] = Array(plugins_io["allowed_paths"]).map { |p| ConfigPath.absolute(p.to_s, base_dir) }
|
|
471
491
|
out["plugins_io"] = duped
|
|
472
492
|
end
|
|
473
493
|
|
|
@@ -518,7 +538,13 @@ module Rigor
|
|
|
518
538
|
# `"key" => value` hash literal — this class's usual call shape, all over the spec suite — is coerced
|
|
519
539
|
# into keyword arguments by Ruby whenever the method declares ANY keyword parameter, which would break
|
|
520
540
|
# every `Configuration.new("some_key" => value)` call site with an "unknown keyword" `ArgumentError`.
|
|
521
|
-
|
|
541
|
+
# @param target_ruby_key_present — ADR-47 WD5 amendment (#1692) — whether the file set `target_ruby:` itself,
|
|
542
|
+
# captured before `DEFAULTS.merge` for the same reason as `effects_key_present` and positional for the same
|
|
543
|
+
# reason. See {#stated_runtime_ruby}. The default reads the key off `data`, which is right only for a hash that
|
|
544
|
+
# has not been through `DEFAULTS.merge` (DEFAULTS carries the key); a caller that merges passes it explicitly.
|
|
545
|
+
# The bare `Configuration.new` is not explicit.
|
|
546
|
+
def initialize(data = DEFAULTS, effects_key_present = data.key?("effects"),
|
|
547
|
+
target_ruby_key_present = data.key?("target_ruby") && !data.equal?(DEFAULTS))
|
|
522
548
|
# Record before the per-key fetches below discard the evidence. Top level only, deliberately —
|
|
523
549
|
# see {ConfigAudit.unknown_key_warnings} for why a nested check cannot key on DEFAULTS.
|
|
524
550
|
@unknown_keys = (data.keys.map(&:to_s) - KNOWN_KEYS).sort.freeze
|
|
@@ -527,6 +553,7 @@ module Rigor
|
|
|
527
553
|
plugins_io = DEFAULTS.fetch("plugins_io").merge(data.fetch("plugins_io", {}))
|
|
528
554
|
|
|
529
555
|
@target_ruby = coerce_target_ruby(data.fetch("target_ruby", DEFAULTS.fetch("target_ruby")))
|
|
556
|
+
@target_ruby_explicit = target_ruby_key_present ? true : false
|
|
530
557
|
@paths = Array(data.fetch("paths", DEFAULTS.fetch("paths"))).map(&:to_s).freeze
|
|
531
558
|
user_excludes = Array(data.fetch("exclude", DEFAULTS.fetch("exclude"))).map(&:to_s)
|
|
532
559
|
@exclude_patterns = (BUILTIN_EXCLUDES + user_excludes).uniq.freeze
|
|
@@ -618,6 +645,9 @@ module Rigor
|
|
|
618
645
|
def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
619
646
|
{
|
|
620
647
|
"target_ruby" => target_ruby,
|
|
648
|
+
# A stated `target_ruby` turns on the deprecation rules a defaulted one leaves off (#1692), so the run cache
|
|
649
|
+
# key and the incremental fingerprint must tell the two apart even where the value matches.
|
|
650
|
+
"target_ruby_explicit" => target_ruby_explicit?,
|
|
621
651
|
"paths" => paths,
|
|
622
652
|
"exclude" => exclude_patterns - BUILTIN_EXCLUDES,
|
|
623
653
|
"plugins" => plugins,
|
|
@@ -716,6 +746,21 @@ module Rigor
|
|
|
716
746
|
copy.freeze
|
|
717
747
|
end
|
|
718
748
|
|
|
749
|
+
# #1777 — a sibling Configuration with only `baseline:` unset, every other field (including the
|
|
750
|
+
# explicitness of `target_ruby`) shared with the receiver. `rigor baseline generate` analyses with
|
|
751
|
+
# this so the generating run sees exactly the configuration `rigor check` will, minus the filter it
|
|
752
|
+
# is about to replace; rebuilding from a key list let each unlisted key revert to its default.
|
|
753
|
+
#
|
|
754
|
+
# Same frozen `dup` shape as {#with_bleeding_edge}. `@baseline_path` is the only ivar
|
|
755
|
+
# `#initialize` derives from the `baseline:` key.
|
|
756
|
+
def without_baseline
|
|
757
|
+
return self if baseline_path.nil?
|
|
758
|
+
|
|
759
|
+
copy = dup
|
|
760
|
+
copy.instance_variable_set(:@baseline_path, nil)
|
|
761
|
+
copy.freeze
|
|
762
|
+
end
|
|
763
|
+
|
|
719
764
|
# ADR-103 WD14 — the ad-hoc opt-in `rigor effects` uses when the project's configuration carries no
|
|
720
765
|
# `effects:` block: a sibling Configuration with an implicit empty block, every other field shared.
|
|
721
766
|
# A configuration that already enables effects is returned unchanged, so a project's own settings
|
|
@@ -311,12 +311,12 @@ module Rigor
|
|
|
311
311
|
# `[class_name, …ancestors]`, memoised per class. Cycle-guarded and capped: the tables are *as
|
|
312
312
|
# written*, so a project can spell an ancestry that loops.
|
|
313
313
|
#
|
|
314
|
-
# Included and prepended modules are walked beside the superclass, nearer first, because
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
#
|
|
314
|
+
# Included and prepended modules are walked beside the superclass, nearer first, because a framework's
|
|
315
|
+
# contract is as often a module as a base class: a Sidekiq worker is `class TriggerWebhookWorker;
|
|
316
|
+
# include Sidekiq::Job`, with no base class at all, so a superclass-only walk could not match a row
|
|
317
|
+
# about it however the plugin spelled one (#456). Breadth-first is close to Ruby's lookup order but not
|
|
318
|
+
# it (a module an included module includes comes after the superclass here); `Scope::ResolutionChain`
|
|
319
|
+
# is the exact order, and this walk over the plugin facts' own copy of the tables does not read it yet.
|
|
320
320
|
def ancestry(class_name)
|
|
321
321
|
@ancestry[class_name] ||= begin
|
|
322
322
|
chain = []
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
|
|
5
|
+
require_relative "../config_path"
|
|
6
|
+
|
|
5
7
|
module Rigor
|
|
6
8
|
class Environment
|
|
7
9
|
# Target-project Bundler awareness (O4, implemented).
|
|
@@ -167,7 +169,7 @@ module Rigor
|
|
|
167
169
|
# for the stats banner so end users can see what rigor picked up.
|
|
168
170
|
def self.resolve_bundle_path(bundle_path:, project_root: Dir.pwd, auto_detect: true, home: nil)
|
|
169
171
|
if bundle_path
|
|
170
|
-
path = Pathname.new(
|
|
172
|
+
path = Pathname.new(Rigor::ConfigPath.absolute(bundle_path.to_s, project_root))
|
|
171
173
|
return path if path.directory?
|
|
172
174
|
|
|
173
175
|
return nil
|
|
@@ -203,7 +205,7 @@ module Rigor
|
|
|
203
205
|
# read of the real `~/.bundle/config`).
|
|
204
206
|
def self.auto_detect(project_root:, home: nil)
|
|
205
207
|
from_config = read_bundle_config_path(File.join(project_root, ".bundle", "config"))
|
|
206
|
-
return
|
|
208
|
+
return Rigor::ConfigPath.absolute(from_config, project_root) if from_config
|
|
207
209
|
|
|
208
210
|
vendor = File.join(project_root, "vendor", "bundle")
|
|
209
211
|
return vendor if File.directory?(vendor)
|
|
@@ -221,7 +223,7 @@ module Rigor
|
|
|
221
223
|
configured = read_bundle_config_path(File.join(home, ".bundle", "config"))
|
|
222
224
|
return nil unless configured
|
|
223
225
|
|
|
224
|
-
resolved =
|
|
226
|
+
resolved = Rigor::ConfigPath.absolute(configured, project_root)
|
|
225
227
|
File.directory?(resolved) ? resolved : nil
|
|
226
228
|
end
|
|
227
229
|
private_class_method :global_bundle_path
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbs"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
class Environment
|
|
7
|
+
# Issue #1700 — the type names a {RequiredFeatures}-gated vendored directory declares, which
|
|
8
|
+
# `RbsLoader.gated_trouble` builds in its trial to decide whether the directory may stay in an environment.
|
|
9
|
+
# Read off the vendored files themselves: a nested `class Prime; class EratosthenesGenerator` names
|
|
10
|
+
# `Prime::EratosthenesGenerator`, and a reopened core class (`class Integer`) is named too, since a clash
|
|
11
|
+
# there fails `Integer`'s own definition.
|
|
12
|
+
module GatedSignatureGuard
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param dir — the vendored directory (Pathname).
|
|
16
|
+
# @return the absolute-less names (`"Prime::PseudoPrimeGenerator"`) of every class and module it declares.
|
|
17
|
+
def type_names(dir)
|
|
18
|
+
Dir.glob(File.join(dir.to_s, "*.rbs")).flat_map do |file|
|
|
19
|
+
_, _, decls = RBS::Parser.parse_signature(File.read(file))
|
|
20
|
+
names(decls, nil)
|
|
21
|
+
rescue RBS::BaseError, SystemCallError
|
|
22
|
+
[]
|
|
23
|
+
end.uniq
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def names(decls, namespace)
|
|
27
|
+
decls.flat_map do |decl|
|
|
28
|
+
next [] unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
|
29
|
+
|
|
30
|
+
name = qualify(namespace, decl.name)
|
|
31
|
+
[name] + names(decl.members, name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def qualify(namespace, type_name)
|
|
36
|
+
text = type_name.to_s
|
|
37
|
+
return text.delete_prefix("::") if text.start_with?("::") || namespace.nil?
|
|
38
|
+
|
|
39
|
+
"#{namespace}::#{text}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|