rigortype 0.4.5 → 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 +1 -1
- data/docs/handbook/11-sig-gen.md +11 -2
- data/docs/manual/02-cli-reference.md +46 -7
- data/docs/manual/03-configuration.md +2 -1
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/10-mcp-server.md +9 -1
- data/docs/manual/12-caching.md +8 -1
- 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 +2 -1
- data/lib/rigor/analysis/buffer_binding.rb +14 -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 +4 -0
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
- 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/unreachable_clause_collector.rb +51 -0
- data/lib/rigor/analysis/check_rules.rb +93 -8
- data/lib/rigor/analysis/dependency_recorder.rb +11 -0
- data/lib/rigor/analysis/edit_replay.rb +67 -0
- data/lib/rigor/analysis/incremental.rb +37 -0
- data/lib/rigor/analysis/incremental_session.rb +175 -27
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +5 -1
- 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 +17 -4
- 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 +156 -49
- data/lib/rigor/analysis/worker_session.rb +14 -2
- data/lib/rigor/cache/incremental_snapshot.rb +30 -7
- data/lib/rigor/cli/check_command.rb +93 -4
- data/lib/rigor/cli/diff_command.rb +2 -1
- 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/plugins_command.rb +5 -0
- 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 +46 -7
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/type_of_project_scope.rb +27 -0
- data/lib/rigor/config_audit.rb +16 -5
- data/lib/rigor/configuration.rb +14 -4
- data/lib/rigor/environment/rbs_loader.rb +115 -6
- 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/callee_mutation_chain.rb +115 -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 +17 -3
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +62 -13
- data/lib/rigor/inference/global_write_census.rb +36 -1
- data/lib/rigor/inference/ivar_writer_census.rb +195 -0
- data/lib/rigor/inference/loop_value_record.rb +53 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +110 -9
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
- data/lib/rigor/inference/method_dispatcher.rb +46 -16
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +12 -0
- data/lib/rigor/inference/mutation_widening.rb +5 -2
- data/lib/rigor/inference/narrowing.rb +66 -5
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/project_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +128 -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 +480 -103
- data/lib/rigor/inference/self_call_ivar_writes.rb +18 -6
- data/lib/rigor/inference/statement_evaluator.rb +658 -90
- 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/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/mcp/server.rb +9 -1
- data/lib/rigor/plugin/base.rb +26 -11
- data/lib/rigor/plugin/manifest.rb +29 -3
- data/lib/rigor/plugin/registry.rb +38 -0
- data/lib/rigor/protection/discovery_seed.rb +57 -2
- data/lib/rigor/rooted_glob.rb +10 -1
- data/lib/rigor/scope.rb +14 -5
- data/lib/rigor/sig_gen/generator.rb +308 -37
- data/lib/rigor/sig_gen/observation_collector.rb +4 -1
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +6 -3
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/type/combinator.rb +4 -3
- data/lib/rigor/type/constant.rb +45 -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-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +60 -43
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +6 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -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 +4 -4
- 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/scope.rbs +31 -50
- data/sig/rigor/type.rbs +0 -6
- metadata +20 -1
|
@@ -56,7 +56,7 @@ module Rigor
|
|
|
56
56
|
:hkt_registrations, :hkt_definitions, :signature_paths, :protocol_contracts,
|
|
57
57
|
:source_rbs_synthesizer, :additional_initializers,
|
|
58
58
|
:effect_root, :effect_labels, :effect_attributions, :effect_edges, :effect_entry_points,
|
|
59
|
-
:effect_ancestry
|
|
59
|
+
:effect_ancestry, :per_invocation_classes, :per_invocation_non_action_macros
|
|
60
60
|
|
|
61
61
|
# #392 — the project-relative globs whose files this plugin compiles into {TemplateUnit}s. Purely a
|
|
62
62
|
# CLAIM: the engine globs these, reads each match's bytes and offers them to
|
|
@@ -74,7 +74,8 @@ module Rigor
|
|
|
74
74
|
hkt_registrations: [], hkt_definitions: [], signature_paths: [], protocol_contracts: [],
|
|
75
75
|
source_rbs_synthesizer: nil, additional_initializers: [],
|
|
76
76
|
effect_root: nil, effect_labels: [], effect_attributions: [], effect_edges: [],
|
|
77
|
-
effect_entry_points: [], effect_ancestry: [], template_globs: []
|
|
77
|
+
effect_entry_points: [], effect_ancestry: [], template_globs: [], per_invocation_classes: [],
|
|
78
|
+
per_invocation_non_action_macros: []
|
|
78
79
|
)
|
|
79
80
|
validate_id!(id)
|
|
80
81
|
validate_version!(version)
|
|
@@ -109,6 +110,7 @@ module Rigor
|
|
|
109
110
|
assign_additional_initializers(additional_initializers)
|
|
110
111
|
assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
|
|
111
112
|
effect_entry_points, effect_ancestry, template_globs)
|
|
113
|
+
assign_per_invocation_classes(per_invocation_classes, per_invocation_non_action_macros)
|
|
112
114
|
freeze
|
|
113
115
|
end
|
|
114
116
|
|
|
@@ -202,6 +204,28 @@ module Rigor
|
|
|
202
204
|
@template_globs = globs.map { |glob| glob.to_s.dup.freeze }.uniq.freeze
|
|
203
205
|
end
|
|
204
206
|
private :assign_template_globs
|
|
207
|
+
|
|
208
|
+
# ADR-125 — `per_invocation_classes:` names the framework classes whose every instance serves exactly one
|
|
209
|
+
# entry point, so ivar writes in two distinct entry points of a descendant are never live together.
|
|
210
|
+
# `per_invocation_non_action_macros:` names the class-body macros whose Symbol, String or inline-`def`
|
|
211
|
+
# arguments name a public method that is NOT an entry point (`before_action :load`, `helper_method
|
|
212
|
+
# :current_user`); they apply to this manifest's own `per_invocation_classes:`, so a list without classes
|
|
213
|
+
# is refused. Validated and assigned in one call, because `#initialize` is at its line budget.
|
|
214
|
+
def assign_per_invocation_classes(names, macros)
|
|
215
|
+
validate_array_of!("per_invocation_classes", names, "non-empty String") do |name|
|
|
216
|
+
name.is_a?(String) && !name.empty?
|
|
217
|
+
end
|
|
218
|
+
validate_array_of!("per_invocation_non_action_macros", macros, "Symbol/non-empty String") do |macro|
|
|
219
|
+
macro.is_a?(Symbol) || (macro.is_a?(String) && !macro.empty?)
|
|
220
|
+
end
|
|
221
|
+
if names.empty? && !macros.empty?
|
|
222
|
+
raise ArgumentError, "per_invocation_non_action_macros needs per_invocation_classes to apply to"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
@per_invocation_classes = names.map { |name| name.to_s.delete_prefix("::").dup.freeze }.uniq.freeze
|
|
226
|
+
@per_invocation_non_action_macros = macros.map(&:to_sym).uniq.freeze
|
|
227
|
+
end
|
|
228
|
+
private :assign_per_invocation_classes
|
|
205
229
|
# rubocop:enable Metrics/ParameterLists, Metrics/AbcSize
|
|
206
230
|
|
|
207
231
|
public
|
|
@@ -258,7 +282,9 @@ module Rigor
|
|
|
258
282
|
"effect_edges" => effect_edges.map(&:to_h),
|
|
259
283
|
"effect_entry_points" => effect_entry_points.map(&:to_h),
|
|
260
284
|
"effect_ancestry" => effect_ancestry.map(&:to_h),
|
|
261
|
-
"template_globs" => template_globs
|
|
285
|
+
"template_globs" => template_globs,
|
|
286
|
+
"per_invocation_classes" => per_invocation_classes,
|
|
287
|
+
"per_invocation_non_action_macros" => per_invocation_non_action_macros.map(&:to_s)
|
|
262
288
|
}
|
|
263
289
|
end
|
|
264
290
|
|
|
@@ -397,6 +397,28 @@ module Rigor
|
|
|
397
397
|
@rbs_complete_extends_set.include?(class_name.to_s)
|
|
398
398
|
end
|
|
399
399
|
|
|
400
|
+
# ADR-125 — the aggregate class names declared across loaded plugins as per-invocation (manifest
|
|
401
|
+
# `per_invocation_classes:`): each instance of a descendant serves exactly one public-method entry point.
|
|
402
|
+
# `per_invocation_class?` is the exact-name membership predicate; `def.ivar-write-mismatch` walks a
|
|
403
|
+
# class's ancestry and asks it of each ancestor.
|
|
404
|
+
attr_reader :per_invocation_classes
|
|
405
|
+
|
|
406
|
+
def per_invocation_class?(class_name)
|
|
407
|
+
return false if class_name.nil?
|
|
408
|
+
|
|
409
|
+
@per_invocation_classes_set.include?(class_name.to_s.delete_prefix("::"))
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# ADR-125 — the macros whose arguments name a non-entry-point method on a descendant of the declared
|
|
413
|
+
# per-invocation class `class_name`: the union of `per_invocation_non_action_macros:` over the manifests
|
|
414
|
+
# that declare that class. An empty Set for a class no manifest declares.
|
|
415
|
+
def per_invocation_non_action_macros(class_name)
|
|
416
|
+
@per_invocation_macros.fetch(class_name.to_s.delete_prefix("::"), EMPTY_MACROS)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
EMPTY_MACROS = Set.new.freeze
|
|
420
|
+
private_constant :EMPTY_MACROS
|
|
421
|
+
|
|
400
422
|
# ADR-28 — flat, ordered list of every loaded plugin's path-scoped method-protocol contracts, in
|
|
401
423
|
# plugin registration order. Read from each plugin's `#protocol_contracts` (which the manifest backs
|
|
402
424
|
# by default but a plugin MAY override to fold in per-project config). Consumed by
|
|
@@ -465,12 +487,28 @@ module Rigor
|
|
|
465
487
|
@rbs_complete_ancestors_set = @rbs_complete_ancestors.to_set.freeze
|
|
466
488
|
@rbs_complete_extends = aggregate_class_names(manifests, :rbs_complete_extends)
|
|
467
489
|
@rbs_complete_extends_set = @rbs_complete_extends.to_set.freeze
|
|
490
|
+
@per_invocation_classes = aggregate_class_names(manifests, :per_invocation_classes)
|
|
491
|
+
@per_invocation_classes_set = @per_invocation_classes.to_set.freeze
|
|
492
|
+
@per_invocation_macros = compile_per_invocation_macros(manifests)
|
|
468
493
|
@type_node_resolvers = manifests.flat_map { |m| m&.type_node_resolvers || [] }.freeze
|
|
469
494
|
@protocol_contracts = @plugins.flat_map { |p| safe_protocol_contracts(p) }.freeze
|
|
470
495
|
@contracts_by_path = {}
|
|
471
496
|
@effect_memo = {}
|
|
472
497
|
end
|
|
473
498
|
|
|
499
|
+
# ADR-125 — declared class name => frozen Set of the non-action macros its declaring manifests list.
|
|
500
|
+
def compile_per_invocation_macros(manifests)
|
|
501
|
+
table = Hash.new { |hash, key| hash[key] = Set.new }
|
|
502
|
+
manifests.each do |manifest|
|
|
503
|
+
next if manifest.nil?
|
|
504
|
+
|
|
505
|
+
manifest.per_invocation_classes.each do |name|
|
|
506
|
+
table[name].merge(manifest.per_invocation_non_action_macros)
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
table.to_h { |name, macros| [name, macros.freeze] }.freeze
|
|
510
|
+
end
|
|
511
|
+
|
|
474
512
|
# The shared shape of `open_receivers:` / `rbs_complete_ancestors:` — a flat uniq'd class-name list
|
|
475
513
|
# aggregated across manifests, each entry normalised to String.
|
|
476
514
|
def aggregate_class_names(manifests, field)
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require_relative "../inference/parameter_inference_collector"
|
|
4
4
|
require_relative "../inference/scope_indexer"
|
|
5
5
|
require_relative "../inference/project_seed_tables"
|
|
6
|
+
require_relative "../cache/incremental_snapshot"
|
|
7
|
+
require_relative "../analysis/path_expansion"
|
|
8
|
+
require_relative "../analysis/buffer_binding"
|
|
6
9
|
|
|
7
10
|
module Rigor
|
|
8
11
|
module Protection
|
|
@@ -73,12 +76,64 @@ module Rigor
|
|
|
73
76
|
# @param paths — the measured file set, in the same order {#bundles} was built from.
|
|
74
77
|
# @param bundles — that bundle set.
|
|
75
78
|
# @param buffer — the mutant binding (logical path → mutant bytes).
|
|
79
|
+
# @param constant_sources — ADR-124 — also carry the per-name writer paths (`constant_sources`), which a
|
|
80
|
+
# `prebuilt:` runner needs to retract a `pre_eval:` constant another file also assigns (#663).
|
|
76
81
|
# @return frozen seed tables.
|
|
77
|
-
def tables_for_buffer(paths:, bundles:, buffer:)
|
|
82
|
+
def tables_for_buffer(paths:, bundles:, buffer:, constant_sources: false)
|
|
78
83
|
index = Inference::ScopeIndexer.discovered_project_index_incremental(
|
|
79
84
|
paths, seed_bundles: bundles, buffer: buffer
|
|
80
85
|
)
|
|
81
|
-
seed_tables(index)
|
|
86
|
+
tables = seed_tables(index)
|
|
87
|
+
sources = index.fetch(:def_index).fetch(:constant_sources)
|
|
88
|
+
tables = tables.merge(constant_sources: sources) if constant_sources && !sources.empty?
|
|
89
|
+
tables.freeze
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Issue #512 — the seed a one-shot probe (`rigor type-of`) can afford: the cross-file discovery tables for
|
|
93
|
+
# the configured paths, folded from the per-file bundles a prior `rigor check --incremental` left in the
|
|
94
|
+
# persistent snapshot, with `buffer`'s file (and any file whose bytes moved since) re-walked. A probe has
|
|
95
|
+
# no business re-parsing the project — that is what made the first attempt cost twice a single-file probe —
|
|
96
|
+
# so when there is no reusable snapshot this answers nil and the caller keeps its single-file scope. It
|
|
97
|
+
# never writes the snapshot.
|
|
98
|
+
#
|
|
99
|
+
# The snapshot is reused only under the fingerprint a bare `rigor check --incremental` writes (roots = the
|
|
100
|
+
# configured paths, the plain run mode): any other fingerprint, a different engine, configuration,
|
|
101
|
+
# lockfile or `sig/` included, is a miss.
|
|
102
|
+
#
|
|
103
|
+
# @param configuration — the probe's loaded configuration.
|
|
104
|
+
# @param buffer — the editor binding, or nil.
|
|
105
|
+
# @return frozen seed tables, or nil when the snapshot cannot seed this project.
|
|
106
|
+
def from_snapshot(configuration:, buffer: nil)
|
|
107
|
+
paths = Analysis::PathExpansion.configured_files(configuration)
|
|
108
|
+
return nil if paths.empty?
|
|
109
|
+
|
|
110
|
+
bundles = snapshot_bundles(configuration)
|
|
111
|
+
return nil if bundles.nil? || bundles.empty?
|
|
112
|
+
|
|
113
|
+
tables_for_buffer(paths: respell_for_buffer(paths, buffer), bundles: bundles, buffer: buffer,
|
|
114
|
+
constant_sources: true)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The configured file list with the entry that reaches `buffer`'s file respelled as the buffer's logical
|
|
118
|
+
# path: the binding substitutes bytes only for a path spelled exactly as its logical path, while the
|
|
119
|
+
# configured paths may be relative or reach the file through a symlink. `real_paths` is the memoised
|
|
120
|
+
# `BufferBinding.real_path` of each entry when the caller keeps one.
|
|
121
|
+
def respell_for_buffer(paths, buffer, real_paths: nil)
|
|
122
|
+
return paths if buffer.nil?
|
|
123
|
+
|
|
124
|
+
real = Analysis::BufferBinding.real_path(buffer.logical_path)
|
|
125
|
+
paths.each_with_index.map do |path, index|
|
|
126
|
+
entry_real = real_paths ? real_paths[index] : Analysis::BufferBinding.real_path(path)
|
|
127
|
+
entry_real == real ? buffer.logical_path : path
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def snapshot_bundles(configuration)
|
|
132
|
+
fingerprint = Cache::IncrementalSnapshot.fingerprint(configuration: configuration, roots: configuration.paths)
|
|
133
|
+
return nil if fingerprint.nil?
|
|
134
|
+
|
|
135
|
+
loaded = Cache::IncrementalSnapshot.new(root: configuration.cache_path).load_any(fingerprints: [fingerprint])
|
|
136
|
+
loaded&.last&.seed_bundles
|
|
82
137
|
end
|
|
83
138
|
|
|
84
139
|
# The whole-project discovery tables, from the single-walk combined pass (one parse per file) that
|
data/lib/rigor/rooted_glob.rb
CHANGED
|
@@ -12,6 +12,9 @@ module Rigor
|
|
|
12
12
|
# would not do that: it reads an absolute pattern, or an absolute alternative, outside `base`. A root without
|
|
13
13
|
# metacharacters is joined unchanged, so the results are the joined glob's strings in its order, `root` kept as
|
|
14
14
|
# given (relative, symlinked or with a trailing `/`).
|
|
15
|
+
#
|
|
16
|
+
# `literal(name)` is the same escape for a single name (a file's basename, a configured path) that is joined into
|
|
17
|
+
# a pattern as a literal: `literal("a[1].rb")` matches `a[1].rb` and not `a1.rb`.
|
|
15
18
|
module RootedGlob
|
|
16
19
|
METACHARACTERS = /[\[\]{}*?\\]/
|
|
17
20
|
|
|
@@ -21,7 +24,13 @@ module Rigor
|
|
|
21
24
|
# @param pattern — a `Dir.glob` pattern relative to `root`
|
|
22
25
|
# @return the matches spelled under `root`, in `Dir.glob` order; empty when `root` is not a directory
|
|
23
26
|
def glob(root, pattern)
|
|
24
|
-
Dir.glob(File.join(root
|
|
27
|
+
Dir.glob(File.join(literal(root), pattern.to_s))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param name — a file or directory name, taken literally
|
|
31
|
+
# @return the pattern that matches exactly `name` under `Dir.glob`, with its metacharacters escaped
|
|
32
|
+
def literal(name)
|
|
33
|
+
name.to_s.gsub(METACHARACTERS, "\\\\\\0")
|
|
25
34
|
end
|
|
26
35
|
end
|
|
27
36
|
end
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -1563,7 +1563,8 @@ module Rigor
|
|
|
1563
1563
|
#
|
|
1564
1564
|
# This is EVIDENCE FOR a singleton ancestor, never against one: the walk sees only what a constant
|
|
1565
1565
|
# argument to a receiverless `extend` (or, since #915, a receiverless `include` / `prepend` inside a
|
|
1566
|
-
# `class << self` body) written inside a declaration body spells,
|
|
1566
|
+
# `class << self` body) written inside a declaration body spells, or, since #2094, `Widget.extend(M)` and
|
|
1567
|
+
# `Widget.singleton_class.include(M)` with a constant receiver, so a runtime `Widget.extend(m)` and an
|
|
1567
1568
|
# `extend` in a file outside the analysed set are both absent from it, as is an `extend` declared only
|
|
1568
1569
|
# in RBS — that one reaches the same consumer through `Environment#singleton_extended_modules` instead.
|
|
1569
1570
|
# Its one consumer ({Inference::Narrowing.narrow_class}) therefore reads it
|
|
@@ -1941,8 +1942,9 @@ module Rigor
|
|
|
1941
1942
|
# declaration-level fact of the whole file, which is also the granularity
|
|
1942
1943
|
# `ScopeIndexer#append_constant_signature` moves the declaration signature at.
|
|
1943
1944
|
#
|
|
1944
|
-
# `constant_sources` is seeded
|
|
1945
|
-
# is empty and this is one Hash miss
|
|
1945
|
+
# `constant_sources` is seeded when dependency recording is on and in the editor publish's discovery seed
|
|
1946
|
+
# (ADR-124), where the recorder is inactive; elsewhere the table is empty and this is one Hash miss. The caller
|
|
1947
|
+
# gates on the recorder being active.
|
|
1946
1948
|
def record_constant_dependency(name)
|
|
1947
1949
|
sites = @discovery.constant_sources[name]
|
|
1948
1950
|
return if sites.nil?
|
|
@@ -1973,11 +1975,18 @@ module Rigor
|
|
|
1973
1975
|
# a wrapping `private def` / `private attr_reader` call, else the surrounding `private` / `protected` / `public`
|
|
1974
1976
|
# modifier state, plus any post-hoc `private :name, ...` named-argument calls. Consumed by the
|
|
1975
1977
|
# `def.method-visibility-mismatch` rule so explicit-non-self calls to a private method surface a diagnostic.
|
|
1976
|
-
|
|
1978
|
+
# `singleton: true` reads the class's singleton slot for the name (`[:singleton, name]`).
|
|
1979
|
+
#
|
|
1980
|
+
# Issue #2134 — every answer is an ADR-46 read of the slot, so it records `visibility:<class>#<method_name>`
|
|
1981
|
+
# whichever way it answers ({Analysis::DependencyRecorder.read_visibility}). A `private :foo` can be written from
|
|
1982
|
+
# a reopening or a `class_eval` block in a file the reader has no other edge to, and adding or removing it must
|
|
1983
|
+
# re-check the reader.
|
|
1984
|
+
def discovered_method_visibility(class_name, method_name, singleton: false)
|
|
1985
|
+
Analysis::DependencyRecorder.read_visibility(class_name, method_name) if Analysis::DependencyRecorder.active?
|
|
1977
1986
|
table = @discovery.discovered_method_visibilities[class_name.to_s]
|
|
1978
1987
|
return nil unless table
|
|
1979
1988
|
|
|
1980
|
-
table[method_name.to_sym]
|
|
1989
|
+
table[singleton ? [:singleton, method_name.to_sym] : method_name.to_sym]
|
|
1981
1990
|
end
|
|
1982
1991
|
|
|
1983
1992
|
# Closes the "`params[:f] ||= []; params[:f] << x`" precision gap (ROADMAP § Type-language / engine —
|