rigortype 0.4.1 → 0.4.3
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 +1 -1
- 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/11-sig-gen.md +18 -5
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/manual/02-cli-reference.md +19 -1
- data/docs/manual/03-configuration.md +7 -5
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/05-inspecting-types.md +50 -0
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/12-caching.md +18 -1
- data/docs/manual/14-rails-quickstart.md +2 -2
- data/docs/manual/18-removing-dead-code.md +41 -4
- 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 +11 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +21 -2
- data/lib/rigor/analysis/check_rules.rb +292 -59
- data/lib/rigor/analysis/dependency_recorder.rb +15 -0
- 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/effects_cache_probe.rb +1 -1
- 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 +30 -5
- data/lib/rigor/analysis/incremental_run_slot.rb +6 -4
- data/lib/rigor/analysis/incremental_session.rb +103 -23
- data/lib/rigor/analysis/path_expansion.rb +62 -6
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +124 -37
- data/lib/rigor/analysis/reachability/graph.rb +241 -37
- data/lib/rigor/analysis/reachability/scan.rb +490 -77
- data/lib/rigor/analysis/reachability/scan_cache.rb +4 -2
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +16 -1
- data/lib/rigor/analysis/run_cache_probe.rb +1 -1
- data/lib/rigor/analysis/run_stats.rb +28 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +113 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -19
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -1
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +62 -13
- data/lib/rigor/analysis/worker_session.rb +8 -5
- data/lib/rigor/cache/engine_source.rb +21 -7
- data/lib/rigor/cache/incremental_snapshot.rb +55 -5
- 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 +140 -40
- data/lib/rigor/cli/docs_command.rb +5 -4
- data/lib/rigor/cli/lens_command.rb +144 -0
- data/lib/rigor/cli/lsp_command.rb +5 -2
- data/lib/rigor/cli/skill_command.rb +3 -3
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +6 -3
- data/lib/rigor/cli/unused_command.rb +166 -27
- data/lib/rigor/cli.rb +39 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -0
- data/lib/rigor/configuration.rb +61 -2
- data/lib/rigor/environment/bundle_sig_discovery.rb +11 -7
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/installed_gem_set.rb +38 -11
- data/lib/rigor/environment/lockfile_resolver.rb +37 -6
- data/lib/rigor/environment/missing_gem_constant_index.rb +22 -7
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +316 -14
- data/lib/rigor/environment/required_features.rb +208 -0
- data/lib/rigor/environment.rb +33 -8
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/builtins/cruby_definers.rb +78 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +43 -3
- data/lib/rigor/inference/def_node_resolver.rb +23 -1
- data/lib/rigor/inference/expression_typer.rb +261 -29
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +1 -1
- 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 +286 -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 +59 -12
- data/lib/rigor/inference/match_rebinding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/argument_nodes.rb +25 -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/kernel_dispatch.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/optional_count.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +247 -28
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -7
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +8 -26
- data/lib/rigor/inference/method_dispatcher/unit_denominator_rational.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +96 -14
- data/lib/rigor/inference/module_function_state.rb +2 -1
- data/lib/rigor/inference/narrowing.rb +20 -6
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/project_patched_definedness_guard.rb +86 -0
- data/lib/rigor/inference/project_patched_methods.rb +12 -7
- data/lib/rigor/inference/project_patched_scanner.rb +108 -26
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/class_object.rb +350 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +475 -0
- data/lib/rigor/inference/refined_dispatch.rb +320 -0
- data/lib/rigor/inference/repeated_or_writes.rb +2 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +13 -0
- data/lib/rigor/inference/scope_indexer.rb +827 -65
- data/lib/rigor/inference/statement_evaluator.rb +294 -51
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/untyped_rejoin.rb +75 -0
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +87 -5
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/lens/anchor.rb +115 -0
- data/lib/rigor/lens/contract_reader.rb +118 -0
- data/lib/rigor/lens/document.rb +113 -0
- data/lib/rigor/lens/file_reader.rb +433 -0
- data/lib/rigor/lens/members.rb +153 -0
- data/lib/rigor/lens/parameters.rb +91 -0
- data/lib/rigor/lens/plugins.rb +40 -0
- data/lib/rigor/lens/run.rb +280 -0
- data/lib/rigor/lens/slot.rb +79 -0
- data/lib/rigor/lens.rb +40 -0
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +42 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/project_environment.rb +48 -6
- data/lib/rigor/protection/mutation_cache.rb +3 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +127 -11
- data/lib/rigor/reflection/constant_path.rb +13 -2
- data/lib/rigor/reflection.rb +40 -8
- data/lib/rigor/scope/discovery_index.rb +40 -1
- data/lib/rigor/scope/resolution_chain.rb +159 -11
- data/lib/rigor/scope.rb +211 -21
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +100 -10
- 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 +34 -2
- 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-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +29 -3
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +5 -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 +7 -6
- 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/plugin/io_boundary.rbs +3 -0
- data/sig/rigor/scope.rbs +30 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +9 -4
- data/skills/rigor-project-init/references/02-configure.md +2 -2
- metadata +83 -1
|
@@ -49,9 +49,9 @@ module Rigor
|
|
|
49
49
|
|
|
50
50
|
configuration = Configuration.load(options.fetch(:config))
|
|
51
51
|
paths = @argv.empty? ? configuration.paths : @argv
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
declarations, references, dynamic_uses = scan(paths, configuration, scan_cache)
|
|
52
|
+
establish_root(configuration)
|
|
53
|
+
scan_cache = open_scan_cache(configuration)
|
|
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,50 @@ module Rigor
|
|
|
67
68
|
|
|
68
69
|
private
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
# A scan's role and the spelling it records depend on the root and the working directory (#1760): a role
|
|
72
|
+
# reads every spelling below either ({#role_of}), and a relative path argument is recorded as written
|
|
73
|
+
# relative to the working directory.
|
|
74
|
+
def open_scan_cache(configuration)
|
|
75
|
+
cwd = canonical(Dir.pwd)
|
|
76
|
+
spelled = cwd == @root ? "" : ":#{cwd}"
|
|
77
|
+
Analysis::Reachability::ScanCache.open(configuration.cache_path, target_ruby: configuration.target_ruby,
|
|
78
|
+
root: "#{@root}#{spelled}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The root, and its spellings: the realpath and the logical directory it was named by.
|
|
82
|
+
def establish_root(configuration)
|
|
83
|
+
@root = project_root(configuration)
|
|
84
|
+
logical = @root == canonical(Dir.pwd) ? Dir.pwd : configuration.exclude_base
|
|
85
|
+
@root_spellings = [@root, File.absolute_path(logical)].uniq
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The project root (#1760), realpath'd: the config file's directory, unless the config sits below the
|
|
89
|
+
# working directory (`--config=ci/rigor.yml` from the project root), where the working directory is the
|
|
90
|
+
# project. Run from a subdirectory, or from a directory unrelated to the config, it is the config's.
|
|
91
|
+
# The root never rises above the config's directory or the working directory, so the role rules, which
|
|
92
|
+
# anchor `qa/`, `e2e/` and `features/` at the root, keep reading the project; an analysed path outside
|
|
93
|
+
# it reads as `../…`.
|
|
94
|
+
def project_root(configuration)
|
|
95
|
+
base = canonical(configuration.exclude_base)
|
|
96
|
+
cwd = canonical(Dir.pwd)
|
|
97
|
+
inside?(base, cwd) && base != cwd ? cwd : base
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def inside?(path, dir)
|
|
101
|
+
path.start_with?("#{dir.chomp('/')}/")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The directories the wider corpus is read from: the root, and the working directory when it lies
|
|
105
|
+
# outside the root. Reading the working directory too keeps the corpus no narrower than it was when
|
|
106
|
+
# the working directory alone was read; a reference only ever makes a declaration more reachable.
|
|
107
|
+
def corpus_dirs
|
|
108
|
+
cwd = canonical(Dir.pwd)
|
|
109
|
+
cwd == @root || inside?(cwd, @root) ? [@root] : [@root, cwd]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_graph(configuration, options, contribution, declarations:, references:, dynamic_uses:, shadows:)
|
|
71
113
|
Analysis::Reachability::Graph.new(
|
|
72
|
-
declarations: declarations, references: references, dynamic_uses: dynamic_uses,
|
|
114
|
+
declarations: declarations, references: references, dynamic_uses: dynamic_uses, shadows: shadows,
|
|
73
115
|
root_fqns: root_fqns(declarations, options.fetch(:entry_points)) + contribution.roots,
|
|
74
116
|
foreign: foreign_predicate(configuration)
|
|
75
117
|
)
|
|
@@ -106,22 +148,56 @@ module Rigor
|
|
|
106
148
|
end
|
|
107
149
|
|
|
108
150
|
# Declarations come from the analysed paths; references additionally from the wider corpus (WD7).
|
|
151
|
+
# A wider-corpus file's own declarations come back as shadows: never nodes, only what that file's own
|
|
152
|
+
# references resolve to first (#1732).
|
|
109
153
|
# Every file is consulted every run — an unchanged one contributes its cached scan, so the
|
|
110
154
|
# whole-project completeness the `--incremental` refusal protects is unaffected.
|
|
155
|
+
#
|
|
156
|
+
# A file is parsed once however it is spelled (#1760), but every spelling of it keeps what it referred
|
|
157
|
+
# to under its own path and role, exactly as when each spelling was scanned separately: dropping a
|
|
158
|
+
# spelling lost its role, and with it a production reference a `spec/` symlink to the same file would
|
|
159
|
+
# otherwise have hidden. Only the declarations are taken once, from the first analysed spelling, which
|
|
160
|
+
# is the one the report prints; any other spelling's declarations are shadows, as a wider-corpus
|
|
161
|
+
# file's are (#1732).
|
|
111
162
|
def scan(paths, configuration, cache)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
(declaration_files + reference_files(paths, configuration)).sort.each do |file|
|
|
117
|
-
result = cache.serve(:scan, file) { read_and_scan(file, configuration) }
|
|
163
|
+
out = { declarations: [], references: [], dynamic_uses: [], shadows: [] }
|
|
164
|
+
file_spellings(paths, configuration).sort.each do |real, list|
|
|
165
|
+
primary, analysed = list.find { |_, declared| declared } || list.first
|
|
166
|
+
result = cache.serve(:scan, primary) { read_and_scan(primary, real, configuration) }
|
|
118
167
|
next if result.nil?
|
|
119
168
|
|
|
120
|
-
declarations.concat(result.declarations)
|
|
121
|
-
|
|
122
|
-
dynamic_uses.concat(result.dynamic_uses)
|
|
169
|
+
out[analysed ? :declarations : :shadows].concat(result.declarations)
|
|
170
|
+
list.map(&:first).each { |file| add_spelling(out, result, file, real, primary) }
|
|
123
171
|
end
|
|
124
|
-
|
|
172
|
+
out.values_at(:declarations, :references, :dynamic_uses, :shadows)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def add_spelling(out, result, file, real, primary)
|
|
176
|
+
out[:shadows].concat(result.declarations.map { |d| d.with(path: file) }) unless file == primary
|
|
177
|
+
role = role_of(file, real)
|
|
178
|
+
out[:references].concat(result.references.map { |r| r.with(path: file, role: role) })
|
|
179
|
+
out[:dynamic_uses].concat(result.dynamic_uses.map { |u| respelled(u, file, role) })
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# `{realpath => [[spelling, analysed?], ...]}`: the analysed spellings first, in `paths:` order, then
|
|
183
|
+
# the wider corpus's.
|
|
184
|
+
def file_spellings(paths, configuration)
|
|
185
|
+
analysed = Analysis::PathExpansion.ruby_files(paths, configuration.exclude_patterns,
|
|
186
|
+
base: configuration.exclude_base)
|
|
187
|
+
spellings = Hash.new { |hash, real| hash[real] = [] }
|
|
188
|
+
analysed.each { |file| spellings[canonical(file)] << [file, true] }
|
|
189
|
+
reference_files(paths, configuration).each { |file| spellings[canonical(file)] << [file, false] }
|
|
190
|
+
spellings = spellings.transform_values { |list| list.uniq(&:first) }
|
|
191
|
+
@analysed_spellings = spellings.transform_values do |list|
|
|
192
|
+
list.filter_map do |file, analysed|
|
|
193
|
+
file if analysed
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
spellings
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def respelled(use, file, role)
|
|
200
|
+
use.with(path: file, role: role, site: use.site && "#{file}:#{use.line}")
|
|
125
201
|
end
|
|
126
202
|
|
|
127
203
|
# The reference corpus is the PROJECT, not the analysed paths. A constant declared in `lib/` is commonly
|
|
@@ -129,20 +205,80 @@ module Rigor
|
|
|
129
205
|
# treating those as absent is what manufactured artifacts on two of three corpus targets in #345.
|
|
130
206
|
# Widening the *declaration* set the same way would cancel the gain (measured: +1 / −3 / +2 candidates,
|
|
131
207
|
# ADR-102 WD2), which is exactly why the two corpora are separated rather than both widened.
|
|
208
|
+
#
|
|
209
|
+
# The files are read from every {#corpus_dirs} entry (#1760): running from a subdirectory or through a
|
|
210
|
+
# symlinked spelling reads the same project.
|
|
132
211
|
def reference_files(_paths, configuration)
|
|
133
|
-
|
|
134
|
-
absolute
|
|
135
|
-
|
|
212
|
+
absolute = project_files(REFERENCE_GLOB)
|
|
213
|
+
Analysis::PathExpansion.reject_excluded(absolute, configuration.exclude_patterns,
|
|
214
|
+
base: configuration.exclude_base)
|
|
136
215
|
end
|
|
137
216
|
|
|
138
|
-
|
|
217
|
+
# The role is read from the file's realpath relative to the project root (#1760), so `test/helper.rb`
|
|
218
|
+
# is test code whichever directory the command runs from. A file outside the root reads as `../…`,
|
|
219
|
+
# which names no directory above the root.
|
|
220
|
+
def read_and_scan(file, real, configuration)
|
|
139
221
|
Analysis::Reachability::Scan.call(path: file, source: File.read(file),
|
|
140
|
-
target_ruby: configuration.target_ruby
|
|
222
|
+
target_ruby: configuration.target_ruby,
|
|
223
|
+
role: role_of(file, real))
|
|
141
224
|
rescue SystemCallError
|
|
142
225
|
nil
|
|
143
226
|
end
|
|
144
227
|
|
|
145
|
-
#
|
|
228
|
+
# Absolute paths of the project's own files matching `glob`, under every corpus directory.
|
|
229
|
+
def project_files(glob)
|
|
230
|
+
corpus_dirs.flat_map do |dir|
|
|
231
|
+
Analysis::Reachability::ProjectFiles.own(Dir.glob(glob, base: dir), dir).map { |rel| File.join(dir, rel) }
|
|
232
|
+
end.uniq
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def canonical(path)
|
|
236
|
+
File.realpath(path)
|
|
237
|
+
rescue SystemCallError
|
|
238
|
+
File.absolute_path(path)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# A file's role (#1760), read from its spellings as written below the project root or the working
|
|
242
|
+
# directory: the first one below the root decides, and any of them reading as test code makes it test
|
|
243
|
+
# code, so a `features/` tree below a subdirectory run's working directory stays test code as it was when
|
|
244
|
+
# the working directory was the only root. The realpath is read only when no written spelling lies below
|
|
245
|
+
# either: a symlink whose target runs through a `test/` directory must not turn the production files
|
|
246
|
+
# reached through it into test code.
|
|
247
|
+
def role_of(file, real)
|
|
248
|
+
spellings = below(file, @root_spellings) + below(file, cwd_spellings)
|
|
249
|
+
spellings = below(real, @root_spellings) + below(real, cwd_spellings) if spellings.empty?
|
|
250
|
+
role_for = Analysis::Reachability::Scan.method(:role_for)
|
|
251
|
+
return :test if spellings.uniq.any? { |path| role_for.call(path) == :test }
|
|
252
|
+
|
|
253
|
+
role_for.call(spellings.first || relative_to_root(real))
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# `path` relative to every directory in `dirs` it lies below.
|
|
257
|
+
def below(path, dirs)
|
|
258
|
+
absolute = File.absolute_path(path)
|
|
259
|
+
dirs.filter_map { |dir| absolute.delete_prefix("#{dir.chomp('/')}/") if inside?(absolute, dir) }.uniq
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# The working directory as its realpath and, when the shell's `PWD` names it, as that logical spelling.
|
|
263
|
+
def cwd_spellings
|
|
264
|
+
@cwd_spellings ||= begin
|
|
265
|
+
logical = ENV.fetch("PWD", nil)
|
|
266
|
+
real = canonical(Dir.pwd)
|
|
267
|
+
logical && canonical(logical) == real ? [real, Dir.pwd, logical].uniq : [real, Dir.pwd].uniq
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def relative_to_root(real)
|
|
272
|
+
prefix = "#{@root.chomp('/')}/"
|
|
273
|
+
return real.delete_prefix(prefix) if real.start_with?(prefix)
|
|
274
|
+
|
|
275
|
+
Pathname.new(real).relative_path_from(Pathname.new(@root)).to_s
|
|
276
|
+
rescue ArgumentError
|
|
277
|
+
real
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# A glob is matched against every analysed spelling of the declaration's file, and each of them and its
|
|
281
|
+
# realpath relative to the project root (#1760) and to the working directory:
|
|
146
282
|
# `configuration.paths` are expanded to absolute, so a user-written `--entry-point=lib/entry.rb` would
|
|
147
283
|
# otherwise never match anything and silently produce a root set of zero.
|
|
148
284
|
# A constant named only from the project's own `sig/` is referenced — the #345 probe reported exactly this
|
|
@@ -176,8 +312,8 @@ module Rigor
|
|
|
176
312
|
names = declarations.map(&:fqn)
|
|
177
313
|
return [] if names.empty?
|
|
178
314
|
|
|
179
|
-
|
|
180
|
-
|
|
315
|
+
project_files(TEMPLATE_GLOB).flat_map do |absolute|
|
|
316
|
+
rel = relative_to_root(absolute)
|
|
181
317
|
haystack = cache.serve(:runs, absolute) { constant_bearing_text(File.read(absolute).scrub) }
|
|
182
318
|
names.filter_map do |fqn|
|
|
183
319
|
next unless haystack.include?(fqn)
|
|
@@ -208,10 +344,12 @@ module Rigor
|
|
|
208
344
|
return [] if globs.empty?
|
|
209
345
|
|
|
210
346
|
matcher = Analysis::Reachability::ProjectFiles.method(:entry_point_match?)
|
|
211
|
-
cwd = "#{File.absolute_path(Dir.pwd)}/"
|
|
212
347
|
declarations.filter_map do |d|
|
|
213
|
-
|
|
214
|
-
|
|
348
|
+
real = canonical(d.path)
|
|
349
|
+
dirs = @root_spellings + cwd_spellings
|
|
350
|
+
written = @analysed_spellings.fetch(real, [d.path]) | [d.path]
|
|
351
|
+
spellings = [*written, relative_to_root(real), *written.flat_map { |w| below(w, dirs) }, *below(real, dirs)]
|
|
352
|
+
d.fqn if globs.any? { |g| spellings.any? { |path| matcher.call(g, path) } }
|
|
215
353
|
end
|
|
216
354
|
end
|
|
217
355
|
|
|
@@ -235,6 +373,7 @@ module Rigor
|
|
|
235
373
|
def foreign_predicate(configuration)
|
|
236
374
|
env = Environment.for_project(libraries: configuration.libraries, signature_paths: [],
|
|
237
375
|
cache_store: cache_store(configuration),
|
|
376
|
+
required_features: ProjectEnvironment.required_features(configuration),
|
|
238
377
|
**ProjectEnvironment.dependency_discovery_options(configuration))
|
|
239
378
|
->(fqn) { !env.singleton_for_name(fqn).nil? }
|
|
240
379
|
rescue StandardError
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -51,6 +51,7 @@ module Rigor
|
|
|
51
51
|
"init" => :run_init,
|
|
52
52
|
"annotate" => :run_annotate,
|
|
53
53
|
"type-of" => :run_type_of,
|
|
54
|
+
"lens" => :run_lens,
|
|
54
55
|
"trace" => :run_trace,
|
|
55
56
|
"type-scan" => :run_type_scan,
|
|
56
57
|
"effects" => :run_effects,
|
|
@@ -281,20 +282,46 @@ module Rigor
|
|
|
281
282
|
# tempfile, uri, logger, date, prism, rbs). Adding a
|
|
282
283
|
# `sig/<gem>.rbs` file under `sig/` is the simplest way
|
|
283
284
|
# to extend type coverage today.
|
|
285
|
+
#
|
|
286
|
+
# `target_ruby` is left commented: the default only picks the syntax Rigor parses. Set it to the lowest
|
|
287
|
+
# Ruby your project runs on, and Rigor also reports what that Ruby deprecates.
|
|
288
|
+
# target_ruby: #{Configuration::DEFAULTS.fetch('target_ruby').dump}
|
|
284
289
|
#{YAML.dump(init_settings(path)).sub(/\A---\n/, '')}
|
|
285
290
|
YAML
|
|
286
291
|
end
|
|
287
292
|
|
|
288
293
|
# The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
|
|
289
294
|
# the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
|
|
290
|
-
# relative to the config file's directory, which is what {Configuration.load} resolves them against.
|
|
295
|
+
# relative to the config file's directory, which is what {Configuration.load} resolves them against. `paths:`
|
|
296
|
+
# is written the same way: the default `lib` is resolved against the physical cwd, so a config written to
|
|
297
|
+
# `config/rigor.yml` must say `../lib`, or it loads back as `config/lib` (#1399).
|
|
298
|
+
#
|
|
299
|
+
# `target_ruby` is left out (the template writes it commented): a written key is a statement about the runtime
|
|
300
|
+
# (#1692), and the starter must not make one the user never chose.
|
|
291
301
|
def init_settings(path)
|
|
302
|
+
config_dir = config_dir_of(path)
|
|
303
|
+
settings = Configuration::DEFAULTS.except("target_ruby")
|
|
304
|
+
settings = settings.merge("paths" => roots_relative_to(settings.fetch("paths"), config_dir))
|
|
292
305
|
test_paths = Configuration.new.resolved_test_paths
|
|
293
|
-
return
|
|
306
|
+
return settings if test_paths.empty?
|
|
294
307
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
308
|
+
settings.merge("test_paths" => roots_relative_to(test_paths, config_dir))
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# The directory the config file's roots are written relative to. An absolute `--path` is taken through its
|
|
312
|
+
# physical directory: the roots are resolved against the physical cwd (`Dir.pwd` is getcwd), so a logical
|
|
313
|
+
# spelling through a symlink makes the lexical `..` climb a different tree than the kernel does (#1399 review).
|
|
314
|
+
# A relative `--path` stays lexical, as {Configuration.load} resolves it.
|
|
315
|
+
def config_dir_of(path)
|
|
316
|
+
dir = File.dirname(path)
|
|
317
|
+
Pathname(Pathname(path).absolute? ? File.realdirpath(dir) : File.absolute_path(dir))
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Each root is resolved against the physical cwd (where `rigor` reads it from), then written relative to the config
|
|
321
|
+
# dir. With the config at the project root, both sides are the same physical directory, so the roots come out as
|
|
322
|
+
# they are spelled from it (`lib`, `spec`), which is master's output for the default config.
|
|
323
|
+
def roots_relative_to(roots, config_dir)
|
|
324
|
+
roots.map { |root| Pathname(File.absolute_path(root)).relative_path_from(config_dir).to_s }
|
|
298
325
|
end
|
|
299
326
|
|
|
300
327
|
def run_annotate
|
|
@@ -309,6 +336,12 @@ module Rigor
|
|
|
309
336
|
TypeOfCommand.new(argv: @argv, out: @out, err: @err).run
|
|
310
337
|
end
|
|
311
338
|
|
|
339
|
+
def run_lens
|
|
340
|
+
require_relative "cli/lens_command"
|
|
341
|
+
|
|
342
|
+
LensCommand.new(argv: @argv, out: @out, err: @err).run
|
|
343
|
+
end
|
|
344
|
+
|
|
312
345
|
def run_trace
|
|
313
346
|
require_relative "cli/trace_command"
|
|
314
347
|
|
|
@@ -455,6 +488,7 @@ module Rigor
|
|
|
455
488
|
init Create a starter .rigor.yml
|
|
456
489
|
annotate Print FILE with each line's last-expression type
|
|
457
490
|
type-of Print inferred types at FILE:LINE[:COL] positions
|
|
491
|
+
lens Map a file's declarations with their types and provenance (experimental)
|
|
458
492
|
trace Replay how the engine typed FILE as a terminal animation
|
|
459
493
|
type-scan Report Scope#type_of coverage across PATHs
|
|
460
494
|
effects Report each method's effect labels, and the committed effect snapshot
|
|
@@ -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,
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -283,6 +283,23 @@ module Rigor
|
|
|
283
283
|
:effects_snapshot_path, :effects_snapshot_reach, :effects_snapshot_gate, :effects_tolerated,
|
|
284
284
|
:effects_labels, :effects_attribution, :effects_envelopes
|
|
285
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
|
+
|
|
286
303
|
# ADR-103 WD13 — whether effect collection runs. True exactly when the loaded configuration carried an
|
|
287
304
|
# `effects:` block, whatever its body; `rigor effects` enables it for its own run by loading an
|
|
288
305
|
# implicit `effects: {}` instead of by consulting anything else. Nothing else — no annotation, no
|
|
@@ -327,6 +344,7 @@ module Rigor
|
|
|
327
344
|
if resolved.nil? || !File.exist?(resolved)
|
|
328
345
|
data = DEFAULTS
|
|
329
346
|
effects_key_present = false
|
|
347
|
+
target_ruby_key_present = false
|
|
330
348
|
else
|
|
331
349
|
# ADR-103 WD15 — captured from the RAW, pre-`DEFAULTS.merge` file (+ its `includes:` chain) because
|
|
332
350
|
# `DEFAULTS` itself carries an `"effects" => false` entry: once merged, "the file never wrote
|
|
@@ -334,9 +352,17 @@ module Rigor
|
|
|
334
352
|
# indistinguishable to {#initialize}. This is the only place that distinction still exists.
|
|
335
353
|
raw = load_with_includes(resolved)
|
|
336
354
|
effects_key_present = raw.key?("effects")
|
|
355
|
+
target_ruby_key_present = raw.key?("target_ruby")
|
|
337
356
|
data = DEFAULTS.merge(raw)
|
|
338
357
|
end
|
|
339
|
-
new(autowire_default_plugins(data), effects_key_present)
|
|
358
|
+
new(autowire_default_plugins(data), effects_key_present, target_ruby_key_present || false)
|
|
359
|
+
.with_exclude_base(resolved && File.exist?(resolved) ? File.dirname(resolved) : nil)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# #1576 — the directory relative `exclude:` patterns are matched against: the directory of the config file in
|
|
363
|
+
# use (the base `paths:` resolve against), or the working directory when no config file is used.
|
|
364
|
+
def exclude_base
|
|
365
|
+
@base_dir || Dir.pwd
|
|
340
366
|
end
|
|
341
367
|
|
|
342
368
|
# ADR-93 WD2 — the one bundled plugin default-wired without a `plugins:` entry. `rigor-rbs-inline` is the
|
|
@@ -519,7 +545,13 @@ module Rigor
|
|
|
519
545
|
# `"key" => value` hash literal — this class's usual call shape, all over the spec suite — is coerced
|
|
520
546
|
# into keyword arguments by Ruby whenever the method declares ANY keyword parameter, which would break
|
|
521
547
|
# every `Configuration.new("some_key" => value)` call site with an "unknown keyword" `ArgumentError`.
|
|
522
|
-
|
|
548
|
+
# @param target_ruby_key_present — ADR-47 WD5 amendment (#1692) — whether the file set `target_ruby:` itself,
|
|
549
|
+
# captured before `DEFAULTS.merge` for the same reason as `effects_key_present` and positional for the same
|
|
550
|
+
# reason. See {#stated_runtime_ruby}. The default reads the key off `data`, which is right only for a hash that
|
|
551
|
+
# has not been through `DEFAULTS.merge` (DEFAULTS carries the key); a caller that merges passes it explicitly.
|
|
552
|
+
# The bare `Configuration.new` is not explicit.
|
|
553
|
+
def initialize(data = DEFAULTS, effects_key_present = data.key?("effects"),
|
|
554
|
+
target_ruby_key_present = data.key?("target_ruby") && !data.equal?(DEFAULTS))
|
|
523
555
|
# Record before the per-key fetches below discard the evidence. Top level only, deliberately —
|
|
524
556
|
# see {ConfigAudit.unknown_key_warnings} for why a nested check cannot key on DEFAULTS.
|
|
525
557
|
@unknown_keys = (data.keys.map(&:to_s) - KNOWN_KEYS).sort.freeze
|
|
@@ -528,6 +560,7 @@ module Rigor
|
|
|
528
560
|
plugins_io = DEFAULTS.fetch("plugins_io").merge(data.fetch("plugins_io", {}))
|
|
529
561
|
|
|
530
562
|
@target_ruby = coerce_target_ruby(data.fetch("target_ruby", DEFAULTS.fetch("target_ruby")))
|
|
563
|
+
@target_ruby_explicit = target_ruby_key_present ? true : false
|
|
531
564
|
@paths = Array(data.fetch("paths", DEFAULTS.fetch("paths"))).map(&:to_s).freeze
|
|
532
565
|
user_excludes = Array(data.fetch("exclude", DEFAULTS.fetch("exclude"))).map(&:to_s)
|
|
533
566
|
@exclude_patterns = (BUILTIN_EXCLUDES + user_excludes).uniq.freeze
|
|
@@ -619,6 +652,9 @@ module Rigor
|
|
|
619
652
|
def to_h # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
620
653
|
{
|
|
621
654
|
"target_ruby" => target_ruby,
|
|
655
|
+
# A stated `target_ruby` turns on the deprecation rules a defaulted one leaves off (#1692), so the run cache
|
|
656
|
+
# key and the incremental fingerprint must tell the two apart even where the value matches.
|
|
657
|
+
"target_ruby_explicit" => target_ruby_explicit?,
|
|
622
658
|
"paths" => paths,
|
|
623
659
|
"exclude" => exclude_patterns - BUILTIN_EXCLUDES,
|
|
624
660
|
"plugins" => plugins,
|
|
@@ -717,6 +753,29 @@ module Rigor
|
|
|
717
753
|
copy.freeze
|
|
718
754
|
end
|
|
719
755
|
|
|
756
|
+
# #1576 — a sibling Configuration whose relative `exclude:` patterns match against `dir` (nil: the working
|
|
757
|
+
# directory). {Configuration.load} sets it to the loaded config file's directory.
|
|
758
|
+
def with_exclude_base(dir)
|
|
759
|
+
copy = dup
|
|
760
|
+
copy.instance_variable_set(:@base_dir, dir && File.absolute_path(dir).freeze)
|
|
761
|
+
copy.freeze
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
# #1777 — a sibling Configuration with only `baseline:` unset, every other field (including the
|
|
765
|
+
# explicitness of `target_ruby`) shared with the receiver. `rigor baseline generate` analyses with
|
|
766
|
+
# this so the generating run sees exactly the configuration `rigor check` will, minus the filter it
|
|
767
|
+
# is about to replace; rebuilding from a key list let each unlisted key revert to its default.
|
|
768
|
+
#
|
|
769
|
+
# Same frozen `dup` shape as {#with_bleeding_edge}. `@baseline_path` is the only ivar
|
|
770
|
+
# `#initialize` derives from the `baseline:` key.
|
|
771
|
+
def without_baseline
|
|
772
|
+
return self if baseline_path.nil?
|
|
773
|
+
|
|
774
|
+
copy = dup
|
|
775
|
+
copy.instance_variable_set(:@baseline_path, nil)
|
|
776
|
+
copy.freeze
|
|
777
|
+
end
|
|
778
|
+
|
|
720
779
|
# ADR-103 WD14 — the ad-hoc opt-in `rigor effects` uses when the project's configuration carries no
|
|
721
780
|
# `effects:` block: a sibling Configuration with an implicit empty block, every other field shared.
|
|
722
781
|
# A configuration that already enables effects is returned unchanged, so a project's own settings
|
|
@@ -118,15 +118,19 @@ module Rigor
|
|
|
118
118
|
# `{name => LockedGem}` → set of canonical bundler gem directory basenames. Pure-Ruby gems install as
|
|
119
119
|
# `<name>-<version>`; platform-specific gems install as `<name>-<version>-<platform>` (e.g.
|
|
120
120
|
# `ffi-1.17.4-aarch64-linux-gnu`). Lockfile platform `"ruby"` is the pure-Ruby case; any other value is
|
|
121
|
-
# treated as a platform tag.
|
|
121
|
+
# treated as a platform tag. Every locked variant contributes a directory (#1844): the set only selects
|
|
122
|
+
# among directories that are actually installed, so admitting another platform's name costs nothing,
|
|
123
|
+
# while dropping the host's would discard its `sig/`.
|
|
122
124
|
def self.expected_gem_dirs(locked_gems)
|
|
123
125
|
locked_gems.each_value.with_object(Set.new) do |locked, set|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
locked.variants.each do |version, platform|
|
|
127
|
+
base = "#{locked.name}-#{version}"
|
|
128
|
+
set << if platform == "ruby" || platform.empty?
|
|
129
|
+
base
|
|
130
|
+
else
|
|
131
|
+
"#{base}-#{platform}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
130
134
|
end
|
|
131
135
|
end
|
|
132
136
|
private_class_method :expected_gem_dirs
|
|
@@ -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
|