rigortype 0.2.9 → 0.3.0
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 +2 -2
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +1 -0
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +98 -4
- data/docs/manual/02-cli-reference.md +49 -12
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +4 -4
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +33 -3
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +6 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +19 -27
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +361 -113
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +127 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +451 -36
- data/lib/rigor/inference/statement_evaluator.rb +64 -15
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +42 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -24,8 +24,22 @@ module Rigor
|
|
|
24
24
|
# Bump when the on-disk shape changes so stale snapshots are ignored rather than mis-deserialized. 5:
|
|
25
25
|
# the blob is zlib-deflated (ADR-54 WD2 parity with `Store` entries — the snapshot is the one cache
|
|
26
26
|
# artefact that does not go through `Store`); a raw pre-5 blob fails the inflate and loads as nil, the
|
|
27
|
-
# usual fault-tolerant cold-run path.
|
|
28
|
-
|
|
27
|
+
# usual fault-tolerant cold-run path. 6: adds the ADR-85 WD2 `seed_bundles` section (per-file discovery
|
|
28
|
+
# contributions with `(node_id, name, fingerprint)` def-node handles); a pre-6 blob mismatches the
|
|
29
|
+
# SCHEMA gate and loads as nil (a clean cold rebuild — no migration). 7: the seed bundle gains a
|
|
30
|
+
# `singleton_def_sources` table (ADR-46 slice 4 extended to class/singleton methods) AND `digests`
|
|
31
|
+
# switches to ADR-87 packed stat entries; a pre-7 blob mismatches the gate and loads as nil (clean cold
|
|
32
|
+
# rebuild). 8: each seed bundle additionally gains a comment-stripped `code_fingerprint` for the B1
|
|
33
|
+
# bundle-equality gate; a pre-8 blob mismatches and loads as nil (clean cold rebuild). 9: adds the ADR-88
|
|
34
|
+
# WD1 `plugin_fact_digest` (a fingerprint of the plugin fact SURFACE — ADR-9 facts, ADR-60 producer
|
|
35
|
+
# values, and `incremental_state_fingerprint` hooks — that a cached diagnostic can depend on but the
|
|
36
|
+
# global fingerprint does not capture); a pre-9 blob mismatches the SCHEMA gate and loads as nil (a clean
|
|
37
|
+
# cold rebuild — no migration). 10: ADR-89 WD1 adds a per-file `declaration_signature` to each seed
|
|
38
|
+
# bundle (the per-def parameter-shape / visibility / ancestry surface the declaration-stability gate
|
|
39
|
+
# compares) and WD2 adds `return_summaries` (per-def observed-key return descriptors + mutation-effect
|
|
40
|
+
# sets the behavioural-stability gate compares); a pre-10 blob mismatches the SCHEMA gate and loads as
|
|
41
|
+
# nil (a clean cold rebuild — no migration).
|
|
42
|
+
SCHEMA = 10
|
|
29
43
|
|
|
30
44
|
# The persisted per-file state.
|
|
31
45
|
# `cache` maps an analyzed file to its diagnostics.
|
|
@@ -39,9 +53,25 @@ module Rigor
|
|
|
39
53
|
# ADR-46 slice 3:
|
|
40
54
|
# `missing` maps a consumer to Set<"kind:name"> it looked up and missed.
|
|
41
55
|
# `class_decls` maps a path to Set<qualified class name> it declares.
|
|
56
|
+
# ADR-85 WD2:
|
|
57
|
+
# `seed_bundles` maps an analyzed path to its per-file discovery contribution (plain-data tables +
|
|
58
|
+
# `(node_id, name, fingerprint)` def-node handles + content digest), so a warm recheck rebuilds the
|
|
59
|
+
# cross-file index by folding bundles instead of parsing every file.
|
|
60
|
+
# ADR-88 WD1:
|
|
61
|
+
# `plugin_fact_digest` is a SHA-256 hex fingerprint of the plugin fact surface at the run that wrote the
|
|
62
|
+
# snapshot (or nil for a plugin-free project). A warm recheck recomputes it and, on a mismatch, discards
|
|
63
|
+
# the snapshot and runs a full analysis — the guard for a plugin sig/catalog edit that the global
|
|
64
|
+
# fingerprint cannot see.
|
|
65
|
+
# ADR-89 WD2:
|
|
66
|
+
# `return_summaries` maps a `[path, "Class#method" | "Class.method"]` to the callee's persisted
|
|
67
|
+
# behavioural surface `{ keys:, returns:, effects: }` — the observed `[receiver, arg_types]` call keys
|
|
68
|
+
# (Marshal-clean type tuples), their `describe(:short)` return descriptors, and the content-mutated
|
|
69
|
+
# parameter positions. A recheck re-evaluates a declaration-stable changed callee at these keys and,
|
|
70
|
+
# when every return + the effects are unchanged, skips its symbol dependents.
|
|
42
71
|
Payload = Data.define(:cache, :sources, :digests, :analyzed,
|
|
43
72
|
:symbol_sources, :ancestry_sources, :symbol_fingerprints,
|
|
44
|
-
:missing, :class_decls
|
|
73
|
+
:missing, :class_decls, :seed_bundles, :plugin_fact_digest,
|
|
74
|
+
:return_summaries)
|
|
45
75
|
|
|
46
76
|
# The global fingerprint that gates a snapshot load: a digest of the inputs whose change requires a full
|
|
47
77
|
# rebuild — the engine version + schema, the resolved configuration, the analysis **roots** (the path
|
|
@@ -104,7 +134,10 @@ module Rigor
|
|
|
104
134
|
ancestry_sources: data[:ancestry_sources] || {},
|
|
105
135
|
symbol_fingerprints: data[:symbol_fingerprints] || {},
|
|
106
136
|
missing: data[:missing] || {},
|
|
107
|
-
class_decls: data[:class_decls] || {}
|
|
137
|
+
class_decls: data[:class_decls] || {},
|
|
138
|
+
seed_bundles: data[:seed_bundles] || {},
|
|
139
|
+
plugin_fact_digest: data[:plugin_fact_digest],
|
|
140
|
+
return_summaries: data[:return_summaries] || {}
|
|
108
141
|
)
|
|
109
142
|
rescue StandardError
|
|
110
143
|
nil
|
|
@@ -122,7 +155,10 @@ module Rigor
|
|
|
122
155
|
ancestry_sources: payload.ancestry_sources,
|
|
123
156
|
symbol_fingerprints: payload.symbol_fingerprints,
|
|
124
157
|
missing: payload.missing,
|
|
125
|
-
class_decls: payload.class_decls
|
|
158
|
+
class_decls: payload.class_decls,
|
|
159
|
+
seed_bundles: payload.seed_bundles,
|
|
160
|
+
plugin_fact_digest: payload.plugin_fact_digest,
|
|
161
|
+
return_summaries: payload.return_summaries
|
|
126
162
|
)
|
|
127
163
|
blob = Zlib::Deflate.deflate(raw)
|
|
128
164
|
tmp = "#{@path}.#{Process.pid}.tmp"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
5
|
require_relative "descriptor"
|
|
6
|
+
require_relative "file_digest"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Cache
|
|
@@ -17,15 +18,36 @@ module Rigor
|
|
|
17
18
|
Descriptor.new(
|
|
18
19
|
gems: [rbs_gem_entry],
|
|
19
20
|
files: file_entries(loader),
|
|
20
|
-
configs:
|
|
21
|
+
configs: config_entries(loader)
|
|
21
22
|
)
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
# Lazy-files variant for the ADR-45 run-diagnostics record-and-validate cache. The cache KEY reads only
|
|
26
|
+
# `gems` + `configs` ({Runner#run_key_descriptor}); the RBS signature-tree `files` are read solely on a
|
|
27
|
+
# MISS, by the dependency descriptor ({Runner#run_dependency_descriptor}). So a warm HIT never digests
|
|
28
|
+
# the (large, vendored) RBS tree. {RunDescriptor} is NOT a {Descriptor} — it is never composed, hashed,
|
|
29
|
+
# or `==`'d, only its three readers are consulted — so deferring `files` costs no soundness, and `gems`
|
|
30
|
+
# / `configs` are byte-identical to {.build} (the key is unchanged).
|
|
31
|
+
def self.build_run(loader)
|
|
32
|
+
RunDescriptor.new(loader: loader, gems: [rbs_gem_entry], configs: config_entries(loader))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The `gems` + `configs` slots the run cache key reads. Cheap — no RBS env load, no file digesting
|
|
36
|
+
# (only the configured library names + any plugin-synthesised virtual RBS are hashed).
|
|
37
|
+
def self.config_entries(loader)
|
|
38
|
+
[libraries_entry(loader.libraries), virtual_rbs_entry(loader)].compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Public (ADR-87 WD4) so the boot-slimming run-cache probe reconstructs the identical `gems` +
|
|
42
|
+
# `rbs.libraries` key slots the runner writes, from the config-derived library list, without a loader.
|
|
24
43
|
def self.rbs_gem_entry
|
|
25
44
|
Descriptor::GemEntry.new(name: "rbs", requirement: ">= 0", locked: ::RBS::VERSION.to_s)
|
|
26
45
|
end
|
|
27
46
|
|
|
28
|
-
|
|
47
|
+
# @param comparator [Symbol] `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
|
|
48
|
+
# value must be deterministic; `:stat` (ADR-87 WD1) for the validation-only run-dependency descriptor
|
|
49
|
+
# ({RunDescriptor#files}), where the stat tier short-circuits the SHA-256 on an unmoved file.
|
|
50
|
+
def self.file_entries(loader, comparator: :digest)
|
|
29
51
|
roots = loader.signature_paths +
|
|
30
52
|
Rigor::Environment::RbsLoader.vendored_gem_sig_paths +
|
|
31
53
|
Rigor::Environment::RbsLoader.core_overlay_sig_paths
|
|
@@ -33,17 +55,20 @@ module Rigor
|
|
|
33
55
|
next [] unless root.directory?
|
|
34
56
|
|
|
35
57
|
Dir.glob(root.join("**", "*.rbs")).map do |path|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
digest = FileDigest.hexdigest(path)
|
|
59
|
+
if comparator == :stat
|
|
60
|
+
Descriptor::FileEntry.stat(path: path, digest: digest)
|
|
61
|
+
else
|
|
62
|
+
Descriptor::FileEntry.new(path: path, comparator: :digest, value: digest)
|
|
63
|
+
end
|
|
41
64
|
end
|
|
42
65
|
end
|
|
43
66
|
end
|
|
44
67
|
|
|
45
|
-
|
|
46
|
-
|
|
68
|
+
# @param library_names [Array<String, Symbol>] the loader's merged library list (or, on the WD4 probe
|
|
69
|
+
# path, `Environment::DEFAULT_LIBRARIES + config.libraries` reconstructed without a loader).
|
|
70
|
+
def self.libraries_entry(library_names)
|
|
71
|
+
sorted = library_names.map(&:to_s).sort
|
|
47
72
|
Descriptor::ConfigEntry.new(
|
|
48
73
|
key: "rbs.libraries",
|
|
49
74
|
value_hash: Digest::SHA256.hexdigest(sorted.join("\n"))
|
|
@@ -66,7 +91,28 @@ module Rigor
|
|
|
66
91
|
)
|
|
67
92
|
end
|
|
68
93
|
|
|
69
|
-
private_class_method :
|
|
94
|
+
private_class_method :virtual_rbs_entry
|
|
95
|
+
|
|
96
|
+
# The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the three readers the
|
|
97
|
+
# run-diagnostics cache consults — `gems` + `configs` are supplied eagerly (they feed the cache KEY,
|
|
98
|
+
# and are cheap); `files` (the RBS signature-tree digests, read only on a MISS) is computed once on
|
|
99
|
+
# first access and memoised, so a warm HIT never pays for it.
|
|
100
|
+
class RunDescriptor
|
|
101
|
+
attr_reader :gems, :configs
|
|
102
|
+
|
|
103
|
+
def initialize(loader:, gems:, configs:)
|
|
104
|
+
@loader = loader
|
|
105
|
+
@gems = gems
|
|
106
|
+
@configs = configs
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def files
|
|
110
|
+
# ADR-87 WD1 — this descriptor is validated (never a cache key), so the RBS signature tree rides the
|
|
111
|
+
# stat-then-digest `:stat` tier: a warm run stat-checks the (large, vendored) tree instead of
|
|
112
|
+
# re-hashing it.
|
|
113
|
+
@files ||= RbsDescriptor.file_entries(@loader, comparator: :stat)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
70
116
|
end
|
|
71
117
|
end
|
|
72
118
|
end
|
data/lib/rigor/cache/store.rb
CHANGED
|
@@ -224,10 +224,9 @@ module Rigor
|
|
|
224
224
|
key = key_descriptor.cache_key_for(producer_id: producer_id, params: params)
|
|
225
225
|
path = disk ? entry_path(producer_id, key) : nil
|
|
226
226
|
cached = path && read_entry(path, deserialize: deserialize)
|
|
227
|
-
if
|
|
228
|
-
pair[1].is_a?(Descriptor) && pair[1].fresh?
|
|
227
|
+
if (validated = fresh_pair_value(cached))
|
|
229
228
|
@monitor.synchronize { record(:hits, producer_id) }
|
|
230
|
-
return
|
|
229
|
+
return validated[0]
|
|
231
230
|
end
|
|
232
231
|
|
|
233
232
|
value, dependency_descriptor = block_given? ? yield : [nil, Descriptor.new]
|
|
@@ -239,6 +238,24 @@ module Rigor
|
|
|
239
238
|
value
|
|
240
239
|
end
|
|
241
240
|
|
|
241
|
+
# ADR-87 WD4 — the READ half of {#fetch_or_validate} with no compute and no write: returns the cached
|
|
242
|
+
# value on a fresh hit, nil on a miss / stale / unavailable-disk. The boot-slimming hit probe calls this
|
|
243
|
+
# to serve a run's diagnostics WITHOUT loading the inference engine — it never runs a producer block, so
|
|
244
|
+
# there is nothing to write. Records a hit (for `--cache-stats` parity) but never a miss (a probe miss
|
|
245
|
+
# hands off to the full path, which records its own).
|
|
246
|
+
def peek_validated(producer_id:, key_descriptor:, params: {}, deserialize: nil)
|
|
247
|
+
validate_producer_id!(producer_id)
|
|
248
|
+
return nil unless ensure_schema_version!
|
|
249
|
+
|
|
250
|
+
key = key_descriptor.cache_key_for(producer_id: producer_id, params: params)
|
|
251
|
+
cached = read_entry(entry_path(producer_id, key), deserialize: deserialize)
|
|
252
|
+
validated = fresh_pair_value(cached)
|
|
253
|
+
return nil if validated.nil?
|
|
254
|
+
|
|
255
|
+
@monitor.synchronize { record(:hits, producer_id) }
|
|
256
|
+
validated[0]
|
|
257
|
+
end
|
|
258
|
+
|
|
242
259
|
# ADR-6 § "Eviction" — compaction pass over the on-disk cache. No-op when the store is read-only. Stale
|
|
243
260
|
# temp file cleanup and the whole-project generation cap run regardless of `max_bytes:` — they reclaim
|
|
244
261
|
# provably-dead bytes (leaked temp files, unreachable content-keyed generations) rather than enforcing a
|
|
@@ -274,6 +291,19 @@ module Rigor
|
|
|
274
291
|
Entry = Data.define(:descriptor_bytes, :value)
|
|
275
292
|
private_constant :Entry
|
|
276
293
|
|
|
294
|
+
# A record-and-validate entry is a fresh hit iff it deserialised to a `[value, dependency_descriptor]`
|
|
295
|
+
# pair whose descriptor still validates against the filesystem. Returns the pair on a fresh hit, nil
|
|
296
|
+
# otherwise. Shared by {#fetch_or_validate} and {#peek_validated} so both apply identical hit criteria.
|
|
297
|
+
def fresh_pair_value(cached)
|
|
298
|
+
return nil if cached.nil?
|
|
299
|
+
|
|
300
|
+
pair = cached.value
|
|
301
|
+
return nil unless pair.is_a?(Array) && pair.size == 2 &&
|
|
302
|
+
pair[1].is_a?(Descriptor) && pair[1].fresh?
|
|
303
|
+
|
|
304
|
+
pair
|
|
305
|
+
end
|
|
306
|
+
|
|
277
307
|
def record(counter, producer_id)
|
|
278
308
|
case counter
|
|
279
309
|
when :hits then @hits += 1
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# Runtime CI-environment detection (ADR-51 WD7), modelled on `OndraM/ci-detector` (the library PHPStan uses). Reads
|
|
5
|
+
# the well-known environment variables a CI provider sets and returns the matching {Platform}, classifying it into a
|
|
6
|
+
# **tier** that decides how `rigor check` surfaces diagnostics there:
|
|
7
|
+
#
|
|
8
|
+
# :native_stdout — Rigor has a native format that renders purely from
|
|
9
|
+
# stdout, so it is auto-emitted on top of the human
|
|
10
|
+
# output (GitHub Actions → `github`, TeamCity →
|
|
11
|
+
# `teamcity`). These are the first-class platforms.
|
|
12
|
+
# :native_artifact — Rigor has a native format but it needs a CI-wired
|
|
13
|
+
# report artifact, not stdout (GitLab CI → `gitlab`).
|
|
14
|
+
# First-class, but Rigor only *hints* the format.
|
|
15
|
+
# :reviewdog — no native Rigor format; second-class, routed through
|
|
16
|
+
# reviewdog (`checkstyle`/`sarif`) or `junit`. Hint
|
|
17
|
+
# only.
|
|
18
|
+
#
|
|
19
|
+
# Lives outside the CLI namespace because it is a pure function of the environment hash with no CLI dependency, and
|
|
20
|
+
# non-CLI consumers exist: {Configuration#cache_validation_strict?} resolves the `auto` cache-validation default
|
|
21
|
+
# through it (a fresh CI checkout regenerates every stat tuple, so ADR-87's stat tier cannot work there).
|
|
22
|
+
# Detection is fully testable; callers pass `ENV`.
|
|
23
|
+
# `RIGOR_CI_DETECT=0` (or `false`/`no`) disables it globally — the seam the spec suite uses for determinism.
|
|
24
|
+
module CiDetector
|
|
25
|
+
Platform = Struct.new(:id, :name, :format, :tier, keyword_init: true) do
|
|
26
|
+
def native_stdout? = tier == :native_stdout
|
|
27
|
+
def native_artifact? = tier == :native_artifact
|
|
28
|
+
def reviewdog? = tier == :reviewdog
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The detection table, ordered most-specific first so the generic `CI=true` catch-all is last (a provider that
|
|
32
|
+
# also sets `CI` is still recognised by its own variable). `match` is `:truthy` (value in 1/true/yes/on),
|
|
33
|
+
# `:present` (variable set non-empty), or `:equals`.
|
|
34
|
+
PROVIDERS = [
|
|
35
|
+
{ id: "github-actions", name: "GitHub Actions", format: "github", tier: :native_stdout,
|
|
36
|
+
var: "GITHUB_ACTIONS", match: :truthy },
|
|
37
|
+
{ id: "gitlab", name: "GitLab CI", format: "gitlab", tier: :native_artifact,
|
|
38
|
+
var: "GITLAB_CI", match: :truthy },
|
|
39
|
+
{ id: "teamcity", name: "TeamCity", format: "teamcity", tier: :native_stdout,
|
|
40
|
+
var: "TEAMCITY_VERSION", match: :present },
|
|
41
|
+
{ id: "circleci", name: "CircleCI", format: nil, tier: :reviewdog,
|
|
42
|
+
var: "CIRCLECI", match: :truthy },
|
|
43
|
+
{ id: "jenkins", name: "Jenkins", format: nil, tier: :reviewdog,
|
|
44
|
+
var: "JENKINS_URL", match: :present },
|
|
45
|
+
{ id: "travis", name: "Travis CI", format: nil, tier: :reviewdog,
|
|
46
|
+
var: "TRAVIS", match: :truthy },
|
|
47
|
+
{ id: "appveyor", name: "AppVeyor", format: nil, tier: :reviewdog,
|
|
48
|
+
var: "APPVEYOR", match: :truthy },
|
|
49
|
+
{ id: "azure-pipelines", name: "Azure Pipelines", format: nil, tier: :reviewdog,
|
|
50
|
+
var: "TF_BUILD", match: :present },
|
|
51
|
+
{ id: "bitbucket", name: "Bitbucket Pipelines", format: nil, tier: :reviewdog,
|
|
52
|
+
var: "BITBUCKET_BUILD_NUMBER", match: :present },
|
|
53
|
+
{ id: "buildkite", name: "Buildkite", format: nil, tier: :reviewdog,
|
|
54
|
+
var: "BUILDKITE", match: :truthy },
|
|
55
|
+
{ id: "drone", name: "Drone CI", format: nil, tier: :reviewdog,
|
|
56
|
+
var: "DRONE", match: :truthy },
|
|
57
|
+
{ id: "semaphore", name: "Semaphore", format: nil, tier: :reviewdog,
|
|
58
|
+
var: "SEMAPHORE", match: :truthy },
|
|
59
|
+
{ id: "codeship", name: "Codeship", format: nil, tier: :reviewdog,
|
|
60
|
+
var: "CI_NAME", match: :equals, value: "codeship" },
|
|
61
|
+
{ id: "ci", name: "CI", format: nil, tier: :reviewdog,
|
|
62
|
+
var: "CI", match: :truthy }
|
|
63
|
+
].freeze
|
|
64
|
+
|
|
65
|
+
module_function
|
|
66
|
+
|
|
67
|
+
# Returns the detected {Platform}, or nil when no CI is recognised or detection is disabled via `RIGOR_CI_DETECT`.
|
|
68
|
+
def detect(env = ENV)
|
|
69
|
+
return nil if disabled?(env)
|
|
70
|
+
|
|
71
|
+
row = PROVIDERS.find { |provider| matches?(env, provider) }
|
|
72
|
+
return nil if row.nil?
|
|
73
|
+
|
|
74
|
+
Platform.new(id: row[:id], name: row[:name], format: row[:format], tier: row[:tier])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def disabled?(env)
|
|
78
|
+
%w[0 false no off].include?(env["RIGOR_CI_DETECT"].to_s.strip.downcase)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def matches?(env, provider)
|
|
82
|
+
value = env[provider[:var]].to_s.strip
|
|
83
|
+
case provider[:match]
|
|
84
|
+
when :truthy then %w[1 true yes on].include?(value.downcase)
|
|
85
|
+
when :present then !value.empty?
|
|
86
|
+
when :equals then value.downcase == provider[:value]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -9,11 +9,13 @@ require_relative "../configuration"
|
|
|
9
9
|
require_relative "options"
|
|
10
10
|
require_relative "../environment"
|
|
11
11
|
require_relative "../scope"
|
|
12
|
+
require_relative "../source/node_children"
|
|
12
13
|
require_relative "../inference/def_return_typer"
|
|
13
14
|
require_relative "../inference/scope_indexer"
|
|
14
15
|
require_relative "../inference/statement_evaluator"
|
|
15
16
|
require_relative "prism_colorizer"
|
|
16
17
|
require_relative "command"
|
|
18
|
+
require_relative "probe_environment"
|
|
17
19
|
|
|
18
20
|
module Rigor
|
|
19
21
|
class CLI
|
|
@@ -105,7 +107,7 @@ module Rigor
|
|
|
105
107
|
# bindings, so a loop-body line annotates the joined widened type (`Integer`) rather than a stale
|
|
106
108
|
# first-iterations constant (`1 | 2`).
|
|
107
109
|
scope_index = Inference::ScopeIndexer.index(
|
|
108
|
-
parse_result.value, default_scope: base_scope(configuration),
|
|
110
|
+
parse_result.value, default_scope: base_scope(configuration, file),
|
|
109
111
|
converged_loop_recording: true
|
|
110
112
|
)
|
|
111
113
|
line_types = LineTypeCollector.new(scope_index).collect(parse_result.value)
|
|
@@ -128,12 +130,12 @@ module Rigor
|
|
|
128
130
|
@out.puts(JSON.generate({ "annotations" => annotations }))
|
|
129
131
|
end
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
# The plugin-aware environment for the annotated file, so a `#=> <type>` matches what `rigor check`
|
|
134
|
+
# infers on that line — including types synthesized from the file's own inline RBS annotations (the file
|
|
135
|
+
# is threaded as the synthesizer's `source_files:`; see {ProbeEnvironment}).
|
|
136
|
+
def base_scope(configuration, file)
|
|
132
137
|
Scope.empty(
|
|
133
|
-
environment:
|
|
134
|
-
libraries: configuration.libraries,
|
|
135
|
-
signature_paths: configuration.signature_paths
|
|
136
|
-
)
|
|
138
|
+
environment: ProbeEnvironment.build(configuration: configuration, source_files: [file])
|
|
137
139
|
)
|
|
138
140
|
end
|
|
139
141
|
|
|
@@ -247,14 +249,18 @@ module Rigor
|
|
|
247
249
|
block.call(stmt)
|
|
248
250
|
end
|
|
249
251
|
else
|
|
250
|
-
node.
|
|
252
|
+
node.rigor_each_child { |child| each_statement(child, &block) }
|
|
251
253
|
end
|
|
252
254
|
end
|
|
253
255
|
|
|
254
256
|
# For a line no statement closes (the `if` / block header lines), fall back to the widest expression ending there.
|
|
257
|
+
# A hash-pair node (`1 => 2,` inside a multi-line literal / keyword-argument list) is not an expression — typing
|
|
258
|
+
# it only echoes the `Dynamic[top]` fallback — so those interior lines stay bare and the literal's type appears
|
|
259
|
+
# once, on the line the enclosing statement closes.
|
|
255
260
|
def fill_uncovered_lines(program, by_line)
|
|
256
261
|
widest_per_line(program).each do |line, node|
|
|
257
262
|
next if by_line.key?(line)
|
|
263
|
+
next if node.is_a?(Prism::AssocNode) || node.is_a?(Prism::AssocSplatNode)
|
|
258
264
|
|
|
259
265
|
type = node.is_a?(Prism::BlockParametersNode) ? block_params_type(node) : type_of(node)
|
|
260
266
|
by_line[line] = type unless type.nil?
|
|
@@ -298,7 +304,7 @@ module Rigor
|
|
|
298
304
|
return if node.nil?
|
|
299
305
|
|
|
300
306
|
block.call(node)
|
|
301
|
-
node.
|
|
307
|
+
node.rigor_each_child { |child| walk(child, &block) }
|
|
302
308
|
end
|
|
303
309
|
|
|
304
310
|
# Types the node through the flow evaluator (not the bare expression typer) under its recorded entry scope, so
|
|
@@ -331,7 +337,7 @@ module Rigor
|
|
|
331
337
|
return if node.nil?
|
|
332
338
|
|
|
333
339
|
block.call(node) if node.is_a?(Prism::DefNode)
|
|
334
|
-
node.
|
|
340
|
+
node.rigor_each_child { |child| each_def_node(child, &block) }
|
|
335
341
|
end
|
|
336
342
|
end
|
|
337
343
|
end
|
|
@@ -87,8 +87,11 @@ module Rigor
|
|
|
87
87
|
"match-mode: #{options.fetch(:match_mode)})"
|
|
88
88
|
)
|
|
89
89
|
if configuration.baseline_path.nil?
|
|
90
|
+
# Name the config file that actually governs this project (`.rigor.dist.yml` when no local override
|
|
91
|
+
# exists), so the hint points at the file the user must edit.
|
|
92
|
+
config_name = options.fetch(:config) || Configuration.discover || ".rigor.yml"
|
|
90
93
|
@err.puts(
|
|
91
|
-
"rigor: note —
|
|
94
|
+
"rigor: note — `#{config_name}` does not declare `baseline:`; " \
|
|
92
95
|
"add `baseline: #{path}` to activate the suppression."
|
|
93
96
|
)
|
|
94
97
|
end
|