rigortype 0.1.3 → 0.1.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 +154 -33
- data/lib/rigor/analysis/check_rules.rb +10 -18
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
- data/lib/rigor/analysis/diagnostic.rb +0 -2
- data/lib/rigor/analysis/fact_store.rb +26 -6
- data/lib/rigor/analysis/result.rb +11 -3
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +193 -0
- data/lib/rigor/analysis/runner.rb +498 -12
- data/lib/rigor/analysis/worker_session.rb +327 -0
- data/lib/rigor/builtins/imported_refinements.rb +364 -55
- data/lib/rigor/builtins/regex_refinement.rb +17 -12
- data/lib/rigor/cache/descriptor.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +3 -1
- data/lib/rigor/cache/store.rb +39 -6
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +173 -0
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_renderer.rb +1 -1
- data/lib/rigor/cli/type_scan_report.rb +2 -2
- data/lib/rigor/cli.rb +61 -3
- data/lib/rigor/configuration/dependencies.rb +2 -2
- data/lib/rigor/configuration.rb +131 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
- data/lib/rigor/environment/class_registry.rb +12 -3
- data/lib/rigor/environment/lockfile_resolver.rb +125 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
- data/lib/rigor/environment/rbs_loader.rb +194 -6
- data/lib/rigor/environment/reflection.rb +152 -0
- data/lib/rigor/environment.rb +109 -6
- data/lib/rigor/flow_contribution/conflict.rb +2 -2
- data/lib/rigor/flow_contribution/element.rb +1 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution/merge_result.rb +1 -1
- data/lib/rigor/flow_contribution/merger.rb +3 -3
- data/lib/rigor/flow_contribution.rb +2 -2
- data/lib/rigor/inference/acceptance.rb +35 -1
- data/lib/rigor/inference/block_parameter_binder.rb +0 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
- data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
- data/lib/rigor/inference/coverage_scanner.rb +1 -1
- data/lib/rigor/inference/expression_typer.rb +77 -11
- data/lib/rigor/inference/fallback.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +96 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -41
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +135 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +7 -12
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -44
- data/lib/rigor/inference/method_dispatcher.rb +274 -5
- data/lib/rigor/inference/method_parameter_binder.rb +22 -14
- data/lib/rigor/inference/narrowing.rb +129 -12
- data/lib/rigor/inference/rbs_type_translator.rb +0 -2
- data/lib/rigor/inference/scope_indexer.rb +14 -9
- data/lib/rigor/inference/statement_evaluator.rb +7 -7
- data/lib/rigor/inference/synthetic_method.rb +86 -0
- data/lib/rigor/inference/synthetic_method_index.rb +82 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +521 -0
- data/lib/rigor/plugin/blueprint.rb +60 -0
- data/lib/rigor/plugin/io_boundary.rb +0 -2
- data/lib/rigor/plugin/loader.rb +5 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
- data/lib/rigor/plugin/macro/external_file.rb +143 -0
- data/lib/rigor/plugin/macro/heredoc_template.rb +201 -0
- data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
- data/lib/rigor/plugin/macro.rb +31 -0
- data/lib/rigor/plugin/manifest.rb +102 -10
- data/lib/rigor/plugin/registry.rb +43 -2
- data/lib/rigor/plugin/services.rb +1 -1
- data/lib/rigor/plugin/type_node_resolver.rb +52 -0
- data/lib/rigor/plugin.rb +2 -0
- data/lib/rigor/rbs_extended/reporter.rb +91 -0
- data/lib/rigor/rbs_extended.rb +131 -32
- data/lib/rigor/scope.rb +25 -8
- data/lib/rigor/sig_gen/classification.rb +36 -0
- data/lib/rigor/sig_gen/generator.rb +1048 -0
- data/lib/rigor/sig_gen/layout_index.rb +108 -0
- data/lib/rigor/sig_gen/method_candidate.rb +62 -0
- data/lib/rigor/sig_gen/observation_collector.rb +391 -0
- data/lib/rigor/sig_gen/observed_call.rb +62 -0
- data/lib/rigor/sig_gen/path_mapper.rb +116 -0
- data/lib/rigor/sig_gen/renderer.rb +157 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
- data/lib/rigor/sig_gen/write_result.rb +48 -0
- data/lib/rigor/sig_gen/writer.rb +530 -0
- data/lib/rigor/sig_gen.rb +25 -0
- data/lib/rigor/trinary.rb +15 -11
- data/lib/rigor/type/bot.rb +6 -3
- data/lib/rigor/type/bound_method.rb +79 -0
- data/lib/rigor/type/combinator.rb +207 -3
- data/lib/rigor/type/constant.rb +13 -0
- data/lib/rigor/type/hash_shape.rb +0 -2
- data/lib/rigor/type/integer_range.rb +7 -7
- data/lib/rigor/type/refined.rb +18 -12
- data/lib/rigor/type/top.rb +4 -3
- data/lib/rigor/type/union.rb +20 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +68 -0
- data/lib/rigor/type_node/identifier.rb +38 -0
- data/lib/rigor/type_node/indexed_access.rb +41 -0
- data/lib/rigor/type_node/integer_literal.rb +29 -0
- data/lib/rigor/type_node/name_scope.rb +52 -0
- data/lib/rigor/type_node/resolver_chain.rb +56 -0
- data/lib/rigor/type_node/string_literal.rb +32 -0
- data/lib/rigor/type_node/symbol_literal.rb +28 -0
- data/lib/rigor/type_node/union.rb +42 -0
- data/lib/rigor/type_node.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -0
- data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
- data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
- data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
- data/sig/rigor/cli/diff_command.rbs +4 -0
- data/sig/rigor/cli/explain_command.rbs +4 -0
- data/sig/rigor/cli/sig_gen_command.rbs +4 -0
- data/sig/rigor/cli/type_scan_command.rbs +3 -0
- data/sig/rigor/environment.rbs +8 -2
- data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
- data/sig/rigor/inference/builtins.rbs +2 -0
- data/sig/rigor/plugin/access_denied_error.rbs +3 -0
- data/sig/rigor/plugin/base.rbs +6 -0
- data/sig/rigor/plugin/blueprint.rbs +7 -0
- data/sig/rigor/plugin/fact_store.rbs +11 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/load_error.rbs +6 -0
- data/sig/rigor/plugin/loader.rbs +20 -0
- data/sig/rigor/plugin/manifest.rbs +9 -0
- data/sig/rigor/plugin/registry.rbs +16 -0
- data/sig/rigor/plugin/services.rbs +3 -0
- data/sig/rigor/plugin/trust_policy.rbs +4 -0
- data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
- data/sig/rigor/plugin.rbs +8 -0
- data/sig/rigor/scope.rbs +4 -2
- data/sig/rigor/type.rbs +28 -6
- data/sig/rigor.rbs +35 -2
- metadata +90 -1
|
@@ -24,10 +24,8 @@ module Rigor
|
|
|
24
24
|
# ADR-2 § "Plugin Diagnostic Provenance") let consumers
|
|
25
25
|
# distinguish where a diagnostic originated without committing
|
|
26
26
|
# to the plugin API itself.
|
|
27
|
-
# rubocop:disable Metrics/ParameterLists
|
|
28
27
|
def initialize(path:, line:, column:, message:, severity: :error, rule: nil,
|
|
29
28
|
source_family: DEFAULT_SOURCE_FAMILY)
|
|
30
|
-
# rubocop:enable Metrics/ParameterLists
|
|
31
29
|
@path = path
|
|
32
30
|
@line = line
|
|
33
31
|
@column = column
|
|
@@ -18,7 +18,7 @@ module Rigor
|
|
|
18
18
|
relational
|
|
19
19
|
].freeze
|
|
20
20
|
|
|
21
|
-
Target
|
|
21
|
+
class Target < Data.define(:kind, :name)
|
|
22
22
|
def self.local(name)
|
|
23
23
|
new(kind: :local, name: name.to_sym)
|
|
24
24
|
end
|
|
@@ -28,7 +28,7 @@ module Rigor
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
Fact
|
|
31
|
+
class Fact < Data.define(:bucket, :target, :predicate, :payload, :polarity, :stability)
|
|
32
32
|
def initialize(bucket:, target:, predicate:, payload: nil, polarity: :positive, stability: :local_binding)
|
|
33
33
|
bucket = bucket.to_sym
|
|
34
34
|
raise ArgumentError, "unknown fact bucket #{bucket.inspect}" unless BUCKETS.include?(bucket)
|
|
@@ -47,9 +47,14 @@ module Rigor
|
|
|
47
47
|
attr_reader :facts
|
|
48
48
|
|
|
49
49
|
class << self
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
# ADR-15 Phase 4b.x — return the eagerly-loaded
|
|
51
|
+
# singleton-class `@empty` ivar. Lazy `@empty ||= new`
|
|
52
|
+
# would write to a class/module ivar from non-main
|
|
53
|
+
# Ractors and trip `Ractor::IsolationError`. The
|
|
54
|
+
# `@empty = new.freeze` at module body below
|
|
55
|
+
# pre-populates the ivar on the main Ractor at load
|
|
56
|
+
# time.
|
|
57
|
+
attr_reader :empty
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def initialize(facts: [])
|
|
@@ -125,9 +130,24 @@ module Rigor
|
|
|
125
130
|
unique.freeze
|
|
126
131
|
end
|
|
127
132
|
|
|
133
|
+
# `fact.target` is `Target | Array[Target]` per the carrier
|
|
134
|
+
# contract. Branching with an early return on the `Array`
|
|
135
|
+
# arm lets type narrowing collapse the post-return value to
|
|
136
|
+
# the bare `Target` case, so the wrapped tuple is `[Target]`
|
|
137
|
+
# and the union of return paths is exactly `Array[Target]`.
|
|
128
138
|
def fact_targets(fact)
|
|
129
|
-
|
|
139
|
+
target = fact.target
|
|
140
|
+
return target if target.is_a?(Array)
|
|
141
|
+
|
|
142
|
+
[target]
|
|
130
143
|
end
|
|
144
|
+
|
|
145
|
+
# ADR-15 Phase 4b.x — eager-load the singleton `@empty`
|
|
146
|
+
# on the main Ractor at module-load time. Workers then
|
|
147
|
+
# READ the populated ivar without ever attempting a
|
|
148
|
+
# class/module ivar WRITE (which non-main Ractors are
|
|
149
|
+
# forbidden from doing).
|
|
150
|
+
@empty = new.freeze
|
|
131
151
|
end
|
|
132
152
|
end
|
|
133
153
|
end
|
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Analysis
|
|
5
5
|
class Result
|
|
6
|
-
attr_reader :diagnostics
|
|
6
|
+
attr_reader :diagnostics, :stats
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# @param stats [Rigor::Analysis::RunStats, nil] end-of-run
|
|
9
|
+
# telemetry (target file count, RBS class breakdown,
|
|
10
|
+
# wall + RSS) collected by the Runner. Nil when stats
|
|
11
|
+
# collection wasn't requested or wasn't applicable
|
|
12
|
+
# (early-exit paths like `validate_target_ruby` failure).
|
|
13
|
+
def initialize(diagnostics: [], stats: nil)
|
|
9
14
|
@diagnostics = diagnostics
|
|
15
|
+
@stats = stats
|
|
10
16
|
end
|
|
11
17
|
|
|
12
18
|
def success?
|
|
@@ -18,11 +24,13 @@ module Rigor
|
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def to_h
|
|
21
|
-
{
|
|
27
|
+
hash = {
|
|
22
28
|
"success" => success?,
|
|
23
29
|
"error_count" => error_count,
|
|
24
30
|
"diagnostics" => diagnostics.map(&:to_h)
|
|
25
31
|
}
|
|
32
|
+
hash["stats"] = @stats.to_h if @stats
|
|
33
|
+
hash
|
|
26
34
|
end
|
|
27
35
|
end
|
|
28
36
|
end
|
|
@@ -31,8 +31,8 @@ module Rigor
|
|
|
31
31
|
# from `Configuration::SeverityProfile::PROFILES`.
|
|
32
32
|
# - `since` — first version the rule shipped in.
|
|
33
33
|
module RuleCatalog # rubocop:disable Metrics/ModuleLength
|
|
34
|
-
Entry
|
|
35
|
-
|
|
34
|
+
class Entry < Data.define(:id, :summary, :fires_when, :does_not_fire_when,
|
|
35
|
+
:suppression, :severity_authored, :severity_by_profile, :since)
|
|
36
36
|
def aliases
|
|
37
37
|
CheckRules::LEGACY_RULE_ALIASES.select { |_legacy, canonical| canonical == id }.keys
|
|
38
38
|
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Analysis
|
|
7
|
+
# End-of-run telemetry for the `rigor check` CLI's `--stats`
|
|
8
|
+
# output. Captures four cheap-to-measure groups:
|
|
9
|
+
#
|
|
10
|
+
# - **Check targets** — the Ruby files the analyser actually
|
|
11
|
+
# walks for diagnostics (`expand_paths` output).
|
|
12
|
+
# - **Type universe** — RBS class/module declarations the
|
|
13
|
+
# analyser had visibility of, broken down by source:
|
|
14
|
+
# `project_sig` (declarations whose source file lives under
|
|
15
|
+
# the configured `signature_paths`) vs `bundled` (RBS core,
|
|
16
|
+
# stdlib libraries, gem-bundled RBS — everything outside
|
|
17
|
+
# the project's own `sig/` tree).
|
|
18
|
+
# - **Gem source-walk** — the ADR-10
|
|
19
|
+
# `dependencies.source_inference` catalogue. Reports the
|
|
20
|
+
# class count and the number of opt-in gems contributing.
|
|
21
|
+
# - **Process** — wall-clock seconds + peak resident set size.
|
|
22
|
+
#
|
|
23
|
+
# The split between "check targets" and "type universe" makes
|
|
24
|
+
# explicit that the analyser's diagnostic surface is bounded
|
|
25
|
+
# by the user-controlled `paths:` configuration; the (typically
|
|
26
|
+
# much larger) RBS class universe is symbol-discovery, not a
|
|
27
|
+
# diagnostic surface.
|
|
28
|
+
#
|
|
29
|
+
# Stats collection is intentionally cheap: wall + RSS are
|
|
30
|
+
# single syscalls, target file count is already in
|
|
31
|
+
# `expand_paths`, gem source-walk uses
|
|
32
|
+
# `Index#class_to_gem.size`, and the RBS class breakdown
|
|
33
|
+
# walks `class_decl_paths` (a frozen `Hash<String, String>`
|
|
34
|
+
# populated once per environment by the RBS loader; ~1000-2000
|
|
35
|
+
# entries × one `String#start_with?`).
|
|
36
|
+
class RunStats
|
|
37
|
+
attr_reader :wall_seconds, :peak_rss_bytes,
|
|
38
|
+
:target_files,
|
|
39
|
+
:rbs_classes_total, :rbs_classes_project_sig, :rbs_classes_bundled,
|
|
40
|
+
:gem_walk_classes, :gem_walk_gems, :rbs_attribution_available
|
|
41
|
+
|
|
42
|
+
def initialize(wall_seconds:, peak_rss_bytes:, # rubocop:disable Metrics/ParameterLists
|
|
43
|
+
target_files:,
|
|
44
|
+
rbs_classes_total:, rbs_classes_project_sig:, rbs_classes_bundled:,
|
|
45
|
+
gem_walk_classes:, gem_walk_gems:,
|
|
46
|
+
rbs_attribution_available: true)
|
|
47
|
+
@wall_seconds = wall_seconds
|
|
48
|
+
@peak_rss_bytes = peak_rss_bytes
|
|
49
|
+
@target_files = target_files
|
|
50
|
+
@rbs_classes_total = rbs_classes_total
|
|
51
|
+
@rbs_classes_project_sig = rbs_classes_project_sig
|
|
52
|
+
@rbs_classes_bundled = rbs_classes_bundled
|
|
53
|
+
@gem_walk_classes = gem_walk_classes
|
|
54
|
+
@gem_walk_gems = gem_walk_gems
|
|
55
|
+
@rbs_attribution_available = rbs_attribution_available
|
|
56
|
+
freeze
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Reports the process's resident set size in bytes. Source
|
|
60
|
+
# ordering: `/proc/self/status` (Linux — reads `VmHWM:`,
|
|
61
|
+
# the peak RSS the kernel records) first; otherwise
|
|
62
|
+
# `ps -o rss= -p <pid>` (macOS / BSD — reports CURRENT
|
|
63
|
+
# RSS, the closest universally-available proxy). Returns
|
|
64
|
+
# nil when neither route works so the formatter can render
|
|
65
|
+
# `unavailable` instead of misleading zero.
|
|
66
|
+
def self.peak_rss_bytes
|
|
67
|
+
from_proc = read_vmhwm_from_proc
|
|
68
|
+
return from_proc unless from_proc.nil?
|
|
69
|
+
|
|
70
|
+
from_ps = read_rss_via_ps
|
|
71
|
+
return from_ps unless from_ps.nil?
|
|
72
|
+
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.read_vmhwm_from_proc
|
|
77
|
+
return nil unless File.readable?("/proc/self/status")
|
|
78
|
+
|
|
79
|
+
File.foreach("/proc/self/status") do |line|
|
|
80
|
+
next unless line.start_with?("VmHWM:")
|
|
81
|
+
|
|
82
|
+
kb_token = line.split.find { |token| token.match?(/\A\d+\z/) }
|
|
83
|
+
return Integer(kb_token) * 1024 if kb_token
|
|
84
|
+
end
|
|
85
|
+
nil
|
|
86
|
+
rescue StandardError
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.read_rss_via_ps
|
|
91
|
+
out = `ps -o rss= -p #{Process.pid} 2>/dev/null`.strip
|
|
92
|
+
return nil if out.empty?
|
|
93
|
+
|
|
94
|
+
Integer(out) * 1024
|
|
95
|
+
rescue StandardError
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Source-attribution sentinel produced by `RBS::Environment`
|
|
100
|
+
# entries restored from a cached blob (Marshal-loaded
|
|
101
|
+
# `RBS::Environment` loses real file-path attribution; every
|
|
102
|
+
# buffer reports `"<cached>"`). When every entry carries
|
|
103
|
+
# this sentinel the partition_classes routine returns
|
|
104
|
+
# `[0, total]` AND `attribution_available: false`, which
|
|
105
|
+
# the format routine consumes to suppress the misleading
|
|
106
|
+
# breakdown row.
|
|
107
|
+
CACHED_SENTINEL = "<cached>"
|
|
108
|
+
|
|
109
|
+
# Computes `(project_sig, bundled)` counts from a frozen
|
|
110
|
+
# `Hash<class_name => source_path>` snapshot and the
|
|
111
|
+
# configured `signature_paths`. `project_sig` is the count
|
|
112
|
+
# of classes whose source path begins with any of the
|
|
113
|
+
# signature path prefixes (after expansion to absolute
|
|
114
|
+
# paths); `bundled` is the remainder.
|
|
115
|
+
def self.partition_classes(class_decl_paths:, signature_paths:)
|
|
116
|
+
prefixes = Array(signature_paths).map { |p| File.expand_path(p.to_s) }
|
|
117
|
+
return [0, class_decl_paths.size] if prefixes.empty?
|
|
118
|
+
|
|
119
|
+
project = 0
|
|
120
|
+
class_decl_paths.each_value do |path|
|
|
121
|
+
expanded = File.expand_path(path)
|
|
122
|
+
project += 1 if prefixes.any? { |prefix| expanded.start_with?("#{prefix}/") || expanded == prefix }
|
|
123
|
+
end
|
|
124
|
+
[project, class_decl_paths.size - project]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# True when at least one entry in `class_decl_paths` carries
|
|
128
|
+
# a real source file path (i.e. not the cached-sentinel
|
|
129
|
+
# marker). Used by callers to decide whether the
|
|
130
|
+
# `project_sig` / `bundled` split is meaningful.
|
|
131
|
+
def self.attribution_available?(class_decl_paths:)
|
|
132
|
+
return false if class_decl_paths.empty?
|
|
133
|
+
|
|
134
|
+
class_decl_paths.each_value.any? { |path| path != CACHED_SENTINEL }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Writes a human-facing rendering of the stats to `out`
|
|
138
|
+
# (typically `$stderr` from the CLI). Format is intentionally
|
|
139
|
+
# plain text — JSON consumers should parse the structured
|
|
140
|
+
# output of `rigor check --format=json` and consult `stats`
|
|
141
|
+
# there.
|
|
142
|
+
def format(out, prefix: "")
|
|
143
|
+
out.puts("#{prefix}Check targets")
|
|
144
|
+
out.puts("#{prefix} Ruby source files: #{@target_files}")
|
|
145
|
+
out.puts("#{prefix}Type universe (symbol discovery; not analyzed for diagnostics)")
|
|
146
|
+
out.puts("#{prefix} RBS classes available: #{@rbs_classes_total}")
|
|
147
|
+
if @rbs_attribution_available
|
|
148
|
+
out.puts("#{prefix} project sig/: #{@rbs_classes_project_sig}")
|
|
149
|
+
out.puts("#{prefix} bundled (core+stdlib+gems): #{@rbs_classes_bundled}")
|
|
150
|
+
elsif @rbs_classes_total.positive?
|
|
151
|
+
out.puts("#{prefix} (source attribution unavailable on cache-hit runs; --no-cache surfaces it)")
|
|
152
|
+
end
|
|
153
|
+
if @gem_walk_gems.positive?
|
|
154
|
+
out.puts("#{prefix} Gem source-walk classes: #{@gem_walk_classes} " \
|
|
155
|
+
"(across #{@gem_walk_gems} #{@gem_walk_gems == 1 ? 'gem' : 'gems'} " \
|
|
156
|
+
"via dependencies.source_inference)")
|
|
157
|
+
end
|
|
158
|
+
out.puts("#{prefix}Process")
|
|
159
|
+
out.puts("#{prefix} Wall time: #{Kernel.format('%.2fs', @wall_seconds)}")
|
|
160
|
+
out.puts("#{prefix} Memory peak: #{format_bytes(@peak_rss_bytes)}")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def to_h
|
|
164
|
+
{
|
|
165
|
+
target_files: @target_files,
|
|
166
|
+
rbs_classes_total: @rbs_classes_total,
|
|
167
|
+
rbs_classes_project_sig: @rbs_classes_project_sig,
|
|
168
|
+
rbs_classes_bundled: @rbs_classes_bundled,
|
|
169
|
+
rbs_attribution_available: @rbs_attribution_available,
|
|
170
|
+
gem_walk_classes: @gem_walk_classes,
|
|
171
|
+
gem_walk_gems: @gem_walk_gems,
|
|
172
|
+
wall_seconds: @wall_seconds,
|
|
173
|
+
peak_rss_bytes: @peak_rss_bytes
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
def format_bytes(bytes)
|
|
180
|
+
return "unavailable" if bytes.nil?
|
|
181
|
+
|
|
182
|
+
units = %w[B KB MB GB TB]
|
|
183
|
+
size = bytes.to_f
|
|
184
|
+
index = 0
|
|
185
|
+
while size >= 1024 && index < units.size - 1
|
|
186
|
+
size /= 1024
|
|
187
|
+
index += 1
|
|
188
|
+
end
|
|
189
|
+
Kernel.format("%<size>.1f %<unit>s", size: size, unit: units[index])
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|