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
|
@@ -406,6 +406,17 @@ module Rigor
|
|
|
406
406
|
end
|
|
407
407
|
end
|
|
408
408
|
|
|
409
|
+
# Issue #2134 — {read_name} for a read of `class_name`'s visibility slots for `method_name`, on either side, or
|
|
410
|
+
# of their contest: `visibility:<class>#<method>`. `IncrementalSession#visibility_name_affected` fires the key
|
|
411
|
+
# when a file's visibility entry or contest for the slot moves. A reader that asks whether ANY class records
|
|
412
|
+
# the name private passes `"*"` as the class; the key fires when any entry of the name moves. A wildcard
|
|
413
|
+
# slot (`[:singleton, :*]`) is `*` as the method name.
|
|
414
|
+
def read_visibility(class_name, method_name)
|
|
415
|
+
return if Thread.current[KEY].nil?
|
|
416
|
+
|
|
417
|
+
read_name(:visibility, "#{class_name}##{method_name}")
|
|
418
|
+
end
|
|
419
|
+
|
|
409
420
|
# The miss-only spelling of {read_name}: a cross-file lookup of `name` (kind `:method` / `:class` /
|
|
410
421
|
# `:toplevel` / …) that resolved to nothing. Kept as the name the internal spec's negative-edge
|
|
411
422
|
# contract uses.
|
|
@@ -6,6 +6,7 @@ require_relative "return_type_heuristic"
|
|
|
6
6
|
require_relative "../../source/constant_path"
|
|
7
7
|
require_relative "../../source/node_children"
|
|
8
8
|
require_relative "../../inference/scope_indexer"
|
|
9
|
+
require_relative "../../rooted_glob"
|
|
9
10
|
|
|
10
11
|
module Rigor
|
|
11
12
|
module Analysis
|
|
@@ -127,7 +128,7 @@ module Rigor
|
|
|
127
128
|
def walk_root(root_dir, harvest, budget) # rubocop:disable Naming/PredicateMethod
|
|
128
129
|
return false unless File.directory?(root_dir)
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
RootedGlob.glob(root_dir, "**/*.rb").each do |path|
|
|
131
132
|
harvest_file(path, harvest, budget)
|
|
132
133
|
return true if harvest.catalog.size >= budget
|
|
133
134
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Analysis
|
|
7
|
+
# Issue #1967 — the edits `rigor check --verify-incremental-edits` replays on each sampled file, as the text an
|
|
8
|
+
# editor buffer stands in for it with ({IncrementalSession#replay_edit}).
|
|
9
|
+
#
|
|
10
|
+
# - `emptied` — the file with nothing in it. Every declaration and definition it makes disappears, so the replay
|
|
11
|
+
# exercises the edges a removed declaration fires.
|
|
12
|
+
# - `skeleton` — the file's `class` / `module` declarations, nested as written, with every body statement dropped
|
|
13
|
+
# (methods, `refine` blocks, visibility calls, mixins, constants). The declarations stay, so the class-level keys
|
|
14
|
+
# a removed declaration fires stay quiet, and a finer edge such as #2163's `refinement:` is what puts a reader
|
|
15
|
+
# into the closure. Emptying alone does not exercise that one: the broader key it fires re-checks the reader
|
|
16
|
+
# anyway. Not every finer edge is reached: dropping a body still moves a broader key for some of them. A
|
|
17
|
+
# `visibility:` edge is one (#2161's `module_function :two` to `private :two` swap passes both edits with the
|
|
18
|
+
# edge removed), and only an edit that changes a body while keeping it (#2277) would test it.
|
|
19
|
+
#
|
|
20
|
+
# Neither replays an addition or a body-changing edit. A skeleton identical to the file, or with no declaration
|
|
21
|
+
# (the emptied file again, or a file that does not parse), is not replayed.
|
|
22
|
+
module EditReplay
|
|
23
|
+
Edit = Data.define(:label, :source)
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# The edits to replay for `source`, in order.
|
|
28
|
+
def edits(source)
|
|
29
|
+
emptied = Edit.new(label: "emptying", source: "")
|
|
30
|
+
reduced = skeleton(source)
|
|
31
|
+
return [emptied] if reduced.empty? || reduced == source
|
|
32
|
+
|
|
33
|
+
[emptied, Edit.new(label: "reducing to its class/module skeleton", source: reduced)]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# `source`'s `class` / `module` declarations, nested, with their bodies dropped; "" when it does not parse or
|
|
37
|
+
# declares nothing.
|
|
38
|
+
def skeleton(source)
|
|
39
|
+
result = Prism.parse(source)
|
|
40
|
+
return "" unless result.success?
|
|
41
|
+
|
|
42
|
+
lines = declarations(result.value.statements, 0)
|
|
43
|
+
lines.empty? ? "" : "#{lines.join("\n")}\n"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The skeleton lines of the declarations directly in `statements`, indented `depth` levels.
|
|
47
|
+
def declarations(statements, depth)
|
|
48
|
+
return [] unless statements.is_a?(Prism::StatementsNode)
|
|
49
|
+
|
|
50
|
+
statements.body.flat_map do |node|
|
|
51
|
+
next [] unless node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
|
|
52
|
+
|
|
53
|
+
indent = " " * depth
|
|
54
|
+
["#{indent}#{header_of(node)}", *declarations(node.body, depth + 1), "#{indent}end"]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def header_of(node)
|
|
59
|
+
return "module #{node.constant_path.slice}" if node.is_a?(Prism::ModuleNode)
|
|
60
|
+
|
|
61
|
+
superclass = node.superclass
|
|
62
|
+
superclass ? "class #{node.constant_path.slice} < #{superclass.slice}" : "class #{node.constant_path.slice}"
|
|
63
|
+
end
|
|
64
|
+
private_class_method :declarations, :header_of
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../inference/global_write_census"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Analysis
|
|
5
7
|
# ADR-46 slice 2 — the pure set-algebra core of the incremental step, kept side-effect-free and
|
|
@@ -132,6 +134,41 @@ module Rigor
|
|
|
132
134
|
moved.freeze
|
|
133
135
|
end
|
|
134
136
|
|
|
137
|
+
# Issue #2158 — the `global_census:` keys whose entries moved in this edit: one for every entry that appeared in
|
|
138
|
+
# or vanished from a file's `global_write_census` ({Inference::GlobalWriteCensus.dependency_key}). `paths` MUST
|
|
139
|
+
# include removed files, whose whole census vanished. `before` / `after` map a path to that file's census Set (nil
|
|
140
|
+
# when it records nothing).
|
|
141
|
+
def changed_global_census_keys(paths, before, after)
|
|
142
|
+
moved = Set.new
|
|
143
|
+
paths.each do |path|
|
|
144
|
+
((before[path] || Set.new) ^ (after[path] || Set.new)).each do |entry|
|
|
145
|
+
moved << "global_census:#{Inference::GlobalWriteCensus.dependency_key(entry)}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
moved.freeze
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The {Inference::ScopeIndexer.visibility_entries} entries that carry a leading marker before `class, slot`.
|
|
152
|
+
MARKED_VISIBILITY_ENTRIES = %i[contested module_function].freeze
|
|
153
|
+
|
|
154
|
+
# Issue #2134 — the `<class>#<method>` slots whose visibility entries or contests moved in this edit: one for
|
|
155
|
+
# every entry that appeared in or vanished from a file's {Inference::ScopeIndexer.visibility_entries}, a
|
|
156
|
+
# singleton slot's `[:singleton, name]` read as `name` (the wildcard slot as `*`), plus `*#<method>`, which the
|
|
157
|
+
# "is the name private anywhere" readers record. That key fires for an entry of any visibility: the merge is
|
|
158
|
+
# last-writer-wins, so dropping a `public :foo` can uncover another file's `private :foo`. `paths` MUST include
|
|
159
|
+
# removed files. Issue #2161 — a `[:module_function, owner, name]` entry fires `<owner>#<name>` the same way.
|
|
160
|
+
def changed_visibility_slots(paths, before, after)
|
|
161
|
+
moved = Set.new
|
|
162
|
+
paths.each do |path|
|
|
163
|
+
((before[path] || Set.new) ^ (after[path] || Set.new)).each do |entry|
|
|
164
|
+
class_name, slot = MARKED_VISIBILITY_ENTRIES.include?(entry.first) ? entry.drop(1) : entry
|
|
165
|
+
name = slot.is_a?(Array) ? slot.last : slot
|
|
166
|
+
moved << "#{class_name}##{name}" << "*##{name}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
moved.freeze
|
|
170
|
+
end
|
|
171
|
+
|
|
135
172
|
# Issue #1120 — the method names whose refinements moved in this edit. A consumer whose answer consulted the
|
|
136
173
|
# refinement table recorded a `refinement:<method>` edge whichever way it answered, because the answer is a
|
|
137
174
|
# function of the whole project's refinements of that name. `paths` MUST include removed files, whose whole
|
|
@@ -194,6 +231,55 @@ module Rigor
|
|
|
194
231
|
moved.freeze
|
|
195
232
|
end
|
|
196
233
|
|
|
234
|
+
# Issue #1833 — the `[class name, mark]` pairs that appeared in or vanished from the class marks of the files an
|
|
235
|
+
# edit touched: a Symbol key of a `discovered_parameter_envelopes` bucket (the class-wide marks of
|
|
236
|
+
# `Scope::DiscoveryIndex`) or a `[ENVELOPE_REMOVED_KIND, name]` entry. A file may mark a class it does not declare
|
|
237
|
+
# (`x.singleton_class.prepend(Comparable)`, `o.extend(Helper)` in a method body), so neither the class-declaration
|
|
238
|
+
# diff nor a reader's edge to the declaring files sees such a mark appear or vanish.
|
|
239
|
+
#
|
|
240
|
+
# `before` and `after` are lists of envelope tables (`{class name => bucket}`): the touched files' seed-bundle
|
|
241
|
+
# tables before the edit, and the merged table of the re-parsed files after it. The diff is of their unions, which
|
|
242
|
+
# moves whenever the project's union of marks moves (the untouched files contribute the same marks to both).
|
|
243
|
+
# Returns a frozen Set of frozen `[class name, mark]` pairs.
|
|
244
|
+
def changed_class_marks(before, after)
|
|
245
|
+
(class_marks(before) ^ class_marks(after)).freeze
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def class_marks(tables)
|
|
249
|
+
marks = Set.new
|
|
250
|
+
tables.each do |table|
|
|
251
|
+
table&.each do |class_name, bucket|
|
|
252
|
+
bucket.each_key { |key| marks << [class_name, key].freeze if class_mark?(key) }
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
marks
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def class_mark?(key)
|
|
259
|
+
key.is_a?(Symbol) || (key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_REMOVED_KIND)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Issue #1661 — the names of the classes and modules whose unpositioned mixin edges
|
|
263
|
+
# (`Scope::DiscoveryIndex#unpositioned_mixins`, `"*"` included) appeared or vanished in the files an edit
|
|
264
|
+
# touched. A file can taint a module it does not otherwise change the chain of (`module Comparable;
|
|
265
|
+
# include(*MODS); end`, `Comparable.include(x) if c`), and an external chain entry has no declaring file
|
|
266
|
+
# for a reader's edge to name. `before` and `after` are lists of `{class => {side => [names]}}` tables,
|
|
267
|
+
# diffed as unions like {changed_class_marks}. Returns a frozen Set of class names.
|
|
268
|
+
def changed_unpositioned_owners(before, after)
|
|
269
|
+
moved = unpositioned_edges(before) ^ unpositioned_edges(after)
|
|
270
|
+
moved.to_set { |(owner, _side, _name)| owner }.freeze
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def unpositioned_edges(tables)
|
|
274
|
+
edges = Set.new
|
|
275
|
+
tables.each do |table|
|
|
276
|
+
table&.each do |owner, sides|
|
|
277
|
+
sides.each { |side, names| names.each { |name| edges << [owner, side, name].freeze } }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
edges
|
|
281
|
+
end
|
|
282
|
+
|
|
197
283
|
# Issue #644 — the qualified constant names whose PUBLICATION could have moved in this edit: the names
|
|
198
284
|
# whose per-file census descriptor differs between the before- and after-state of any file in `paths`.
|
|
199
285
|
# The value-constant twin of {appeared_classes}, and deliberately NOT its analogue — a class either
|
|
@@ -7,6 +7,7 @@ require_relative "../incremental_run_slot"
|
|
|
7
7
|
require_relative "../dependency_source_inference/gem_resolver"
|
|
8
8
|
require_relative "../../cache/file_digest"
|
|
9
9
|
require_relative "../../cache/incremental_snapshot"
|
|
10
|
+
require_relative "../../rooted_glob"
|
|
10
11
|
|
|
11
12
|
module Rigor
|
|
12
13
|
module Analysis
|
|
@@ -66,8 +67,8 @@ module Rigor
|
|
|
66
67
|
# opted-in gems' resolved versions, the key's one input that is neither a file nor the configuration (issue
|
|
67
68
|
# #1814), are read at the mark and watched the same way.
|
|
68
69
|
#
|
|
69
|
-
# The mark also records
|
|
70
|
-
# analysis roots, the `pre_eval:` entries and the signature roots.
|
|
70
|
+
# The mark also records what each path a presence row can name is ({Cache::Descriptor::FileEntry.kind_of}):
|
|
71
|
+
# the lockfiles, the analysis roots, the `pre_eval:` entries and the signature roots.
|
|
71
72
|
def self.start(configuration:, roots:, cache_root:, fingerprint:)
|
|
72
73
|
return nil if Gem.win_platform?
|
|
73
74
|
|
|
@@ -79,7 +80,7 @@ module Rigor
|
|
|
79
80
|
lockfiles = lockfile_paths(configuration)
|
|
80
81
|
named = Array(roots) + configuration.pre_eval + (configuration.signature_paths || ["sig"])
|
|
81
82
|
presence = (lockfiles + named.map { |path| File.absolute_path(path.to_s) }).to_h do |path|
|
|
82
|
-
[path,
|
|
83
|
+
[path, Cache::Descriptor::FileEntry.kind_of(path)]
|
|
83
84
|
end
|
|
84
85
|
rows = dependency_rows(configuration)
|
|
85
86
|
return nil if rows.nil?
|
|
@@ -174,11 +175,12 @@ module Rigor
|
|
|
174
175
|
# - no lockfile the key or the fingerprint digests was written, created or removed, and no opted-in gem
|
|
175
176
|
# resolves to another version than at the mark (issue #1814);
|
|
176
177
|
# - no file `rows` or `pinned` names changed. A content row's file changed if its change time moved. An
|
|
177
|
-
# existence row asks only whether its path is there,
|
|
178
|
-
# went,
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
178
|
+
# existence row asks only whether its path is there, and a `:kind` row what it is (#1977), so a path the
|
|
179
|
+
# mark recorded changed if it came or went, or for a kind row turned from a directory into a file or back;
|
|
180
|
+
# an editor's lock file created and removed beside it, which moves its directory's change time, does not
|
|
181
|
+
# count. That needs no clock, so it holds on any filesystem; it cannot see a path that comes and goes
|
|
182
|
+
# again within the run. Any other presence row falls back to change times: its own when present, its
|
|
183
|
+
# nearest existing ancestor's when absent;
|
|
182
184
|
# - no directory a glob row lists gained or lost an entry, nor, for a stat-mode glob, did any file it
|
|
183
185
|
# matches change.
|
|
184
186
|
#
|
|
@@ -231,15 +233,20 @@ module Rigor
|
|
|
231
233
|
ns.nil? || device != @device || ns < @started_ns
|
|
232
234
|
end
|
|
233
235
|
|
|
234
|
-
#
|
|
235
|
-
# is there, the second whether it changed), so the row that comes first must not hide the
|
|
236
|
+
# A presence row and a content row for one path are different questions (the first asks only whether it
|
|
237
|
+
# is there, or what it is, the second whether it changed), so the row that comes first must not hide the
|
|
238
|
+
# other; nor may an existence row hide a kind row.
|
|
239
|
+
PRESENCE_COMPARATORS = %i[exists kind].freeze
|
|
240
|
+
private_constant :PRESENCE_COMPARATORS
|
|
241
|
+
|
|
236
242
|
def files_unchanged?(entries, strict:)
|
|
237
243
|
seen = Set.new
|
|
238
244
|
entries.none? do |entry|
|
|
239
|
-
|
|
245
|
+
presence = PRESENCE_COMPARATORS.include?(entry.comparator) ? entry.comparator : nil
|
|
246
|
+
next false unless seen.add?([entry.path, presence])
|
|
240
247
|
|
|
241
|
-
if
|
|
242
|
-
|
|
248
|
+
if presence
|
|
249
|
+
presence_changed?(entry.path, presence, strict: strict)
|
|
243
250
|
else
|
|
244
251
|
changed?(entry.path, strict: strict)
|
|
245
252
|
end
|
|
@@ -248,8 +255,8 @@ module Rigor
|
|
|
248
255
|
|
|
249
256
|
def globs_unchanged?(entries, strict:)
|
|
250
257
|
entries.none? do |entry|
|
|
251
|
-
listed =
|
|
252
|
-
matched = entry.mode == :stat ?
|
|
258
|
+
listed = RootedGlob.glob(entry.root, File.join(File.dirname(entry.pattern), ""))
|
|
259
|
+
matched = entry.mode == :stat ? RootedGlob.glob(entry.root, entry.pattern) : []
|
|
253
260
|
(listed + matched).any? { |path| changed?(path, strict: strict) }
|
|
254
261
|
end
|
|
255
262
|
end
|
|
@@ -257,14 +264,25 @@ module Rigor
|
|
|
257
264
|
# A lockfile is read for its bytes: one absent at the mark must still be absent, one present must still be
|
|
258
265
|
# there with its change time before the mark.
|
|
259
266
|
def lockfile_changed?(path)
|
|
260
|
-
return File.exist?(path) unless
|
|
267
|
+
return File.exist?(path) unless present_at_mark?(path)
|
|
261
268
|
|
|
262
269
|
changed?(path, strict: true, missing: true)
|
|
263
270
|
end
|
|
264
271
|
|
|
265
|
-
def
|
|
272
|
+
def present_at_mark?(path)
|
|
273
|
+
kind = @presence[path]
|
|
274
|
+
!kind.nil? && kind != Cache::Descriptor::FileEntry::KIND_ABSENT
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# `comparator` is `:exists` or `:kind`: whether the path came or went, or whether it is no longer the kind
|
|
278
|
+
# of thing the mark recorded.
|
|
279
|
+
def presence_changed?(path, comparator, strict:)
|
|
266
280
|
absolute = File.absolute_path(path)
|
|
267
|
-
|
|
281
|
+
if @presence.key?(absolute)
|
|
282
|
+
return File.exist?(absolute) != present_at_mark?(absolute) if comparator == :exists
|
|
283
|
+
|
|
284
|
+
return Cache::Descriptor::FileEntry.kind_of(absolute) != @presence[absolute]
|
|
285
|
+
end
|
|
268
286
|
|
|
269
287
|
nearest = absolute
|
|
270
288
|
nearest = File.dirname(nearest) until File.exist?(nearest) || File.dirname(nearest) == nearest
|