rigortype 0.3.1 → 0.3.3
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 +10 -10
- data/data/core_overlay/pathname.rbs +5 -0
- data/data/core_overlay/resolv.rbs +31 -0
- data/data/core_overlay/string_scanner.rbs +5 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +10 -6
- data/data/vendored_gem_sigs/bundler/bundler.rbs +39 -27
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +6 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +7 -0
- data/data/vendored_gem_sigs/racc/racc.rbs +67 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +20 -9
- data/docs/manual/02-cli-reference.md +71 -11
- data/docs/manual/03-configuration.md +16 -1
- data/docs/manual/04-diagnostics.md +8 -0
- data/docs/manual/12-caching.md +17 -5
- data/docs/manual/15-type-protection-coverage.md +122 -1
- data/docs/manual/plugins/rigor-rbs-inline.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +8 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +53 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +7 -0
- data/lib/rigor/analysis/check_rules.rb +107 -28
- data/lib/rigor/analysis/incremental_session.rb +156 -11
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +14 -0
- data/lib/rigor/analysis/rule_catalog.rb +5 -1
- data/lib/rigor/analysis/run_cache_key.rb +17 -1
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +201 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +28 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +7 -10
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -1
- data/lib/rigor/analysis/runner.rb +78 -7
- data/lib/rigor/analysis/severity_stamp.rb +2 -1
- data/lib/rigor/bleeding_edge.rb +132 -7
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +162 -0
- data/lib/rigor/cache/incremental_snapshot.rb +87 -18
- data/lib/rigor/cli/check_command.rb +49 -28
- data/lib/rigor/cli/coverage_command.rb +15 -2
- data/lib/rigor/cli/coverage_mutation.rb +242 -11
- data/lib/rigor/cli/fused_protection_renderer.rb +10 -0
- data/lib/rigor/cli/fused_protection_report.rb +12 -3
- data/lib/rigor/cli/mutation_fork_scan.rb +64 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +12 -0
- data/lib/rigor/cli/mutation_protection_report.rb +26 -5
- data/lib/rigor/cli/protection_renderer.rb +13 -0
- data/lib/rigor/cli/protection_report.rb +11 -3
- data/lib/rigor/cli/show_bleedingedge_command.rb +17 -4
- data/lib/rigor/cli/sig_gen_command.rb +5 -4
- data/lib/rigor/configuration.rb +45 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +2 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +434 -75
- data/lib/rigor/environment.rb +28 -13
- data/lib/rigor/inference/anonymous_meta_class.rb +81 -0
- data/lib/rigor/inference/expression_typer.rb +62 -4
- data/lib/rigor/inference/fork_map.rb +47 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +35 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +158 -12
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +64 -5
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +234 -11
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +3 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +124 -1
- data/lib/rigor/inference/method_dispatcher.rb +41 -10
- data/lib/rigor/inference/mutation_widening.rb +30 -25
- data/lib/rigor/inference/narrowing.rb +3 -1
- data/lib/rigor/inference/optimistic_origin.rb +123 -0
- data/lib/rigor/inference/protection_scanner.rb +18 -2
- data/lib/rigor/inference/receiver_alias.rb +57 -0
- data/lib/rigor/inference/scope_indexer.rb +197 -55
- data/lib/rigor/inference/singleton_object_constant.rb +63 -0
- data/lib/rigor/inference/statement_evaluator.rb +55 -4
- data/lib/rigor/language_server/buffer_table.rb +22 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +203 -4
- data/lib/rigor/language_server/project_context.rb +45 -0
- data/lib/rigor/language_server/publish_batcher.rb +82 -0
- data/lib/rigor/language_server/server.rb +20 -1
- data/lib/rigor/language_server.rb +1 -0
- data/lib/rigor/plugin/node_rule_walk.rb +6 -0
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/protection/closure_kill_oracle.rb +190 -0
- data/lib/rigor/protection/dependency_closure.rb +59 -0
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -8
- data/lib/rigor/protection/discovery_seed.rb +116 -0
- data/lib/rigor/protection/kill_signature.rb +31 -0
- data/lib/rigor/protection/mutation_cache.rb +355 -0
- data/lib/rigor/protection/mutation_scanner.rb +55 -11
- data/lib/rigor/protection/mutator.rb +26 -4
- data/lib/rigor/runtime/jit.rb +63 -1
- data/lib/rigor/scope/discovery_index.rb +9 -0
- data/lib/rigor/scope.rb +114 -10
- data/lib/rigor/sig_gen/generator.rb +130 -31
- data/lib/rigor/sig_gen/layout_index.rb +6 -0
- data/lib/rigor/sig_gen/meta_class_shape.rb +84 -0
- data/lib/rigor/sig_gen/renderer.rb +20 -1
- data/lib/rigor/sig_gen/write_result.rb +6 -4
- data/lib/rigor/sig_gen/writer.rb +12 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/node_walker.rb +12 -0
- data/lib/rigor/type/anonymous_class_name.rb +40 -0
- data/lib/rigor/type/nominal.rb +5 -2
- data/lib/rigor/type/singleton.rb +4 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +6 -6
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/result_shape.rb +146 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +177 -12
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +74 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +257 -0
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +69 -0
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +87 -11
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +34 -4
- data/sig/rigor/inference/void_origin.rbs +6 -3
- data/sig/rigor/scope.rbs +12 -0
- data/sig/rigor.rbs +4 -0
- metadata +21 -1
|
@@ -4,6 +4,8 @@ require "fileutils"
|
|
|
4
4
|
require "digest"
|
|
5
5
|
require "zlib"
|
|
6
6
|
|
|
7
|
+
require_relative "engine_source"
|
|
8
|
+
|
|
7
9
|
module Rigor
|
|
8
10
|
module Cache
|
|
9
11
|
# ADR-46 — disk persistence for the incremental analyzer's per-file state, so a `--incremental` session
|
|
@@ -14,8 +16,16 @@ module Rigor
|
|
|
14
16
|
# this snapshot is loaded UNCONDITIONALLY when the global fingerprint matches — the per-file digests
|
|
15
17
|
# *inside* it drive the incremental re-analysis decision; they do not gate the load. The fingerprint
|
|
16
18
|
# captures the inputs whose change requires a full rebuild — the resolved configuration, the RBS
|
|
17
|
-
# environment, the engine version
|
|
18
|
-
# gem / version change) drops the snapshot and
|
|
19
|
+
# environment, the engine version and (on a checkout) the engine's own source — but NOT the analyzed
|
|
20
|
+
# source contents. A fingerprint mismatch (config / gem / version / engine change) drops the snapshot and
|
|
21
|
+
# forces a full re-analysis, the conservative direction.
|
|
22
|
+
#
|
|
23
|
+
# An engine change drops the WHOLE snapshot rather than part of it, which is a soundness point before it
|
|
24
|
+
# is a simplicity one: every section here except `digests` is a value the analyzer computed, so a changed
|
|
25
|
+
# engine can move any of it — including the dependency edges, where a new engine recording an edge the old
|
|
26
|
+
# one missed would let a recheck skip the very file that needed re-analysing. Retaining the one
|
|
27
|
+
# engine-independent section would not pay either: `digests` is 2.5% of a 2.5 MB snapshot of this repo,
|
|
28
|
+
# and re-deriving it is a file-digest walk costing ~0.2% of the full run it would be saving.
|
|
19
29
|
#
|
|
20
30
|
# Every operation is fault-tolerant: a missing, unreadable, schema-mismatched, fingerprint-mismatched, or
|
|
21
31
|
# corrupt snapshot loads as nil (→ a cold full run), and a write failure is swallowed (→ the next run is
|
|
@@ -38,8 +48,11 @@ module Rigor
|
|
|
38
48
|
# bundle (the per-def parameter-shape / visibility / ancestry surface the declaration-stability gate
|
|
39
49
|
# compares) and WD2 adds `return_summaries` (per-def observed-key return descriptors + mutation-effect
|
|
40
50
|
# 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
|
-
|
|
51
|
+
# nil (a clean cold rebuild — no migration). 11: ADR-67 WD6c lift adds `param_table` (the inferred-param
|
|
52
|
+
# seed table the run's diagnostics were computed under, diffed on the next recheck to invalidate a
|
|
53
|
+
# callee whose seeds moved because a caller changed); a pre-11 blob mismatches the SCHEMA gate and
|
|
54
|
+
# loads as nil (a clean cold rebuild — no migration).
|
|
55
|
+
SCHEMA = 11
|
|
43
56
|
|
|
44
57
|
# The persisted per-file state.
|
|
45
58
|
# `cache` maps an analyzed file to its diagnostics.
|
|
@@ -68,20 +81,35 @@ module Rigor
|
|
|
68
81
|
# (Marshal-clean type tuples), their `describe(:short)` return descriptors, and the content-mutated
|
|
69
82
|
# parameter positions. A recheck re-evaluates a declaration-stable changed callee at these keys and,
|
|
70
83
|
# when every return + the effects are unchanged, skips its symbol dependents.
|
|
84
|
+
# ADR-67 WD6c lift:
|
|
85
|
+
# `param_table` is the `parameter_inference:` seed table (`[class, method, kind] => {param => Type}`)
|
|
86
|
+
# the run that wrote the snapshot seeded its analysis from — `{}` when the gate is off. A recheck
|
|
87
|
+
# recomputes the table fresh (the pre-pass is whole-project by design) and diffs it against this copy;
|
|
88
|
+
# a changed entry invalidates the callee's file and its symbol dependents. The types are Marshal-clean
|
|
89
|
+
# by the session's per-entry filter; a dropped entry re-checks its callee, the conservative direction.
|
|
71
90
|
Payload = Data.define(:cache, :sources, :digests, :analyzed,
|
|
72
91
|
:symbol_sources, :ancestry_sources, :symbol_fingerprints,
|
|
73
92
|
:missing, :class_decls, :seed_bundles, :plugin_fact_digest,
|
|
74
|
-
:return_summaries)
|
|
93
|
+
:return_summaries, :param_table)
|
|
75
94
|
|
|
76
95
|
# The global fingerprint that gates a snapshot load: a digest of the inputs whose change requires a full
|
|
77
|
-
# rebuild — the engine version + schema, the
|
|
78
|
-
# arguments, e.g. `["lib"]`, NOT the expanded
|
|
79
|
-
#
|
|
80
|
-
# the
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# under-capture (it would surface as an
|
|
84
|
-
# caller falls back to a non-persisted run.
|
|
96
|
+
# rebuild — the engine version + schema, the engine's own SOURCE when the version does not pin it, the
|
|
97
|
+
# resolved configuration, the analysis **roots** (the path arguments, e.g. `["lib"]`, NOT the expanded
|
|
98
|
+
# file list — so a snapshot is keyed to an invocation's roots but adding / removing a file under them is
|
|
99
|
+
# handled incrementally by the session, not a full rebuild), the resolved gem set (`Gemfile.lock` /
|
|
100
|
+
# `rbs_collection`), and the project's own RBS (`signature_paths` file contents). Built WITHOUT
|
|
101
|
+
# constructing the RBS environment so the warm path can gate the load cheaply, before the costly env
|
|
102
|
+
# build. The `--verify-incremental` gate is the safety net for any under-capture (it would surface as an
|
|
103
|
+
# incremental-vs-full mismatch). Returns nil on any error → the caller falls back to a non-persisted run.
|
|
104
|
+
#
|
|
105
|
+
# Issue #285, wired here by #289 — every value in this snapshot is something the ANALYZER computed, so
|
|
106
|
+
# `Rigor::VERSION` alone is not enough to identify what produced it. A version pins the engine's bytes
|
|
107
|
+
# for a RubyGems install and for nothing else, so on a checkout a warm recheck served diagnostics a
|
|
108
|
+
# pre-edit analyzer had computed: editing `lib/rigor/inference/*.rb` moved no ANALYZED file, the changed
|
|
109
|
+
# set came back empty, and 357 unchanged files replayed their old answers.
|
|
110
|
+
# {EngineSource.process_identity} closes it, and answers nil for a version-pinned tree — which adds no
|
|
111
|
+
# part, so a released gem's fingerprint is byte-identical to the pre-#289 one and its warm snapshots
|
|
112
|
+
# survive the upgrade untouched.
|
|
85
113
|
def self.fingerprint(configuration:, roots:)
|
|
86
114
|
parts = [
|
|
87
115
|
"engine:#{Rigor::VERSION}:#{SCHEMA}",
|
|
@@ -91,8 +119,15 @@ module Rigor
|
|
|
91
119
|
"rbs_collection:#{digest_file_if_present('rbs_collection.lock.yaml')}",
|
|
92
120
|
"sig:#{digest_signature_paths(configuration.signature_paths)}"
|
|
93
121
|
]
|
|
122
|
+
identity = EngineSource.process_identity
|
|
123
|
+
parts << "engine-source:#{identity}" if identity
|
|
94
124
|
Digest::SHA256.hexdigest(parts.join("\x00"))
|
|
95
125
|
rescue StandardError
|
|
126
|
+
# {EngineSource::Unavailable} lands here too, and nil is the answer it requires rather than one it
|
|
127
|
+
# merely tolerates: an engine we cannot identify must DISABLE the snapshot, never fall back to the
|
|
128
|
+
# version-only key that is the blind spot above. Nil does disable it on both sides —
|
|
129
|
+
# {Analysis::IncrementalSession} guards the load AND the save on the fingerprint, so nothing stale is
|
|
130
|
+
# read and no nil-keyed blob is written for the next equally-unidentifiable run to match against.
|
|
96
131
|
nil
|
|
97
132
|
end
|
|
98
133
|
|
|
@@ -124,9 +159,42 @@ module Rigor
|
|
|
124
159
|
# The stored {Payload}, or nil when absent / unreadable / schema or fingerprint mismatch / corrupt.
|
|
125
160
|
# Never raises.
|
|
126
161
|
def load(fingerprint:)
|
|
162
|
+
data = read_data
|
|
163
|
+
return nil unless data && data[:fingerprint] == fingerprint
|
|
164
|
+
|
|
165
|
+
payload_from(data)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Issue #134 slice 2 — the same load against SEVERAL acceptable fingerprints, reading the blob once.
|
|
169
|
+
# A reader that did not itself write the snapshot cannot know which analysis ROOTS it was written under
|
|
170
|
+
# (`rigor check --incremental lib` and a bare `rigor check --incremental` produce different fingerprints
|
|
171
|
+
# for the same project), and `#load` would have to re-inflate + re-unmarshal the whole blob per candidate.
|
|
172
|
+
# The fingerprint that matched is returned alongside the payload so the caller can mix it into ITS own
|
|
173
|
+
# cache key — the snapshot's identity is exactly what "these dependency edges came from that world" means.
|
|
174
|
+
#
|
|
175
|
+
# @param fingerprints [Array<String>] candidates, most-specific first.
|
|
176
|
+
# @return [Array(String, Payload), nil] `[matched fingerprint, payload]`, or nil on any miss.
|
|
177
|
+
def load_any(fingerprints:)
|
|
178
|
+
data = read_data
|
|
179
|
+
return nil if data.nil?
|
|
180
|
+
|
|
181
|
+
matched = Array(fingerprints).compact.find { |candidate| data[:fingerprint] == candidate }
|
|
182
|
+
return nil if matched.nil?
|
|
183
|
+
|
|
184
|
+
[matched, payload_from(data)]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# The raw stored Hash when it is present, readable, and schema-current; nil otherwise. Never raises —
|
|
188
|
+
# a missing, corrupt, or stale-schema snapshot is a cold run, not an error (the ADR-45 invariant).
|
|
189
|
+
def read_data
|
|
127
190
|
data = Marshal.load(Zlib::Inflate.inflate(File.binread(@path))) # rubocop:disable Security/MarshalLoad
|
|
128
|
-
|
|
191
|
+
data.is_a?(Hash) && data[:schema] == SCHEMA ? data : nil
|
|
192
|
+
rescue StandardError
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
195
|
+
private :read_data
|
|
129
196
|
|
|
197
|
+
def payload_from(data)
|
|
130
198
|
Payload.new(
|
|
131
199
|
cache: data[:cache], sources: data[:sources],
|
|
132
200
|
digests: data[:digests], analyzed: data[:analyzed],
|
|
@@ -137,11 +205,11 @@ module Rigor
|
|
|
137
205
|
class_decls: data[:class_decls] || {},
|
|
138
206
|
seed_bundles: data[:seed_bundles] || {},
|
|
139
207
|
plugin_fact_digest: data[:plugin_fact_digest],
|
|
140
|
-
return_summaries: data[:return_summaries] || {}
|
|
208
|
+
return_summaries: data[:return_summaries] || {},
|
|
209
|
+
param_table: data[:param_table] || {}
|
|
141
210
|
)
|
|
142
|
-
rescue StandardError
|
|
143
|
-
nil
|
|
144
211
|
end
|
|
212
|
+
private :payload_from
|
|
145
213
|
|
|
146
214
|
# Persist `payload` under `fingerprint`. Writes via a temp file + atomic rename so a concurrent reader
|
|
147
215
|
# never sees a half-written snapshot. Returns true on success, false on any failure (never raises).
|
|
@@ -158,7 +226,8 @@ module Rigor
|
|
|
158
226
|
class_decls: payload.class_decls,
|
|
159
227
|
seed_bundles: payload.seed_bundles,
|
|
160
228
|
plugin_fact_digest: payload.plugin_fact_digest,
|
|
161
|
-
return_summaries: payload.return_summaries
|
|
229
|
+
return_summaries: payload.return_summaries,
|
|
230
|
+
param_table: payload.param_table
|
|
162
231
|
)
|
|
163
232
|
blob = Zlib::Deflate.deflate(raw)
|
|
164
233
|
tmp = "#{@path}.#{Process.pid}.tmp"
|
|
@@ -48,9 +48,6 @@ module Rigor
|
|
|
48
48
|
|
|
49
49
|
configuration = load_check_configuration(options)
|
|
50
50
|
configuration = apply_bleeding_edge_override(configuration, options)
|
|
51
|
-
conflict = parameter_inference_incremental_conflict(configuration, options)
|
|
52
|
-
return conflict unless conflict.nil?
|
|
53
|
-
|
|
54
51
|
config_warnings = warn_unresolved_config(configuration)
|
|
55
52
|
cache_root = configuration.cache_path
|
|
56
53
|
handle_clear_cache(cache_root) if options.fetch(:clear_cache)
|
|
@@ -62,7 +59,7 @@ module Rigor
|
|
|
62
59
|
return finalize_cache_hit(probed, configuration, options, config_warnings) unless probed.nil?
|
|
63
60
|
|
|
64
61
|
load_check_dependencies
|
|
65
|
-
special = dispatch_special_check_mode(configuration, options, cache_root)
|
|
62
|
+
special = dispatch_special_check_mode(configuration, options, cache_root, buffer)
|
|
66
63
|
return special unless special.nil?
|
|
67
64
|
|
|
68
65
|
invocation = invoke_check(
|
|
@@ -143,30 +140,24 @@ module Rigor
|
|
|
143
140
|
exit_code
|
|
144
141
|
end
|
|
145
142
|
|
|
146
|
-
# ADR-67 WD6c — `parameter_inference:` and the ADR-46 incremental modes are mutually exclusive in slice 1.
|
|
147
|
-
# The parameter table introduces cross-file edges (a caller's argument types drive a callee's body
|
|
148
|
-
# diagnostics) that the per-file dependency recorder does not yet carry, so a cached file whose parameter
|
|
149
|
-
# seeds changed would serve a stale result. Refuse with a message naming the ADR-46 gap rather than
|
|
150
|
-
# silently producing unsound incremental diagnostics; the edge wiring is the named follow-up. Returns the
|
|
151
|
-
# usage exit code on a conflict, or nil to proceed. `--verify-incremental` (the acceptance gate that runs
|
|
152
|
-
# incremental analysis) is caught here too.
|
|
153
|
-
def parameter_inference_incremental_conflict(configuration, options)
|
|
154
|
-
return nil unless configuration.parameter_inference
|
|
155
|
-
return nil unless options.fetch(:incremental) || options.fetch(:verify_incremental)
|
|
156
|
-
|
|
157
|
-
flag = options.fetch(:incremental) ? "--incremental" : "--verify-incremental"
|
|
158
|
-
@err.puts("rigor: parameter_inference: cannot combine with #{flag} — the call-site parameter table " \
|
|
159
|
-
"introduces cross-file caller→callee edges the ADR-46 incremental dependency graph does not " \
|
|
160
|
-
"yet record, so a cached file could serve a stale diagnostic. Run a full check (drop " \
|
|
161
|
-
"#{flag}), or disable parameter_inference: for this run.")
|
|
162
|
-
CLI::EXIT_USAGE
|
|
163
|
-
end
|
|
164
|
-
|
|
165
143
|
# ADR-46 — the two incremental-analysis check modes both fully handle the run and return an exit code (so `run`
|
|
166
144
|
# short-circuits); returns nil for an ordinary check.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
145
|
+
# A nil return means "not a special mode" — `run` continues with the ordinary check. Editor mode option B
|
|
146
|
+
# also returns nil when it declines (no reusable snapshot), so the run falls back to single-file scope.
|
|
147
|
+
def dispatch_special_check_mode(configuration, options, cache_root, buffer)
|
|
148
|
+
if options.fetch(:verify_incremental)
|
|
149
|
+
# The gate compares an incremental recheck against a full-run oracle, and a buffer makes the two
|
|
150
|
+
# disagree by construction (the oracle reads the file on disk). Refusing beats the silent wrong
|
|
151
|
+
# answer both incremental modes gave a buffer before #146.
|
|
152
|
+
if buffer
|
|
153
|
+
@err.puts("rigor: --verify-incremental cannot run against an editor buffer " \
|
|
154
|
+
"(--tmp-file / --instead-of); it compares against a full analysis of the files on disk.")
|
|
155
|
+
return CLI::EXIT_USAGE
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
return run_verify_incremental(configuration, options, cache_root)
|
|
159
|
+
end
|
|
160
|
+
return run_incremental_check(configuration, options, cache_root, buffer) if options.fetch(:incremental)
|
|
170
161
|
|
|
171
162
|
nil
|
|
172
163
|
end
|
|
@@ -212,7 +203,7 @@ module Rigor
|
|
|
212
203
|
# run (plus their dependents), serving the rest from the snapshot; on a miss runs a full baseline. Persists the
|
|
213
204
|
# updated snapshot for the next invocation. Diagnostics are identical to a full run (the `--verify-incremental`
|
|
214
205
|
# gate enforces this); the win is skipping per-file inference for unchanged files.
|
|
215
|
-
def run_incremental_check(configuration, options, cache_root)
|
|
206
|
+
def run_incremental_check(configuration, options, cache_root, buffer = nil)
|
|
216
207
|
require_relative "check_runner_factory"
|
|
217
208
|
paths = @argv.empty? ? nil : @argv
|
|
218
209
|
fingerprint = Cache::IncrementalSnapshot.fingerprint(
|
|
@@ -226,9 +217,12 @@ module Rigor
|
|
|
226
217
|
# ADR-46 — thread the same worker-count precedence the standard `check` path uses
|
|
227
218
|
# (CLI `--workers` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > 0) so the recheck's closure
|
|
228
219
|
# re-analysis parallelises; the fork pool marshals dependency records back so the graph is sound.
|
|
229
|
-
workers: CheckRunnerFactory.resolve_workers(options, configuration)
|
|
220
|
+
workers: CheckRunnerFactory.resolve_workers(options, configuration),
|
|
221
|
+
buffer: buffer
|
|
230
222
|
)
|
|
231
223
|
|
|
224
|
+
return run_editor_mode_option_b(session, snapshot, fingerprint, configuration, options) if buffer
|
|
225
|
+
|
|
232
226
|
diagnostics, warm = session.run_incremental(snapshot: snapshot, fingerprint: fingerprint)
|
|
233
227
|
# The banner's file count comes from the session's analyzed set (cold analyses all; a warm recheck's
|
|
234
228
|
# `@analyzed` advances to the current file set), so a dedicated probe Runner + `Dir.glob` is no longer
|
|
@@ -244,6 +238,33 @@ module Rigor
|
|
|
244
238
|
result.success? ? 0 : 1
|
|
245
239
|
end
|
|
246
240
|
|
|
241
|
+
# Editor mode option B (#146) — `--incremental` plus an editor buffer. The whole project is in scope with
|
|
242
|
+
# the buffer substituted for one file: the buffer's logical path and its dependents re-analyse, every
|
|
243
|
+
# other file is served from the snapshot. Before this, the two flags together silently ignored the
|
|
244
|
+
# buffer and analysed the file on disk, which is a wrong answer rather than a missing feature.
|
|
245
|
+
#
|
|
246
|
+
# The session never saves — the buffer's bytes exist only in the editor — so there is no warm state to
|
|
247
|
+
# build up. That is why a snapshot it cannot reuse means falling back to option A (single-file scope,
|
|
248
|
+
# the pre-#146 behaviour) instead of running a baseline that would repeat on the next keystroke. The
|
|
249
|
+
# note tells the user how to get option B: warm the snapshot with a plain `rigor check --incremental`.
|
|
250
|
+
def run_editor_mode_option_b(session, snapshot, fingerprint, configuration, options)
|
|
251
|
+
result = session.run_buffer_recheck(snapshot: snapshot, fingerprint: fingerprint)
|
|
252
|
+
if result.nil?
|
|
253
|
+
@err.puts("rigor: --incremental has no reusable snapshot for this project; analysing the buffer " \
|
|
254
|
+
"alone (run `rigor check --incremental` once to enable whole-project editor mode).")
|
|
255
|
+
return nil
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
@err.puts("rigor: --incremental editor mode — re-analysed #{result.affected.size} file(s), " \
|
|
259
|
+
"#{result.reused.size} served from cache")
|
|
260
|
+
emit_incremental_fact_surface_notes(session)
|
|
261
|
+
filtered = apply_baseline_filter(
|
|
262
|
+
Analysis::Result.new(diagnostics: result.diagnostics, stats: nil), configuration, options
|
|
263
|
+
)
|
|
264
|
+
write_result(filtered, options.fetch(:format))
|
|
265
|
+
filtered.success? ? 0 : 1
|
|
266
|
+
end
|
|
267
|
+
|
|
247
268
|
# ADR-88 WD1 — a one-line stderr note when the plugin fact surface (an ADR-9 fact, an ADR-60 producer
|
|
248
269
|
# value, or an `incremental_state_fingerprint` hook) forced a full run: either it CHANGED since the
|
|
249
270
|
# snapshot (a Sorbet sig edit, a schema change) or a contributing plugin declares NO fingerprint surface
|
|
@@ -27,6 +27,7 @@ require_relative "fused_protection_report"
|
|
|
27
27
|
require_relative "fused_protection_renderer"
|
|
28
28
|
require_relative "coverage_mutation"
|
|
29
29
|
require_relative "protection_fork_scan"
|
|
30
|
+
require_relative "mutation_fork_scan"
|
|
30
31
|
require_relative "check_runner_factory"
|
|
31
32
|
require_relative "command"
|
|
32
33
|
|
|
@@ -90,7 +91,7 @@ module Rigor
|
|
|
90
91
|
def parse_options
|
|
91
92
|
options = { format: "text", threshold: nil, config: nil, protection: false, mutation: false,
|
|
92
93
|
with_tests: false, test_command: DEFAULT_TEST_COMMAND, include_dynamic: false,
|
|
93
|
-
limit: nil, seed: 1, workers: nil }
|
|
94
|
+
limit: nil, seed: 1, workers: nil, no_cache: false }
|
|
94
95
|
OptionParser.new { |opts| define_options(opts, options) }.parse!(@argv)
|
|
95
96
|
options
|
|
96
97
|
end
|
|
@@ -103,7 +104,8 @@ module Rigor
|
|
|
103
104
|
options[:protection] = true
|
|
104
105
|
end
|
|
105
106
|
define_mutation_options(opts, options)
|
|
106
|
-
opts.on("--workers=N", Integer, "With --protection
|
|
107
|
+
opts.on("--workers=N", Integer, "With --protection (with or without --mutation, but not --with-tests, " \
|
|
108
|
+
"which must stay sequential): fork N workers over the scanned files " \
|
|
107
109
|
"(default: config parallel.workers / RIGOR_RACTOR_WORKERS / 0)") do |v|
|
|
108
110
|
options[:workers] = v
|
|
109
111
|
end
|
|
@@ -135,6 +137,12 @@ module Rigor
|
|
|
135
137
|
options[:limit] = v
|
|
136
138
|
end
|
|
137
139
|
opts.on("--seed=N", Integer, "RNG seed for --limit sampling (default 1)") { |v| options[:seed] = v }
|
|
140
|
+
opts.on("--no-cache", "With --mutation: measure every file from scratch, neither reading nor writing " \
|
|
141
|
+
"the per-file mutation-result cache (#134 slice 2). The cold arm of the " \
|
|
142
|
+
"warm==cold gate; unrelated caches (RBS environment, plugin producers) are " \
|
|
143
|
+
"untouched.") do
|
|
144
|
+
options[:no_cache] = true
|
|
145
|
+
end
|
|
138
146
|
end
|
|
139
147
|
|
|
140
148
|
def mutation_misuse_error
|
|
@@ -204,6 +212,11 @@ module Rigor
|
|
|
204
212
|
#
|
|
205
213
|
# Both span the scanned `paths` only (no whole-project pre-pass) — a site that gains neither is classified exactly
|
|
206
214
|
# as before.
|
|
215
|
+
#
|
|
216
|
+
# Tier 2's analogue is {Protection::DiscoverySeed} (#253, #260): the same idea over a *fuller* table set,
|
|
217
|
+
# because Tier 2 must also let its kill oracle resolve the method on the receiver, not merely name its
|
|
218
|
+
# class. Tier 1 seeds unconditionally because it only reclassifies sites it already counted; Tier 2 adds
|
|
219
|
+
# sites to a denominator `--threshold` gates CI on, which is why only the latter is gated on a feature id.
|
|
207
220
|
def scope_with_inferred_params(paths, configuration, environment, workers)
|
|
208
221
|
base = Scope.empty(environment: environment)
|
|
209
222
|
seed = {}
|