rigortype 0.1.16 → 0.1.18
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 +4 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +18 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +67 -0
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +226 -0
- data/lib/rigor/analysis/check_rules.rb +180 -73
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +18 -0
- data/lib/rigor/analysis/incremental.rb +162 -0
- data/lib/rigor/analysis/incremental_session.rb +337 -0
- data/lib/rigor/analysis/rule_catalog.rb +48 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +580 -0
- data/lib/rigor/analysis/runner/pool_coordinator.rb +569 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +318 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +46 -0
- data/lib/rigor/analysis/runner.rb +477 -1110
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/analysis/worker_session.rb +47 -8
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +50 -49
- data/lib/rigor/cache/incremental_snapshot.rb +153 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +34 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
- data/lib/rigor/cache/rbs_constant_table.rb +2 -8
- data/lib/rigor/cache/rbs_environment.rb +2 -8
- data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +145 -14
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/check_command.rb +705 -0
- data/lib/rigor/cli/ci_detector.rb +94 -0
- data/lib/rigor/cli/command.rb +47 -0
- data/lib/rigor/cli/coverage_command.rb +3 -23
- data/lib/rigor/cli/coverage_renderer.rb +3 -8
- data/lib/rigor/cli/diagnostic_formats.rb +345 -0
- data/lib/rigor/cli/diff_command.rb +3 -7
- data/lib/rigor/cli/explain_command.rb +2 -7
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mcp_command.rb +3 -7
- data/lib/rigor/cli/options.rb +57 -0
- data/lib/rigor/cli/plugin_command.rb +3 -7
- data/lib/rigor/cli/plugins_command.rb +2 -7
- data/lib/rigor/cli/prism_colorizer.rb +10 -3
- data/lib/rigor/cli/renderable.rb +26 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -7
- data/lib/rigor/cli/skill_command.rb +3 -7
- data/lib/rigor/cli/trace_command.rb +143 -0
- data/lib/rigor/cli/trace_renderer.rb +310 -0
- data/lib/rigor/cli/triage_command.rb +2 -7
- data/lib/rigor/cli/type_of_command.rb +5 -38
- data/lib/rigor/cli/type_of_renderer.rb +4 -9
- data/lib/rigor/cli/type_scan_command.rb +3 -23
- data/lib/rigor/cli/type_scan_renderer.rb +4 -9
- data/lib/rigor/cli.rb +15 -532
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +16 -3
- data/lib/rigor/environment/rbs_loader.rb +129 -71
- data/lib/rigor/environment.rb +1 -1
- data/lib/rigor/inference/acceptance.rb +10 -0
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
- data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
- data/lib/rigor/inference/expression_typer.rb +149 -63
- data/lib/rigor/inference/flow_tracer.rb +180 -0
- data/lib/rigor/inference/macro_block_self_type.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
- data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +33 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher.rb +185 -84
- data/lib/rigor/inference/narrowing.rb +262 -5
- data/lib/rigor/inference/scope_indexer.rb +208 -21
- data/lib/rigor/inference/statement_evaluator.rb +110 -48
- data/lib/rigor/language_server/buffer_resolution.rb +33 -0
- data/lib/rigor/language_server/completion_provider.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
- data/lib/rigor/language_server/folding_range_provider.rb +4 -4
- data/lib/rigor/language_server/hover_provider.rb +4 -4
- data/lib/rigor/language_server/selection_range_provider.rb +4 -4
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/plugin/additional_initializer.rb +61 -38
- data/lib/rigor/plugin/base.rb +302 -45
- data/lib/rigor/plugin/node_rule_walk.rb +147 -0
- data/lib/rigor/plugin/registry.rb +281 -15
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/conformance_checker.rb +293 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope/discovery_index.rb +58 -0
- data/lib/rigor/scope.rb +150 -167
- data/lib/rigor/sig_gen/observation_collector.rb +6 -6
- data/lib/rigor/source/literals.rb +14 -0
- data/lib/rigor/type/acceptance_router.rb +19 -0
- data/lib/rigor/type/accepts_result.rb +3 -10
- data/lib/rigor/type/app.rb +3 -7
- data/lib/rigor/type/bot.rb +2 -3
- data/lib/rigor/type/bound_method.rb +5 -12
- data/lib/rigor/type/combinator.rb +22 -0
- data/lib/rigor/type/constant.rb +2 -3
- data/lib/rigor/type/data_class.rb +80 -0
- data/lib/rigor/type/data_instance.rb +100 -0
- data/lib/rigor/type/difference.rb +5 -10
- data/lib/rigor/type/dynamic.rb +5 -10
- data/lib/rigor/type/hash_shape.rb +5 -15
- data/lib/rigor/type/integer_range.rb +5 -10
- data/lib/rigor/type/intersection.rb +5 -10
- data/lib/rigor/type/nominal.rb +5 -10
- data/lib/rigor/type/refined.rb +5 -10
- data/lib/rigor/type/singleton.rb +5 -10
- data/lib/rigor/type/top.rb +2 -3
- data/lib/rigor/type/tuple.rb +5 -10
- data/lib/rigor/type/union.rb +5 -10
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/value_semantics.rb +77 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +2 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +70 -32
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -3
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +15 -21
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +35 -18
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +8 -29
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +17 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +83 -36
- data/sig/rigor/cache.rbs +19 -0
- data/sig/rigor/environment.rbs +0 -2
- data/sig/rigor/inference.rbs +27 -0
- data/sig/rigor/plugin/base.rbs +1 -2
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +42 -25
- data/sig/rigor/source.rbs +1 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +6 -1
- data/skills/rigor-ci-setup/SKILL.md +319 -0
- metadata +36 -2
- data/lib/rigor/cache/rbs_instance_definitions.rb +0 -79
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "rbs_descriptor"
|
|
4
|
+
require_relative "rbs_cache_producer"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Cache
|
|
@@ -15,19 +16,12 @@ module Rigor
|
|
|
15
16
|
# Cache descriptor shape is shared with every other cache
|
|
16
17
|
# producer that depends on the RBS environment — see
|
|
17
18
|
# {RbsDescriptor.build} for the slot definitions.
|
|
18
|
-
class RbsConstantTable
|
|
19
|
+
class RbsConstantTable < RbsCacheProducer
|
|
19
20
|
PRODUCER_ID = "rbs.constant_type_table"
|
|
20
21
|
|
|
21
22
|
# @param loader [Rigor::Environment::RbsLoader]
|
|
22
23
|
# @param store [Rigor::Cache::Store]
|
|
23
24
|
# @return [Hash{String => Rigor::Type}]
|
|
24
|
-
def self.fetch(loader:, store:)
|
|
25
|
-
descriptor = RbsDescriptor.build(loader)
|
|
26
|
-
store.fetch_or_compute(producer_id: PRODUCER_ID, params: {}, descriptor: descriptor) do
|
|
27
|
-
compute(loader)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
25
|
def self.compute(loader)
|
|
32
26
|
table = {}
|
|
33
27
|
loader.each_constant_decl do |name, entry|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "rbs_descriptor"
|
|
4
|
+
require_relative "rbs_cache_producer"
|
|
4
5
|
require_relative "rbs_environment_marshal_patch"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
@@ -26,19 +27,12 @@ module Rigor
|
|
|
26
27
|
# Cache descriptor shape is shared with every other cache
|
|
27
28
|
# producer that depends on the RBS environment — see
|
|
28
29
|
# {RbsDescriptor.build}.
|
|
29
|
-
class RbsEnvironment
|
|
30
|
+
class RbsEnvironment < RbsCacheProducer
|
|
30
31
|
PRODUCER_ID = "rbs.environment"
|
|
31
32
|
|
|
32
33
|
# @param loader [Rigor::Environment::RbsLoader]
|
|
33
34
|
# @param store [Rigor::Cache::Store]
|
|
34
35
|
# @return [::RBS::Environment]
|
|
35
|
-
def self.fetch(loader:, store:)
|
|
36
|
-
descriptor = RbsDescriptor.build(loader)
|
|
37
|
-
store.fetch_or_compute(producer_id: PRODUCER_ID, params: {}, descriptor: descriptor) do
|
|
38
|
-
compute(loader)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
36
|
def self.compute(loader)
|
|
43
37
|
Rigor::Environment::RbsLoader.build_env_for(
|
|
44
38
|
libraries: loader.libraries,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "rbs_descriptor"
|
|
4
|
+
require_relative "rbs_cache_producer"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Cache
|
|
@@ -18,19 +19,12 @@ module Rigor
|
|
|
18
19
|
# Cache descriptor shape is shared with {RbsConstantTable} via
|
|
19
20
|
# {RbsDescriptor.build}; a single signature change or rbs gem
|
|
20
21
|
# bump invalidates both producers in lockstep.
|
|
21
|
-
class RbsKnownClassNames
|
|
22
|
+
class RbsKnownClassNames < RbsCacheProducer
|
|
22
23
|
PRODUCER_ID = "rbs.known_class_names"
|
|
23
24
|
|
|
24
25
|
# @param loader [Rigor::Environment::RbsLoader]
|
|
25
26
|
# @param store [Rigor::Cache::Store]
|
|
26
27
|
# @return [Set<String>]
|
|
27
|
-
def self.fetch(loader:, store:)
|
|
28
|
-
descriptor = RbsDescriptor.build(loader)
|
|
29
|
-
store.fetch_or_compute(producer_id: PRODUCER_ID, params: {}, descriptor: descriptor) do
|
|
30
|
-
compute(loader)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
28
|
def self.compute(loader)
|
|
35
29
|
names = Set.new
|
|
36
30
|
loader.each_known_class_name { |name| names << name }
|
data/lib/rigor/cache/store.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "fileutils"
|
|
|
5
5
|
require "json"
|
|
6
6
|
require "monitor"
|
|
7
7
|
require "securerandom"
|
|
8
|
+
require "zlib"
|
|
8
9
|
|
|
9
10
|
require_relative "descriptor"
|
|
10
11
|
|
|
@@ -17,17 +18,27 @@ module Rigor
|
|
|
17
18
|
# and nothing else.
|
|
18
19
|
#
|
|
19
20
|
# Read failures (missing file, bad magic, format-version mismatch,
|
|
20
|
-
# corrupt SHA-256 trailer, unmarshal-able payload)
|
|
21
|
-
# treated as cache misses; the producer block reruns
|
|
22
|
-
# next write replaces the bad entry. The trailing SHA-256
|
|
23
|
-
# accidental corruption (partial writes, FS errors); it is
|
|
24
|
-
# a security boundary, per ADR-2's trusted-gem trust model.
|
|
21
|
+
# corrupt SHA-256 trailer, un-inflatable or unmarshal-able payload)
|
|
22
|
+
# are silently treated as cache misses; the producer block reruns
|
|
23
|
+
# and the next write replaces the bad entry. The trailing SHA-256
|
|
24
|
+
# catches accidental corruption (partial writes, FS errors); it is
|
|
25
|
+
# **not** a security boundary, per ADR-2's trusted-gem trust model.
|
|
25
26
|
class Store # rubocop:disable Metrics/ClassLength
|
|
27
|
+
# On-disk byte-layout version. Bumped on incompatible format
|
|
28
|
+
# changes (independent of {Descriptor::SCHEMA_VERSION}, which
|
|
29
|
+
# covers the descriptor schema rather than the byte layout).
|
|
30
|
+
# v2 (ADR-54 WD2): the value payload is zlib-deflated on write
|
|
31
|
+
# and inflated on read — Marshal blobs compress to 13–16 % at
|
|
32
|
+
# an inflate cost an order of magnitude below their
|
|
33
|
+
# `Marshal.load`. v1 entries fail the header check and read as
|
|
34
|
+
# silent misses; the `schema_version.txt` marker additionally
|
|
35
|
+
# carries this version, so the first writable run after a bump
|
|
36
|
+
# clears the root and reclaims the unreadable bytes.
|
|
37
|
+
FORMAT_VERSION = 2
|
|
38
|
+
|
|
26
39
|
# Header literal: 5-byte ASCII magic, 1-byte separator, 1-byte
|
|
27
|
-
# format version.
|
|
28
|
-
|
|
29
|
-
# the descriptor schema rather than the byte layout).
|
|
30
|
-
HEADER = "RIGOR\x00\x01".b.freeze
|
|
40
|
+
# format version.
|
|
41
|
+
HEADER = "RIGOR\x00#{FORMAT_VERSION.chr}".b.freeze
|
|
31
42
|
|
|
32
43
|
VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/
|
|
33
44
|
|
|
@@ -44,9 +55,11 @@ module Rigor
|
|
|
44
55
|
# invocations can read from the same cache concurrently
|
|
45
56
|
# without churning it. See
|
|
46
57
|
# `docs/design/20260516-editor-mode.md` § "Cache behaviour".
|
|
47
|
-
def initialize(root:, read_only: false)
|
|
58
|
+
def initialize(root:, read_only: false, max_bytes: nil)
|
|
48
59
|
@root = root.to_s.dup.freeze
|
|
49
60
|
@read_only = read_only
|
|
61
|
+
@max_bytes = max_bytes&.then { |n| Integer(n) }
|
|
62
|
+
@schema_version_ensured = false
|
|
50
63
|
@hits = 0
|
|
51
64
|
@misses = 0
|
|
52
65
|
@writes = 0
|
|
@@ -106,6 +119,18 @@ module Rigor
|
|
|
106
119
|
# When the root does not exist or has no schema-version
|
|
107
120
|
# marker, `schema_version` is nil and the producer list is
|
|
108
121
|
# empty.
|
|
122
|
+
# The `schema_version.txt` marker content. Covers BOTH
|
|
123
|
+
# invalidation axes: the descriptor schema and the on-disk byte
|
|
124
|
+
# layout ({FORMAT_VERSION}, ADR-54). A format bump leaves the
|
|
125
|
+
# old entries permanently unreadable (header mismatch → miss)
|
|
126
|
+
# but, alone, would never reclaim their bytes — they can sit
|
|
127
|
+
# below the eviction cap forever. Folding the format version
|
|
128
|
+
# into the marker routes the bump through the established
|
|
129
|
+
# clear-the-root path instead.
|
|
130
|
+
def self.schema_marker_value
|
|
131
|
+
"#{Descriptor::SCHEMA_VERSION}.#{FORMAT_VERSION}"
|
|
132
|
+
end
|
|
133
|
+
|
|
109
134
|
def self.disk_inventory(root:)
|
|
110
135
|
root_s = root.to_s
|
|
111
136
|
marker = File.join(root_s, "schema_version.txt")
|
|
@@ -197,6 +222,81 @@ module Rigor
|
|
|
197
222
|
value
|
|
198
223
|
end
|
|
199
224
|
|
|
225
|
+
# ADR-45 — record-and-validate variant. Unlike {fetch_or_compute},
|
|
226
|
+
# which keys the entry on its descriptor (so the inputs MUST be
|
|
227
|
+
# known before running), this keys on `key_descriptor` (the stable
|
|
228
|
+
# inputs known up front) and stores, alongside the value, a
|
|
229
|
+
# `dependency_descriptor` of the files the value actually read —
|
|
230
|
+
# including inputs discovered DURING the computation (e.g. a plugin
|
|
231
|
+
# reading a file mid-analysis). On the next run the stored
|
|
232
|
+
# dependencies are re-validated against the filesystem
|
|
233
|
+
# ({Descriptor#fresh?}); a stale dependency forces a recompute.
|
|
234
|
+
#
|
|
235
|
+
# The block MUST return `[value, dependency_descriptor]`. Disk reads
|
|
236
|
+
# are not in-process-memoised — validation always re-checks the
|
|
237
|
+
# filesystem — but a single run only looks up once.
|
|
238
|
+
def fetch_or_validate(producer_id:, key_descriptor:, params: {}, serialize: nil, deserialize: nil)
|
|
239
|
+
validate_producer_id!(producer_id)
|
|
240
|
+
ensure_schema_version!
|
|
241
|
+
|
|
242
|
+
key = key_descriptor.cache_key_for(producer_id: producer_id, params: params)
|
|
243
|
+
path = entry_path(producer_id, key)
|
|
244
|
+
cached = read_entry(path, deserialize: deserialize)
|
|
245
|
+
if cached && (pair = cached.value).is_a?(Array) && pair.size == 2 &&
|
|
246
|
+
pair[1].is_a?(Descriptor) && pair[1].fresh?
|
|
247
|
+
@monitor.synchronize { record(:hits, producer_id) }
|
|
248
|
+
return pair[0]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
value, dependency_descriptor = block_given? ? yield : [nil, Descriptor.new]
|
|
252
|
+
wrote = false
|
|
253
|
+
unless @read_only
|
|
254
|
+
# A cache write must never break the run. If the value is not
|
|
255
|
+
# Marshal-clean (or any disk error occurs) skip caching and
|
|
256
|
+
# return the freshly-computed value — the next run recomputes.
|
|
257
|
+
begin
|
|
258
|
+
write_entry(path, key_descriptor, [value, dependency_descriptor], serialize: serialize)
|
|
259
|
+
wrote = true
|
|
260
|
+
rescue StandardError
|
|
261
|
+
wrote = false
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
@monitor.synchronize do
|
|
265
|
+
record(:misses, producer_id)
|
|
266
|
+
record(:writes, producer_id) if wrote
|
|
267
|
+
end
|
|
268
|
+
value
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# ADR-6 § "Eviction" — LRU pass over the on-disk cache. No-op when
|
|
272
|
+
# `max_bytes:` was not configured or the store is read-only.
|
|
273
|
+
# Walks all `.entry` files, sorts by mtime ascending (oldest = least
|
|
274
|
+
# recently used), and unlinks from the oldest until the total is at
|
|
275
|
+
# or below the cap. Touch-on-disk-read ({read_entry}) is the
|
|
276
|
+
# cross-process LRU signal: every disk hit (not in-process-memo hit)
|
|
277
|
+
# updates the mtime so recently-read entries survive the eviction pass.
|
|
278
|
+
# Any FS error is swallowed — eviction must never break a run.
|
|
279
|
+
def evict!
|
|
280
|
+
return if @max_bytes.nil? || @read_only
|
|
281
|
+
|
|
282
|
+
entries = collect_entry_stats
|
|
283
|
+
total = entries.sum { |e| e[:bytes] }
|
|
284
|
+
return if total <= @max_bytes
|
|
285
|
+
|
|
286
|
+
entries.sort_by! { |e| e[:mtime] }
|
|
287
|
+
entries.each do |entry|
|
|
288
|
+
break if total <= @max_bytes
|
|
289
|
+
|
|
290
|
+
File.unlink(entry[:path])
|
|
291
|
+
total -= entry[:bytes]
|
|
292
|
+
rescue StandardError
|
|
293
|
+
next
|
|
294
|
+
end
|
|
295
|
+
nil
|
|
296
|
+
rescue StandardError
|
|
297
|
+
nil
|
|
298
|
+
end
|
|
299
|
+
|
|
200
300
|
private
|
|
201
301
|
|
|
202
302
|
Entry = Data.define(:descriptor_bytes, :value)
|
|
@@ -238,6 +338,7 @@ module Rigor
|
|
|
238
338
|
value = safe_load(value_bytes, deserialize)
|
|
239
339
|
return nil if value.equal?(LOAD_FAILED)
|
|
240
340
|
|
|
341
|
+
touch_for_lru(path) if @max_bytes
|
|
241
342
|
Entry.new(descriptor_bytes, value)
|
|
242
343
|
end
|
|
243
344
|
|
|
@@ -271,11 +372,15 @@ module Rigor
|
|
|
271
372
|
LOAD_FAILED = Object.new.freeze
|
|
272
373
|
private_constant :LOAD_FAILED
|
|
273
374
|
|
|
375
|
+
# Inflates the stored value payload (ADR-54 WD2), then hands the
|
|
376
|
+
# raw bytes to the deserialiser. Any failure — corrupt deflate
|
|
377
|
+
# stream included — reads as a miss.
|
|
274
378
|
def safe_load(bytes, deserialize)
|
|
379
|
+
raw = Zlib::Inflate.inflate(bytes)
|
|
275
380
|
if deserialize
|
|
276
|
-
deserialize.call(
|
|
381
|
+
deserialize.call(raw)
|
|
277
382
|
else
|
|
278
|
-
Marshal.load(
|
|
383
|
+
Marshal.load(raw) # rubocop:disable Security/MarshalLoad
|
|
279
384
|
end
|
|
280
385
|
rescue StandardError
|
|
281
386
|
LOAD_FAILED
|
|
@@ -285,7 +390,7 @@ module Rigor
|
|
|
285
390
|
FileUtils.mkdir_p(File.dirname(path))
|
|
286
391
|
|
|
287
392
|
descriptor_bytes = descriptor.to_canonical_bytes
|
|
288
|
-
value_bytes = serialize_value(value, serialize)
|
|
393
|
+
value_bytes = Zlib::Deflate.deflate(serialize_value(value, serialize))
|
|
289
394
|
|
|
290
395
|
body = +"".b
|
|
291
396
|
body << HEADER
|
|
@@ -331,10 +436,15 @@ module Rigor
|
|
|
331
436
|
# never collides with a read under the old). The next
|
|
332
437
|
# writable run will repair the cache.
|
|
333
438
|
return if @read_only
|
|
439
|
+
# The marker is process-stable; one check per Store is
|
|
440
|
+
# enough (a benign double-check under a thread race just
|
|
441
|
+
# repeats idempotent work).
|
|
442
|
+
return if @schema_version_ensured
|
|
334
443
|
|
|
444
|
+
@schema_version_ensured = true
|
|
335
445
|
FileUtils.mkdir_p(@root)
|
|
336
446
|
marker = File.join(@root, "schema_version.txt")
|
|
337
|
-
current =
|
|
447
|
+
current = self.class.schema_marker_value
|
|
338
448
|
|
|
339
449
|
if File.file?(marker)
|
|
340
450
|
on_disk = File.read(marker).strip
|
|
@@ -370,6 +480,27 @@ module Rigor
|
|
|
370
480
|
end
|
|
371
481
|
end
|
|
372
482
|
|
|
483
|
+
# Updates both atime and mtime of `path` to the current time — the
|
|
484
|
+
# cross-process LRU signal used by {evict!}. Best-effort: any FS
|
|
485
|
+
# error (read-only mount, deleted file) is silently ignored.
|
|
486
|
+
def touch_for_lru(path)
|
|
487
|
+
now = Time.now
|
|
488
|
+
File.utime(now, now, path)
|
|
489
|
+
rescue StandardError
|
|
490
|
+
nil
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Returns an array of `{ path:, mtime:, bytes: }` hashes for every
|
|
494
|
+
# `.entry` file under the cache root, skipping unreadable entries.
|
|
495
|
+
def collect_entry_stats
|
|
496
|
+
Dir.glob(File.join(@root, "**", "*.entry")).filter_map do |path|
|
|
497
|
+
stat = File.stat(path)
|
|
498
|
+
{ path: path, mtime: stat.mtime, bytes: stat.size }
|
|
499
|
+
rescue StandardError
|
|
500
|
+
nil
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
373
504
|
def read_varint(bytes, offset)
|
|
374
505
|
result = 0
|
|
375
506
|
shift = 0
|
|
@@ -9,6 +9,7 @@ require_relative "../scope"
|
|
|
9
9
|
require_relative "../inference/def_return_typer"
|
|
10
10
|
require_relative "../inference/scope_indexer"
|
|
11
11
|
require_relative "prism_colorizer"
|
|
12
|
+
require_relative "command"
|
|
12
13
|
|
|
13
14
|
module Rigor
|
|
14
15
|
class CLI
|
|
@@ -25,19 +26,13 @@ module Rigor
|
|
|
25
26
|
# since the appended text is always a comment — and printed to
|
|
26
27
|
# stdout with IRB-style syntax highlighting via
|
|
27
28
|
# {PrismColorizer}.
|
|
28
|
-
class AnnotateCommand
|
|
29
|
+
class AnnotateCommand < Command
|
|
29
30
|
USAGE = "Usage: rigor annotate [options] FILE"
|
|
30
31
|
|
|
31
32
|
# Appended ` #=> dump_type: <type>` suffix. Matched and
|
|
32
33
|
# stripped before re-annotating so re-running is idempotent.
|
|
33
34
|
ANNOTATION_PATTERN = /\s*#=>\s*dump_type:.*\z/
|
|
34
35
|
|
|
35
|
-
def initialize(argv:, out:, err:)
|
|
36
|
-
@argv = argv
|
|
37
|
-
@out = out
|
|
38
|
-
@err = err
|
|
39
|
-
end
|
|
40
|
-
|
|
41
36
|
# @return [Integer] CLI exit status.
|
|
42
37
|
def run
|
|
43
38
|
options = parse_options
|
|
@@ -6,6 +6,7 @@ require_relative "../analysis/baseline"
|
|
|
6
6
|
require_relative "../analysis/runner"
|
|
7
7
|
require_relative "../cache/store"
|
|
8
8
|
require_relative "../configuration"
|
|
9
|
+
require_relative "command"
|
|
9
10
|
|
|
10
11
|
module Rigor
|
|
11
12
|
class CLI
|
|
@@ -20,18 +21,12 @@ module Rigor
|
|
|
20
21
|
# rigor baseline dump
|
|
21
22
|
# rigor baseline drift
|
|
22
23
|
# rigor baseline prune
|
|
23
|
-
class BaselineCommand # rubocop:disable Metrics/ClassLength
|
|
24
|
+
class BaselineCommand < Command # rubocop:disable Metrics/ClassLength
|
|
24
25
|
EXIT_USAGE = 64
|
|
25
26
|
DEFAULT_BASELINE_PATH = ".rigor-baseline.yml"
|
|
26
27
|
|
|
27
28
|
SUBCOMMANDS = %w[generate regenerate dump drift prune].freeze
|
|
28
29
|
|
|
29
|
-
def initialize(argv:, out: $stdout, err: $stderr)
|
|
30
|
-
@argv = argv
|
|
31
|
-
@out = out
|
|
32
|
-
@err = err
|
|
33
|
-
end
|
|
34
|
-
|
|
35
30
|
def run
|
|
36
31
|
subcommand = @argv.shift
|
|
37
32
|
case subcommand
|