rigortype 0.4.3 → 0.4.5
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/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +104 -15
- data/docs/manual/02-cli-reference.md +43 -12
- data/docs/manual/03-configuration.md +20 -7
- data/docs/manual/04-diagnostics.md +56 -3
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +3 -2
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +40 -4
- data/docs/manual/18-removing-dead-code.md +23 -2
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-rspec.md +101 -1
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +60 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
- data/lib/rigor/analysis/check_rules.rb +274 -51
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +61 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +270 -28
- data/lib/rigor/analysis/path_expansion.rb +3 -1
- data/lib/rigor/analysis/reachability/graph.rb +111 -14
- data/lib/rigor/analysis/reachability/scan.rb +305 -23
- data/lib/rigor/analysis/rule_catalog.rb +22 -4
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +5 -3
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
- data/lib/rigor/analysis/runner/pool_coordinator.rb +179 -31
- data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
- data/lib/rigor/analysis/runner.rb +102 -72
- data/lib/rigor/analysis/severity_stamp.rb +45 -1
- data/lib/rigor/analysis/worker_session.rb +69 -2
- data/lib/rigor/cache/descriptor.rb +79 -12
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +39 -5
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/command.rb +3 -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/mcp_command.rb +1 -1
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +32 -11
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +163 -22
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +29 -3
- 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 +67 -11
- data/lib/rigor/inference/block_call_timing.rb +12 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/captured_locals.rb +67 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/definer_resolution.rb +101 -25
- data/lib/rigor/inference/expression_typer.rb +94 -9
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher.rb +67 -3
- data/lib/rigor/inference/module_function_state.rb +213 -51
- data/lib/rigor/inference/narrowing.rb +235 -75
- 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 +120 -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 +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +414 -1
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer.rb +1061 -122
- data/lib/rigor/inference/self_call_ivar_writes.rb +346 -0
- 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 +512 -150
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/lens/anchor.rb +3 -3
- data/lib/rigor/lens/contract_reader.rb +60 -2
- data/lib/rigor/lens/document.rb +4 -3
- data/lib/rigor/lens/file_reader.rb +452 -73
- data/lib/rigor/lens/members.rb +81 -17
- data/lib/rigor/lens/run.rb +73 -4
- data/lib/rigor/lens.rb +2 -1
- data/lib/rigor/mcp/server.rb +63 -22
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/project_environment.rb +6 -3
- data/lib/rigor/protection/discovery_seed.rb +9 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +27 -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 +56 -8
- 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 +167 -22
- data/lib/rigor/sig_gen/layout_index.rb +40 -1
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/observation_collector.rb +137 -11
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/renderer.rb +13 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- 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 +50 -35
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/constant.rb +10 -1
- 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 +1 -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-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-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-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +57 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -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 +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- 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/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +4 -3
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +16 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
5
|
require_relative "access_denied_error"
|
|
6
|
+
require_relative "../rooted_glob"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Plugin
|
|
@@ -33,6 +34,8 @@ module Rigor
|
|
|
33
34
|
# for a plugin that asks "which of these N candidate files exists here?" — rigor-actionpack tries
|
|
34
35
|
# nine template extensions per `render` call — where N per-path probe rows would put thousands of
|
|
35
36
|
# validations on every warm run and one listing row puts one per directory.
|
|
37
|
+
# - `#list_directory_kinds(path)` — the KIND-aware listing (#1959): each entry with its `lstat` kind, and one
|
|
38
|
+
# `:kinds` row that moves when an entry appears, vanishes or changes kind under the same name.
|
|
36
39
|
# - `#open_url(url)` — fetches the URL when the policy permits it (`network_policy: :allowlist` plus an
|
|
37
40
|
# `allowed_url_hosts` match) and raises {AccessDeniedError} otherwise. v0.1.2 ships the allowlist
|
|
38
41
|
# surface; the default project policy still has `network_policy: :disabled` so plugins that want network
|
|
@@ -189,7 +192,7 @@ module Rigor
|
|
|
189
192
|
# @return the absolute paths directly under `path` in `Dir.glob` order; empty when not a directory
|
|
190
193
|
def list_directory(path)
|
|
191
194
|
absolute = File.absolute_path(path.to_s)
|
|
192
|
-
entries =
|
|
195
|
+
entries = RootedGlob.glob(absolute, "*")
|
|
193
196
|
unless @policy.allow_read?(absolute)
|
|
194
197
|
record_refusal(absolute)
|
|
195
198
|
return entries
|
|
@@ -199,6 +202,34 @@ module Rigor
|
|
|
199
202
|
entries
|
|
200
203
|
end
|
|
201
204
|
|
|
205
|
+
# Issue #1959 — the KIND-aware listing. Returns each entry directly under `path` with what it is, a symlink
|
|
206
|
+
# not followed ({Cache::Descriptor::GlobEntry.lstat_kind}: `:file`, `:directory`, `:symlink` or `:other`, and
|
|
207
|
+
# `:absent` for an entry that vanished between the listing and its `lstat`),
|
|
208
|
+
# and records one `:kinds` {Cache::Descriptor::GlobEntry} over `path/*`. The row reads stale when an entry
|
|
209
|
+
# appears, disappears, or keeps its name and changes kind; NOT when an entry's bytes or stat tuple move, nor
|
|
210
|
+
# when anything below a subdirectory changes. It is the row for a walk that decides per top-level entry
|
|
211
|
+
# whether to descend: rigor-ffi skips `vendor/` and does not follow a symlinked directory, so a file `gen`
|
|
212
|
+
# replaced by a directory `gen/`, or a symlink replaced by a real directory, changes what it reads while
|
|
213
|
+
# every name stays put. {#list_directory}'s `:stat` row leaves directories out, and {#glob}'s `:names` row
|
|
214
|
+
# reads names alone.
|
|
215
|
+
#
|
|
216
|
+
# Like {#list_directory}, dot entries are not listed, and an out-of-policy directory is listed truthfully
|
|
217
|
+
# and contributes no row.
|
|
218
|
+
#
|
|
219
|
+
# @param path — project directory; relative paths expand against the working directory
|
|
220
|
+
# @return a Hash from each absolute entry path, in `Dir.glob` order, to its kind; empty when not a directory
|
|
221
|
+
def list_directory_kinds(path)
|
|
222
|
+
absolute = File.absolute_path(path.to_s)
|
|
223
|
+
entries = RootedGlob.glob(absolute, "*").to_h { |entry| [entry, Cache::Descriptor::GlobEntry.lstat_kind(entry)] }
|
|
224
|
+
unless @policy.allow_read?(absolute)
|
|
225
|
+
record_refusal(absolute)
|
|
226
|
+
return entries
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
record_glob_entry(absolute, "*", mode: :kinds)
|
|
230
|
+
entries
|
|
231
|
+
end
|
|
232
|
+
|
|
202
233
|
# Issue #1652 — the MEMBERSHIP fingerprint of a `Dir.glob` pattern under `root`, `**` included. Returns the
|
|
203
234
|
# matching paths and records one `:names` {Cache::Descriptor::GlobEntry} over `root` and `pattern`: the row
|
|
204
235
|
# reads stale when a match appears or disappears, in a subdirectory created after the read too, and NOT
|
|
@@ -209,8 +240,13 @@ module Rigor
|
|
|
209
240
|
#
|
|
210
241
|
# The pattern must stay under `root`, since the policy is checked against `root` alone: a `..` segment, a
|
|
211
242
|
# brace (`{..,x}`) or a backslash escape (`.\\./`) raises {AccessDeniedError} (`:read_outside_scope`).
|
|
212
|
-
# The last two are refused outright because `Dir.glob` can spell `..` with them.
|
|
213
|
-
#
|
|
243
|
+
# The last two are refused outright because `Dir.glob` can spell `..` with them. A leading `/` is read under
|
|
244
|
+
# `root`, as the joined spelling always read it (`/lib/**/*.rb` is `root/lib/**/*.rb`). Out of policy, `root`
|
|
245
|
+
# is globbed truthfully and contributes no row, as in {#list_directory}.
|
|
246
|
+
#
|
|
247
|
+
# `root` is read literally (#1959): {RootedGlob} escapes its glob metacharacters before the join, so a root
|
|
248
|
+
# spelled `fx[1]` or a directory named `{..,zz}` neither matches a sibling nor leaves itself.
|
|
249
|
+
# {#list_directory} lists the same way, and {Cache::Descriptor::GlobEntry} re-globs the row the same way.
|
|
214
250
|
#
|
|
215
251
|
# @param root — project directory; relative paths expand against the working directory
|
|
216
252
|
# @param pattern — a `Dir.glob` pattern relative to `root`, such as `"ext/**/extconf.rb"`
|
|
@@ -227,7 +263,7 @@ module Rigor
|
|
|
227
263
|
)
|
|
228
264
|
end
|
|
229
265
|
|
|
230
|
-
entries =
|
|
266
|
+
entries = RootedGlob.glob(absolute, pattern)
|
|
231
267
|
unless @policy.allow_read?(absolute)
|
|
232
268
|
record_refusal(absolute)
|
|
233
269
|
return entries
|
|
@@ -390,8 +426,8 @@ module Rigor
|
|
|
390
426
|
self.class.attribute(entry)
|
|
391
427
|
end
|
|
392
428
|
|
|
393
|
-
# ADR-45 WD1c (#629) — the listing row for {#list_directory} (and the `:names` row for {#glob}
|
|
394
|
-
# deduplicated per (root, pattern, mode) slot.
|
|
429
|
+
# ADR-45 WD1c (#629) — the listing row for {#list_directory} (and the `:names` row for {#glob} and the
|
|
430
|
+
# `:kinds` row for {#list_directory_kinds}), deduplicated per (root, pattern, mode) slot.
|
|
395
431
|
# Recomputed on each call rather than `||=`d, so a directory listed again after a mid-run mutation
|
|
396
432
|
# carries the signature of the state the LAST reader saw.
|
|
397
433
|
def record_glob_entry(root, pattern, mode: :stat)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "box"
|
|
4
|
+
require_relative "../rooted_glob"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Plugin
|
|
@@ -74,8 +75,8 @@ module Rigor
|
|
|
74
75
|
# requiring the gem, which is what the caller is about to do.
|
|
75
76
|
def bundle_require_paths(bundle_root)
|
|
76
77
|
bundle_gem_dirs(bundle_root).flat_map do |gem_home|
|
|
77
|
-
specs =
|
|
78
|
-
|
|
78
|
+
specs = RootedGlob.glob(gem_home, "specifications/*.gemspec")
|
|
79
|
+
.filter_map { |file| Gem::Specification.load(file) }
|
|
79
80
|
specs.group_by(&:name)
|
|
80
81
|
.flat_map { |_, versions| versions.max_by(&:version).full_require_paths }
|
|
81
82
|
end
|
|
@@ -88,7 +89,7 @@ module Rigor
|
|
|
88
89
|
return [] if bundle_root.nil? || bundle_root.to_s.empty?
|
|
89
90
|
|
|
90
91
|
root = bundle_root.to_s
|
|
91
|
-
(
|
|
92
|
+
(RootedGlob.glob(root, "ruby/*") + [root])
|
|
92
93
|
.select { |dir| File.directory?(File.join(dir, "specifications")) }
|
|
93
94
|
end
|
|
94
95
|
|
|
@@ -56,9 +56,12 @@ module Rigor
|
|
|
56
56
|
# `source_rbs_synthesizer` runs over them at env-build time; an empty list contributes no synthesized RBS.
|
|
57
57
|
# @param feature_sources — path => source text the required-feature scan reads in place of that file (an
|
|
58
58
|
# editor buffer bound with `--tmp-file` / `--instead-of`).
|
|
59
|
-
# @param project_patched_methods — the `pre_eval:` registry
|
|
60
|
-
#
|
|
61
|
-
|
|
59
|
+
# @param project_patched_methods — the `pre_eval:` registry. Defaults to the configuration's own (see
|
|
60
|
+
# {.project_patched_methods}), so every probe command (`type-scan`, `trace`, `annotate`, `sig-gen`) types a
|
|
61
|
+
# patched method as `rigor check` does (issue #1895); `type-of` passes its buffer-aware one. An explicit
|
|
62
|
+
# nil keeps the unpatched universe.
|
|
63
|
+
def build(configuration:, source_files:, feature_sources: {},
|
|
64
|
+
project_patched_methods: ProjectEnvironment.project_patched_methods(configuration))
|
|
62
65
|
features = required_features(configuration, source_files, sources: feature_sources)
|
|
63
66
|
Environment.for_project(
|
|
64
67
|
project_patched_methods: project_patched_methods,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../inference/parameter_inference_collector"
|
|
4
4
|
require_relative "../inference/scope_indexer"
|
|
5
|
+
require_relative "../inference/project_seed_tables"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Protection
|
|
@@ -80,47 +81,19 @@ module Rigor
|
|
|
80
81
|
seed_tables(index).freeze
|
|
81
82
|
end
|
|
82
83
|
|
|
83
|
-
# The whole-project discovery tables, from the single-walk combined pass (one parse per file
|
|
84
|
-
#
|
|
85
|
-
# tables are dropped so an empty seed stays `{}` and every consumer's "seed nothing"
|
|
86
|
-
#
|
|
87
|
-
# `discovered_class_sources` is carried as the runner carries it on every run: Tier 2 never records edges,
|
|
88
|
-
# but an ancestor-order reader asks whether a class is declared in more than one file. The mixin tables
|
|
89
|
-
# (`discovered_includes`, `discovered_prepends`, `discovered_extends`) ride the same fold, so a singleton
|
|
90
|
-
# `extend` reads the same from a per-file scope under coverage and protection as it does under `check`.
|
|
84
|
+
# The whole-project discovery tables, from the single-walk combined pass (one parse per file) that
|
|
85
|
+
# {Analysis::Runner::ProjectPrePasses#discover} uses, mapped by the one list {Inference::ProjectSeedTables}
|
|
86
|
+
# keeps (issue #1614). Empty tables are dropped so an empty seed stays `{}` and every consumer's "seed nothing"
|
|
87
|
+
# branch keeps working.
|
|
91
88
|
def discovery_tables(paths)
|
|
92
89
|
seed_tables(Inference::ScopeIndexer.discovered_project_index_for_paths(paths))
|
|
93
90
|
end
|
|
94
91
|
|
|
95
|
-
# Maps a `{ classes:, def_index: }` discovery index onto the {Scope::DiscoveryIndex} slot names both
|
|
96
|
-
#
|
|
97
|
-
#
|
|
92
|
+
# Maps a `{ classes:, def_index: }` discovery index onto the {Scope::DiscoveryIndex} slot names both consumers
|
|
93
|
+
# take. Shared by the whole-walk ({#discovery_tables}) and bundle-fold ({#tables_for_buffer}) producers so a
|
|
94
|
+
# mutant's seed and the parent's seed can never disagree about shape.
|
|
98
95
|
def seed_tables(index)
|
|
99
|
-
|
|
100
|
-
tables = { discovered_classes: index.fetch(:classes) }
|
|
101
|
-
%i[
|
|
102
|
-
def_nodes def_nestings singleton_def_nodes def_sources singleton_def_sources superclasses includes
|
|
103
|
-
extends prepends unpositioned_mixins class_sources
|
|
104
|
-
method_visibilities methods parameter_envelopes data_member_layouts struct_member_layouts
|
|
105
|
-
deferred_ranges refinements global_write_census
|
|
106
|
-
].each do |slot|
|
|
107
|
-
tables[seed_key(slot)] = def_index.fetch(slot)
|
|
108
|
-
end
|
|
109
|
-
# ADR-119 WD1 — the pairs travel as one: an unpaired empty table is dropped here, a pair only when both
|
|
110
|
-
# halves are empty (`compact_pairs`), so a non-empty sibling is never stranded by an empty member.
|
|
111
|
-
tables.merge!(def_index.fetch(:siblings))
|
|
112
|
-
Scope::DiscoveryIndex.compact_pairs(
|
|
113
|
-
tables.reject { |name, table| !Scope::DiscoveryIndex.paired?(name) && (table.nil? || table.empty?) }
|
|
114
|
-
)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
UNPREFIXED_SLOTS = %i[data_member_layouts struct_member_layouts unpositioned_mixins].freeze
|
|
118
|
-
private_constant :UNPREFIXED_SLOTS
|
|
119
|
-
|
|
120
|
-
# The def-index slot names match the {Scope::DiscoveryIndex} ones under a `discovered_` prefix, except the
|
|
121
|
-
# two member-layout tables and the unpositioned-mixin table, which carry the same name on both sides.
|
|
122
|
-
def seed_key(slot)
|
|
123
|
-
UNPREFIXED_SLOTS.include?(slot) ? slot : :"discovered_#{slot}"
|
|
96
|
+
Inference::ProjectSeedTables.from_index(index)
|
|
124
97
|
end
|
|
125
98
|
end
|
|
126
99
|
end
|
|
@@ -209,7 +209,13 @@ module Rigor
|
|
|
209
209
|
def table_digest(project_scan)
|
|
210
210
|
payload = {
|
|
211
211
|
"synthetic" => project_scan.synthetic_method_index.to_h,
|
|
212
|
-
"patched" => patched_rows(project_scan.project_patched_methods)
|
|
212
|
+
"patched" => patched_rows(project_scan.project_patched_methods),
|
|
213
|
+
"patched_mixins" => project_scan.project_patched_methods.mixins.map do |mixin|
|
|
214
|
+
mixin.to_h.values.map(&:to_s)
|
|
215
|
+
end,
|
|
216
|
+
"patched_overrides" => project_scan.project_patched_methods.overrides.map do |key, visibility|
|
|
217
|
+
[*key, visibility].map(&:to_s)
|
|
218
|
+
end
|
|
213
219
|
}
|
|
214
220
|
Digest::SHA256.hexdigest(JSON.generate(payload))
|
|
215
221
|
rescue StandardError
|
|
@@ -221,7 +227,7 @@ module Rigor
|
|
|
221
227
|
def patched_rows(patched)
|
|
222
228
|
patched.by_key.map do |(class_name, method_name, kind), entry|
|
|
223
229
|
[class_name.to_s, method_name.to_s, kind.to_s, entry.source_path.to_s, entry.source_line,
|
|
224
|
-
entry.return_type&.describe(:short).to_s, entry.alias_of.to_s]
|
|
230
|
+
entry.return_type&.describe(:short).to_s, entry.alias_of.to_s, entry.visibility.to_s]
|
|
225
231
|
end.sort
|
|
226
232
|
end
|
|
227
233
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# `Dir.glob(File.join(root, pattern))` with `root` read as a literal path (#1956, #1959).
|
|
5
|
+
#
|
|
6
|
+
# Joining a directory into the pattern makes `Dir.glob` read the directory's own characters as glob syntax:
|
|
7
|
+
# a project at `ds [x]` or `ds{a}` matches nothing, `ds*` matches its siblings, `fx[1]` matches `fx1`, and
|
|
8
|
+
# a directory literally named `{..,zz}` expands into the parent tree. Each glob metacharacter of `root` is
|
|
9
|
+
# backslash-escaped before the join, so `root` matches only itself while `pattern` keeps exactly the meaning the
|
|
10
|
+
# joined spelling gave it: a leading `/` collapses into the join (`/etc/*` reads `root/etc/*`), and so does one
|
|
11
|
+
# inside a brace alternative (`{/etc,lib}/*` reads `root/etc/*` and `root/lib/*`). `Dir.glob(pattern, base:)`
|
|
12
|
+
# would not do that: it reads an absolute pattern, or an absolute alternative, outside `base`. A root without
|
|
13
|
+
# metacharacters is joined unchanged, so the results are the joined glob's strings in its order, `root` kept as
|
|
14
|
+
# given (relative, symlinked or with a trailing `/`).
|
|
15
|
+
module RootedGlob
|
|
16
|
+
METACHARACTERS = /[\[\]{}*?\\]/
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# @param root — a directory path (String or Pathname), taken literally
|
|
21
|
+
# @param pattern — a `Dir.glob` pattern relative to `root`
|
|
22
|
+
# @return the matches spelled under `root`, in `Dir.glob` order; empty when `root` is not a directory
|
|
23
|
+
def glob(root, pattern)
|
|
24
|
+
Dir.glob(File.join(root.to_s.gsub(METACHARACTERS, "\\\\\\0"), pattern.to_s))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -231,6 +231,11 @@ module Rigor
|
|
|
231
231
|
# (the meta-new walk does not route `Parent` through ancestor discovery), so a reader that must place `K` in a
|
|
232
232
|
# hierarchy reads its superclass as unknown, as it reads the `nil` the table records for `class K < <expr>`.
|
|
233
233
|
ENVELOPE_META_SUPERCLASS_MARK = :"<meta-superclass>"
|
|
234
|
+
# Issue #1979 — the kind of a `[ENVELOPE_CONSTRUCTED_KIND, constant_name]` bucket entry: `class K < <expr>` whose
|
|
235
|
+
# superclass expression is a core constructor call (`Data.define(...)`, `Struct.new(...)`, `Class.new(...)`)
|
|
236
|
+
# naming that constant, as the receiver or as `Class.new`'s single bare-constant argument (`Object` for none).
|
|
237
|
+
# Read only by the #1596 proof's subclass walk (`RefinedDispatch::OwnMethod.each_subclass`).
|
|
238
|
+
ENVELOPE_CONSTRUCTED_KIND = :constructed_from
|
|
234
239
|
ENVELOPE_MODULE_MARK = :"<module>"
|
|
235
240
|
ENVELOPE_DYNAMIC_MARK = :"<dynamic>"
|
|
236
241
|
ENVELOPE_OBJECT_EXTENDED_MARK = :"<object-extended>"
|
|
@@ -84,6 +84,20 @@ module Rigor
|
|
|
84
84
|
false
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
# {.external_lacks?} on the side an entry stands for: an external superclass on a singleton chain
|
|
88
|
+
# (`:singleton`, its whole tail) is asked for a singleton method, an extended module (`:instance`) and every
|
|
89
|
+
# instance-side entry for an instance method.
|
|
90
|
+
def external_lacks_on?(scope, candidates, side, name)
|
|
91
|
+
return external_lacks?(scope, candidates, name) if side == :instance
|
|
92
|
+
|
|
93
|
+
known = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: scope) }
|
|
94
|
+
return false if known.nil?
|
|
95
|
+
|
|
96
|
+
Rigor::Reflection.singleton_method_definition(known, name, scope: scope).nil?
|
|
97
|
+
rescue StandardError
|
|
98
|
+
false
|
|
99
|
+
end
|
|
100
|
+
|
|
87
101
|
# One verdict's working state: the scope, the resolver, and the edges the tests read.
|
|
88
102
|
class Context
|
|
89
103
|
attr_reader :mark, :edges
|
|
@@ -300,17 +300,24 @@ module Rigor
|
|
|
300
300
|
# `Kernel` or `Enumerable` on any reopening. So each candidate name files the negative method edge of every
|
|
301
301
|
# {Relevance::HOOKS} name on its singleton side (`method:Comparable.included`, the key a new `def
|
|
302
302
|
# self.included` satisfies); a file adding any other method to the module re-checks nothing, and once the
|
|
303
|
-
# module is declared its sites carry later edits and the removal.
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
303
|
+
# module is declared its sites carry later edits and the removal. #1661 — each name also files `mixin:<name>`,
|
|
304
|
+
# which `IncrementalSession#mark_affected` fires when the module's unpositioned mixin edges move: a new file
|
|
305
|
+
# that taints it (`module Comparable; include(*MODS); end`) without a hook. The key is the qualified candidate,
|
|
306
|
+
# the spelling the chain resolves a project entry by. The unpositioned table may key the owner by a relative
|
|
307
|
+
# spelling (`Constants` inside `class File`), so the producer also fires every recorded `mixin:` key ending in
|
|
308
|
+
# `::<owner>` (`IncrementalSession#mixin_keys`); a taint on a project `Foo::Base` still re-checks no reader of
|
|
309
|
+
# an external `Lib::Base`. An ambiguous spelling has no candidate and files none: no project module can resolve
|
|
310
|
+
# it, so no taint can mark it. The edges are a function of the chain alone, so a consumer files them once per
|
|
311
|
+
# chain ({Analysis::DependencyRecorder.file_chain_once}, #1590). A hook written another way — an instance `def
|
|
312
|
+
# included` in a module extended onto the external, a `define_singleton_method(:included)` — satisfies no such
|
|
313
|
+
# key and is not seen.
|
|
307
314
|
def record_hooks(scope)
|
|
308
315
|
externals = (@memo[:externals] ||= @entries.select(&:external?).freeze)
|
|
309
316
|
return if externals.empty?
|
|
310
317
|
|
|
311
318
|
Analysis::DependencyRecorder.file_chain_once(self, :hooks, scope.discovery) do
|
|
312
319
|
externals.each do |entry|
|
|
313
|
-
entry.candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.
|
|
320
|
+
entry.candidates.each { |name| Analysis::DependencyRecorder.read_keys(ResolutionChain.external_keys(name)) }
|
|
314
321
|
end
|
|
315
322
|
end
|
|
316
323
|
end
|
|
@@ -393,12 +400,18 @@ module Rigor
|
|
|
393
400
|
end
|
|
394
401
|
|
|
395
402
|
@hook_keys = {}
|
|
403
|
+
@external_keys = {}
|
|
396
404
|
|
|
397
405
|
# The frozen `method:Name.hook` negative keys of `name`'s {Relevance::HOOKS}, built once per name.
|
|
398
406
|
def self.hook_keys(name)
|
|
399
407
|
@hook_keys[name] ||= Relevance::HOOKS.map { |hook| "method:#{name}.#{hook}".freeze }.freeze
|
|
400
408
|
end
|
|
401
409
|
|
|
410
|
+
# {hook_keys} plus the `mixin:<name>` key an external entry files for its unpositioned mixins (#1661).
|
|
411
|
+
def self.external_keys(name)
|
|
412
|
+
@external_keys[name] ||= (hook_keys(name) + ["mixin:#{name}".freeze]).freeze
|
|
413
|
+
end
|
|
414
|
+
|
|
402
415
|
def record_head(scope, start, side)
|
|
403
416
|
ResolutionChain.record_class(scope, @root) unless start.zero? && @entries.first&.name == @root
|
|
404
417
|
start.times { |index| ResolutionChain.record_entry(scope, @entries[index], side) }
|
|
@@ -440,6 +453,28 @@ module Rigor
|
|
|
440
453
|
@entries[@level_starts[index]...finish]
|
|
441
454
|
end
|
|
442
455
|
|
|
456
|
+
# Issue #1624 — whether the chain's last class has a superclass the tables cannot name, so ancestors the chain
|
|
457
|
+
# does not list follow it where a chain otherwise ends in the implicit `Object`: a superclass written as an
|
|
458
|
+
# expression (`class K < Struct.new(:a)`, `< Data.define(:a)`, `< Class.new(Base)`, `< pick_base`), which the
|
|
459
|
+
# superclass table records as `nil`, or a class a constant write creates (`K = Class.new(Base)`,
|
|
460
|
+
# `K = Struct.new(:a)`, `K = Data.define(:a)`), which has no superclass entry. The constructor's methods
|
|
461
|
+
# (`members`, the member readers, a block's `def`s) and everything it inherits live in that unlisted part.
|
|
462
|
+
# Every fact read here is filed under the last class's class edge, which a read reaching the tail has passed.
|
|
463
|
+
def opaque_tail?(scope)
|
|
464
|
+
@memo.fetch(:opaque_tail) do
|
|
465
|
+
last = level_count.positive? ? @level_classes[level_count - 1] : nil
|
|
466
|
+
@memo[:opaque_tail] = !last.nil? && ResolutionChain.opaque_superclass?(scope, last)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def self.opaque_superclass?(scope, name)
|
|
471
|
+
superclasses = scope.discovery.discovered_superclasses
|
|
472
|
+
return superclasses[name].nil? if superclasses.key?(name)
|
|
473
|
+
|
|
474
|
+
scope.discovery.discovered_parameter_envelopes[name]&.key?(DiscoveryIndex::ENVELOPE_META_SUPERCLASS_MARK) ||
|
|
475
|
+
!(scope.struct_member_layout(name) || scope.data_member_layout(name)).nil?
|
|
476
|
+
end
|
|
477
|
+
|
|
443
478
|
# Each complete level up to the first superclass the project does not declare, as `[class_name,
|
|
444
479
|
# modules, externals]`: the level's class, the project modules around it (each once), and the candidate
|
|
445
480
|
# lists of the ancestors in it the project does not declare — the shape `call.wrong-arity` reads.
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -876,13 +876,18 @@ module Rigor
|
|
|
876
876
|
STDOUT_ALIASES = { :$stdout => :$>, :$> => :$stdout }.freeze
|
|
877
877
|
private_constant :STDOUT_ALIASES
|
|
878
878
|
|
|
879
|
-
# Issue #1429 — {#forget_guard_narrowings} for the one name `[kind, name]`.
|
|
879
|
+
# Issue #1429 — {#forget_guard_narrowings} for the one name `[kind, name]`. Issue #1596 — an `:ivar` kind too,
|
|
880
|
+
# for a self-call proven to write only some instance variables.
|
|
880
881
|
def forget_guard_narrowing_of(kind, name)
|
|
881
882
|
key = [kind, name]
|
|
882
883
|
pre_guard = @guard_records[key]
|
|
883
884
|
return self if pre_guard.nil?
|
|
884
885
|
|
|
885
|
-
if kind == :
|
|
886
|
+
if kind == :ivar
|
|
887
|
+
current = @ivars[name]
|
|
888
|
+
ivars = current ? @ivars.merge(name => Type::Combinator.union(pre_guard, current)).freeze : @ivars
|
|
889
|
+
rebuild(ivars: ivars, guard_records: @guard_records.except(key).freeze)
|
|
890
|
+
elsif kind == :global
|
|
886
891
|
current = @globals[name]
|
|
887
892
|
globals = current ? @globals.merge(name => Type::Combinator.union(pre_guard, current)).freeze : @globals
|
|
888
893
|
rebuild(globals: globals, guard_records: @guard_records.except(key).freeze)
|
|
@@ -1208,13 +1213,28 @@ module Rigor
|
|
|
1208
1213
|
# wrong-arity rules consult this map to suppress diagnostics for methods the user has defined dynamically,
|
|
1209
1214
|
# even when no RBS sig describes them.
|
|
1210
1215
|
# A name defined on both sides of one class records {DiscoveryIndex::METHOD_KIND_BOTH} and matches either kind.
|
|
1216
|
+
#
|
|
1217
|
+
# Issue #2121 — a hit is an ADR-46 read. The table carries what no `def` symbol edge covers — an `attr_*`, an
|
|
1218
|
+
# `alias` or a `define_method` (a cross-file `def` is withheld from it) — so an answer that read a hit records
|
|
1219
|
+
# the class edge to the files declaring `class_name` and the name edge `defines:<method_name>`: deleting the
|
|
1220
|
+
# `attr_accessor` re-checks the caller it silenced, whether or not the deleting file declares the class. A miss
|
|
1221
|
+
# records nothing here; the readers that act on one record their own negative edges.
|
|
1211
1222
|
def discovered_method?(class_name, method_name, kind)
|
|
1212
1223
|
table = @discovery.discovered_methods[class_name.to_s]
|
|
1213
1224
|
return false unless table
|
|
1214
1225
|
|
|
1215
1226
|
recorded = table[method_name.to_sym]
|
|
1216
|
-
recorded == kind || recorded == DiscoveryIndex::METHOD_KIND_BOTH
|
|
1227
|
+
return false unless recorded == kind || recorded == DiscoveryIndex::METHOD_KIND_BOTH
|
|
1228
|
+
|
|
1229
|
+
record_discovered_method(class_name, method_name) if Analysis::DependencyRecorder.active?
|
|
1230
|
+
true
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
def record_discovered_method(class_name, method_name)
|
|
1234
|
+
record_class_dependency(class_name)
|
|
1235
|
+
Analysis::DependencyRecorder.read_name(:defines, method_name)
|
|
1217
1236
|
end
|
|
1237
|
+
private :record_discovered_method
|
|
1218
1238
|
|
|
1219
1239
|
# ADR-34 § "Decision" — predicate identifying a toplevel-shaped scope (no enclosing `class` / `module` body).
|
|
1220
1240
|
# True at the top of a file AND inside a top-level `def` body (since toplevel defs leave `self_type` nil per
|
|
@@ -1405,6 +1425,27 @@ module Rigor
|
|
|
1405
1425
|
call_node)
|
|
1406
1426
|
end
|
|
1407
1427
|
|
|
1428
|
+
# Issue #1569 — whether `def_node`, written in this file, is a module function of `class_name`: a `:both`
|
|
1429
|
+
# deferred-range row over the def (a bare `module_function` before it, or `module_function def x`;
|
|
1430
|
+
# `ModuleFunctionState.prescan_deferred`), or a `:singleton` row for the name after the def
|
|
1431
|
+
# (`module_function :x`) whose singleton copy is this def (`ModuleFunctionState.each_singleton_copy`).
|
|
1432
|
+
def module_function_def?(class_name, def_node)
|
|
1433
|
+
path = source_path
|
|
1434
|
+
return false if path.nil?
|
|
1435
|
+
|
|
1436
|
+
ranges = discovered_deferred_ranges[path]
|
|
1437
|
+
return false if ranges.nil?
|
|
1438
|
+
|
|
1439
|
+
start = def_node.location.start_offset
|
|
1440
|
+
ranges.any? do |(row_start, _finish, name, kind, owner)|
|
|
1441
|
+
next false unless name == def_node.name && owner == class_name.to_s
|
|
1442
|
+
|
|
1443
|
+
(kind == :both && row_start == start) ||
|
|
1444
|
+
(kind == :singleton && row_start > start &&
|
|
1445
|
+
singleton_def_for(class_name, name)&.location&.start_offset == start)
|
|
1446
|
+
end
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1408
1449
|
# Shared ordering half of the two `*_def_shadows_call?` predicates, over this file's
|
|
1409
1450
|
# `discovered_deferred_ranges` (issue #1097). `exists` — a recorded `"path:line"` site or the
|
|
1410
1451
|
# method-existence table — says a def of `method_name`/`kind` on `class_name` is known at all; a
|
|
@@ -1912,8 +1953,15 @@ module Rigor
|
|
|
1912
1953
|
# Issue #992 — one class's `discovered_parameter_envelopes` bucket (`{[kind, method_name] => envelope}` plus
|
|
1913
1954
|
# the class-wide marks), or an empty Hash. Records the ADR-46 class edge first: a verdict read off the
|
|
1914
1955
|
# bucket depends on every file that declares the class, and a `memoize :f` added to one of them moves it.
|
|
1956
|
+
#
|
|
1957
|
+
# Issue #1833 — and the name edge `mark:<last segment>`: a file that does not declare the class may still mark it
|
|
1958
|
+
# (`x.singleton_class.prepend(Comparable)`, `o.extend(Helper)` in a method body, a `refine` block), and
|
|
1959
|
+
# `IncrementalSession#mark_affected` fires the key when such a mark appears or vanishes.
|
|
1915
1960
|
def parameter_envelopes_of(class_name)
|
|
1916
|
-
|
|
1961
|
+
if Analysis::DependencyRecorder.active?
|
|
1962
|
+
record_class_dependency(class_name)
|
|
1963
|
+
Analysis::DependencyRecorder.read_last_segment(:mark, class_name.to_s)
|
|
1964
|
+
end
|
|
1917
1965
|
@discovery.discovered_parameter_envelopes[class_name.to_s] || EMPTY_PARAMETER_ENVELOPES
|
|
1918
1966
|
end
|
|
1919
1967
|
|
|
@@ -1921,10 +1969,10 @@ module Rigor
|
|
|
1921
1969
|
private_constant :EMPTY_PARAMETER_ENVELOPES
|
|
1922
1970
|
|
|
1923
1971
|
# v0.1.2 — per-class table mapping `method_name (Symbol) → :public | :private | :protected`. Populated by
|
|
1924
|
-
# `ScopeIndexer` for every `def` it sees inside a class body, with the visibility taken from
|
|
1925
|
-
# `private` / `
|
|
1926
|
-
#
|
|
1927
|
-
# surface a diagnostic.
|
|
1972
|
+
# `ScopeIndexer` for every `def` and `attr_*` member it sees inside a class body, with the visibility taken from
|
|
1973
|
+
# a wrapping `private def` / `private attr_reader` call, else the surrounding `private` / `protected` / `public`
|
|
1974
|
+
# modifier state, plus any post-hoc `private :name, ...` named-argument calls. Consumed by the
|
|
1975
|
+
# `def.method-visibility-mismatch` rule so explicit-non-self calls to a private method surface a diagnostic.
|
|
1928
1976
|
def discovered_method_visibility(class_name, method_name)
|
|
1929
1977
|
table = @discovery.discovered_method_visibilities[class_name.to_s]
|
|
1930
1978
|
return nil unless table
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../inference/rbs_type_translator"
|
|
4
4
|
require_relative "type_elaborator"
|
|
5
|
+
require_relative "../rooted_glob"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module SigGen
|
|
@@ -101,7 +102,7 @@ module Rigor
|
|
|
101
102
|
dir = path.is_a?(Pathname) ? path : Pathname(path.to_s)
|
|
102
103
|
next [] unless dir.directory?
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
RootedGlob.glob(dir, "**/*.rbs").map { |p| File.expand_path(p) }
|
|
105
106
|
end.to_set
|
|
106
107
|
end
|
|
107
108
|
|
|
@@ -60,7 +60,10 @@ module Rigor
|
|
|
60
60
|
inline_differs: "sig.skipped.inline-differs",
|
|
61
61
|
# #1676 — a plain top-level `def` defines a private `Object` method and has no class to write a
|
|
62
62
|
# declaration under, so it is reported rather than silently dropped. Whether to emit it stays open.
|
|
63
|
-
top_level_def: "sig.skipped.top-level-def"
|
|
63
|
+
top_level_def: "sig.skipped.top-level-def",
|
|
64
|
+
# #1968 — a bare `public` / `private` / `protected` that may not run (in control flow or a block) sits
|
|
65
|
+
# between a bare `module_function` and the def, so whether the def is a module function is unknown.
|
|
66
|
+
uncertain_module_function: "sig.skipped.uncertain-module-function"
|
|
64
67
|
}.freeze
|
|
65
68
|
end
|
|
66
69
|
end
|