rigortype 0.0.8 → 0.1.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 +234 -22
- data/data/builtins/ruby_core/encoding.yml +210 -0
- data/data/builtins/ruby_core/exception.yml +641 -0
- data/data/builtins/ruby_core/numeric.yml +3 -2
- data/data/builtins/ruby_core/proc.yml +731 -0
- data/data/builtins/ruby_core/random.yml +166 -0
- data/data/builtins/ruby_core/re.yml +689 -0
- data/data/builtins/ruby_core/struct.yml +449 -0
- data/lib/rigor/analysis/check_rules.rb +228 -40
- data/lib/rigor/analysis/diagnostic.rb +15 -1
- data/lib/rigor/analysis/runner.rb +199 -4
- data/lib/rigor/builtins/imported_refinements.rb +6 -1
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +63 -0
- data/lib/rigor/cache/rbs_class_type_param_names.rb +60 -0
- data/lib/rigor/cache/rbs_constant_table.rb +15 -51
- data/lib/rigor/cache/rbs_descriptor.rb +55 -0
- data/lib/rigor/cache/rbs_environment.rb +52 -0
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +40 -0
- data/lib/rigor/cache/rbs_instance_definitions.rb +79 -0
- data/lib/rigor/cache/rbs_known_class_names.rb +43 -0
- data/lib/rigor/cache/store.rb +81 -15
- data/lib/rigor/cli.rb +45 -7
- data/lib/rigor/configuration/severity_profile.rb +109 -0
- data/lib/rigor/configuration.rb +110 -6
- data/lib/rigor/environment/rbs_hierarchy.rb +18 -5
- data/lib/rigor/environment/rbs_loader.rb +220 -32
- data/lib/rigor/environment.rb +11 -2
- data/lib/rigor/flow_contribution/conflict.rb +81 -0
- data/lib/rigor/flow_contribution/element.rb +53 -0
- data/lib/rigor/flow_contribution/fact.rb +88 -0
- data/lib/rigor/flow_contribution/merge_result.rb +67 -0
- data/lib/rigor/flow_contribution/merger.rb +275 -0
- data/lib/rigor/flow_contribution.rb +179 -0
- data/lib/rigor/inference/block_parameter_binder.rb +15 -0
- data/lib/rigor/inference/builtins/encoding_catalog.rb +67 -0
- data/lib/rigor/inference/builtins/exception_catalog.rb +92 -0
- data/lib/rigor/inference/builtins/proc_catalog.rb +122 -0
- data/lib/rigor/inference/builtins/random_catalog.rb +58 -0
- data/lib/rigor/inference/builtins/re_catalog.rb +81 -0
- data/lib/rigor/inference/builtins/struct_catalog.rb +55 -0
- data/lib/rigor/inference/expression_typer.rb +110 -6
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +16 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +173 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +21 -1
- data/lib/rigor/inference/method_dispatcher.rb +2 -0
- data/lib/rigor/inference/multi_target_binder.rb +2 -0
- data/lib/rigor/inference/narrowing.rb +134 -144
- data/lib/rigor/inference/scope_indexer.rb +75 -1
- data/lib/rigor/inference/statement_evaluator.rb +380 -40
- data/lib/rigor/plugin/access_denied_error.rb +24 -0
- data/lib/rigor/plugin/base.rb +241 -0
- data/lib/rigor/plugin/io_boundary.rb +102 -0
- data/lib/rigor/plugin/load_error.rb +23 -0
- data/lib/rigor/plugin/loader.rb +191 -0
- data/lib/rigor/plugin/manifest.rb +134 -0
- data/lib/rigor/plugin/registry.rb +50 -0
- data/lib/rigor/plugin/services.rb +65 -0
- data/lib/rigor/plugin/trust_policy.rb +99 -0
- data/lib/rigor/plugin.rb +61 -0
- data/lib/rigor/rbs_extended.rb +103 -0
- data/lib/rigor/reflection.rb +2 -2
- data/lib/rigor/type/combinator.rb +72 -0
- data/lib/rigor/type/refined.rb +50 -2
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +13 -0
- data/sig/rigor/environment.rbs +7 -1
- data/sig/rigor/inference.rbs +1 -0
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +7 -0
- data/sig/rigor.rbs +3 -1
- metadata +38 -1
data/lib/rigor/cli.rb
CHANGED
|
@@ -72,13 +72,19 @@ module Rigor
|
|
|
72
72
|
|
|
73
73
|
cache_root = ".rigor/cache"
|
|
74
74
|
handle_clear_cache(cache_root) if options.fetch(:clear_cache)
|
|
75
|
+
cache_store = options.fetch(:no_cache) ? nil : Cache::Store.new(root: cache_root)
|
|
75
76
|
|
|
76
77
|
configuration = Configuration.load(options.fetch(:config))
|
|
77
78
|
paths = @argv.empty? ? configuration.paths : @argv
|
|
78
|
-
|
|
79
|
+
runner = Analysis::Runner.new(
|
|
80
|
+
configuration: configuration,
|
|
81
|
+
explain: options.fetch(:explain),
|
|
82
|
+
cache_store: cache_store
|
|
83
|
+
)
|
|
84
|
+
result = runner.run(paths)
|
|
79
85
|
|
|
80
86
|
write_result(result, options.fetch(:format))
|
|
81
|
-
write_cache_stats(cache_root) if options.fetch(:cache_stats)
|
|
87
|
+
write_cache_stats(cache_root, runner.cache_store) if options.fetch(:cache_stats)
|
|
82
88
|
result.success? ? 0 : 1
|
|
83
89
|
end
|
|
84
90
|
|
|
@@ -88,7 +94,8 @@ module Rigor
|
|
|
88
94
|
format: "text",
|
|
89
95
|
explain: false,
|
|
90
96
|
cache_stats: false,
|
|
91
|
-
clear_cache: false
|
|
97
|
+
clear_cache: false,
|
|
98
|
+
no_cache: false
|
|
92
99
|
}
|
|
93
100
|
parser = OptionParser.new do |opts|
|
|
94
101
|
opts.banner = "Usage: rigor check [options] [paths]"
|
|
@@ -97,6 +104,7 @@ module Rigor
|
|
|
97
104
|
opts.on("--explain", "Surface fail-soft fallback events as :info diagnostics") { options[:explain] = true }
|
|
98
105
|
opts.on("--cache-stats", "Print on-disk cache inventory at end of run") { options[:cache_stats] = true }
|
|
99
106
|
opts.on("--clear-cache", "Remove the .rigor/cache directory before running") { options[:clear_cache] = true }
|
|
107
|
+
opts.on("--no-cache", "Disable the persistent cache for this run") { options[:no_cache] = true }
|
|
100
108
|
end
|
|
101
109
|
parser.parse!(@argv)
|
|
102
110
|
options
|
|
@@ -111,13 +119,18 @@ module Rigor
|
|
|
111
119
|
end
|
|
112
120
|
end
|
|
113
121
|
|
|
114
|
-
def write_cache_stats(cache_root)
|
|
122
|
+
def write_cache_stats(cache_root, runtime_store)
|
|
115
123
|
inv = Cache::Store.disk_inventory(root: cache_root)
|
|
116
124
|
|
|
117
125
|
@out.puts("")
|
|
118
126
|
@out.puts("Cache (root: #{inv.fetch(:root)})")
|
|
119
127
|
schema = inv.fetch(:schema_version)
|
|
120
128
|
@out.puts(" schema_version: #{schema.nil? ? 'absent' : schema}")
|
|
129
|
+
write_disk_inventory(inv)
|
|
130
|
+
write_runtime_stats(runtime_store) if runtime_store
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def write_disk_inventory(inv)
|
|
121
134
|
if inv.fetch(:total_entries).zero?
|
|
122
135
|
@out.puts(" (empty)")
|
|
123
136
|
return
|
|
@@ -130,6 +143,25 @@ module Rigor
|
|
|
130
143
|
end
|
|
131
144
|
end
|
|
132
145
|
|
|
146
|
+
def write_runtime_stats(store)
|
|
147
|
+
stats = store.stats
|
|
148
|
+
hits = stats.fetch(:hits)
|
|
149
|
+
misses = stats.fetch(:misses)
|
|
150
|
+
writes = stats.fetch(:writes)
|
|
151
|
+
@out.puts(" this run: #{hits} #{plural(hits, 'hit')}, " \
|
|
152
|
+
"#{misses} #{plural(misses, 'miss', 'misses')}, " \
|
|
153
|
+
"#{writes} #{plural(writes, 'write')}")
|
|
154
|
+
stats.fetch(:by_producer).each do |id, counts|
|
|
155
|
+
@out.puts(" #{id}: #{counts.fetch(:hits)} #{plural(counts.fetch(:hits), 'hit')}, " \
|
|
156
|
+
"#{counts.fetch(:misses)} #{plural(counts.fetch(:misses), 'miss', 'misses')}, " \
|
|
157
|
+
"#{counts.fetch(:writes)} #{plural(counts.fetch(:writes), 'write')}")
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def plural(count, singular, plural = "#{singular}s")
|
|
162
|
+
count == 1 ? singular : plural
|
|
163
|
+
end
|
|
164
|
+
|
|
133
165
|
def format_bytes(bytes)
|
|
134
166
|
return "#{bytes} B" if bytes < 1024
|
|
135
167
|
return format("%.1f KiB", bytes / 1024.0) if bytes < 1024 * 1024
|
|
@@ -179,9 +211,15 @@ module Rigor
|
|
|
179
211
|
# (no plugins are loaded today).
|
|
180
212
|
# - disable: list of `rigor check` rule identifiers to
|
|
181
213
|
# silence project-wide. The shipped rules are
|
|
182
|
-
# undefined-method, wrong-arity,
|
|
183
|
-
# argument-type-mismatch,
|
|
184
|
-
#
|
|
214
|
+
# call.undefined-method, call.wrong-arity,
|
|
215
|
+
# call.argument-type-mismatch,
|
|
216
|
+
# call.possible-nil-receiver, dump.type,
|
|
217
|
+
# assert.type-mismatch, flow.always-raises.
|
|
218
|
+
# A bare family token (`call`, `flow`,
|
|
219
|
+
# `assert`, `dump`, `def`) wildcards every
|
|
220
|
+
# rule under that prefix. Legacy unprefixed
|
|
221
|
+
# names (`undefined-method`, …) still
|
|
222
|
+
# resolve. In-source
|
|
185
223
|
# `# rigor:disable <rule>` comments at the end
|
|
186
224
|
# of an offending line silence per-line; use
|
|
187
225
|
# `# rigor:disable all` to suppress every rule.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
class Configuration
|
|
5
|
+
# ADR-8 § "Severity profile" — three named profiles tune the
|
|
6
|
+
# severity of every built-in `Analysis::CheckRules` rule for
|
|
7
|
+
# the run. Profiles are applied as a **final filter** on
|
|
8
|
+
# `Diagnostic#severity`: rules emit with their authored
|
|
9
|
+
# severity, then `Analysis::Runner` re-stamps the severity
|
|
10
|
+
# from the active profile before adding the diagnostic to
|
|
11
|
+
# the result.
|
|
12
|
+
#
|
|
13
|
+
# Three profiles:
|
|
14
|
+
#
|
|
15
|
+
# - `lenient`: Only proven (`:no`) diagnostics are errors;
|
|
16
|
+
# uncertain (`:maybe`) drop to `:warning`. Useful for
|
|
17
|
+
# incremental adoption on legacy code.
|
|
18
|
+
# - `balanced` (**default**): Current Rigor stance — most
|
|
19
|
+
# rules `:error`; `dump.type` `:info`; uncertain rules
|
|
20
|
+
# `:warning`.
|
|
21
|
+
# - `strict`: Every rule is `:error`. CI-friendly.
|
|
22
|
+
#
|
|
23
|
+
# The profile resolution order:
|
|
24
|
+
#
|
|
25
|
+
# 1. Profile-specific entry for the canonical rule id.
|
|
26
|
+
# 2. The diagnostic's own authored severity (the rule's
|
|
27
|
+
# default).
|
|
28
|
+
# 3. `:error` (catch-all so an unrecognised rule still emits
|
|
29
|
+
# visibly — the public-API drift spec catches the
|
|
30
|
+
# bookkeeping gap separately).
|
|
31
|
+
module SeverityProfile
|
|
32
|
+
VALID_PROFILES = %i[lenient balanced strict].freeze
|
|
33
|
+
VALID_SEVERITIES = %i[error warning info off].freeze
|
|
34
|
+
|
|
35
|
+
DEFAULT_PROFILE = :balanced
|
|
36
|
+
|
|
37
|
+
# Per-profile severity tables. Missing keys fall back to
|
|
38
|
+
# the diagnostic's authored severity (typically `:error`).
|
|
39
|
+
PROFILES = {
|
|
40
|
+
lenient: {
|
|
41
|
+
"call.undefined-method" => :error,
|
|
42
|
+
"call.wrong-arity" => :error,
|
|
43
|
+
"call.argument-type-mismatch" => :warning,
|
|
44
|
+
"call.possible-nil-receiver" => :warning,
|
|
45
|
+
"flow.always-raises" => :warning,
|
|
46
|
+
"assert.type-mismatch" => :error,
|
|
47
|
+
"dump.type" => :info,
|
|
48
|
+
"def.return-type-mismatch" => :warning
|
|
49
|
+
}.freeze,
|
|
50
|
+
balanced: {
|
|
51
|
+
"call.undefined-method" => :error,
|
|
52
|
+
"call.wrong-arity" => :error,
|
|
53
|
+
"call.argument-type-mismatch" => :error,
|
|
54
|
+
"call.possible-nil-receiver" => :error,
|
|
55
|
+
"flow.always-raises" => :error,
|
|
56
|
+
"assert.type-mismatch" => :error,
|
|
57
|
+
"dump.type" => :info,
|
|
58
|
+
"def.return-type-mismatch" => :warning
|
|
59
|
+
}.freeze,
|
|
60
|
+
strict: {
|
|
61
|
+
"call.undefined-method" => :error,
|
|
62
|
+
"call.wrong-arity" => :error,
|
|
63
|
+
"call.argument-type-mismatch" => :error,
|
|
64
|
+
"call.possible-nil-receiver" => :error,
|
|
65
|
+
"flow.always-raises" => :error,
|
|
66
|
+
"assert.type-mismatch" => :error,
|
|
67
|
+
"dump.type" => :error,
|
|
68
|
+
"def.return-type-mismatch" => :error
|
|
69
|
+
}.freeze
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
72
|
+
module_function
|
|
73
|
+
|
|
74
|
+
# Resolves the configured severity for a diagnostic given
|
|
75
|
+
# the active profile and any per-rule overrides.
|
|
76
|
+
#
|
|
77
|
+
# @param rule [String, nil] canonical rule id (`call.undefined-method`).
|
|
78
|
+
# @param authored_severity [Symbol] severity the rule emitted
|
|
79
|
+
# the diagnostic with (`:error`, `:warning`, `:info`).
|
|
80
|
+
# @param profile [Symbol] one of {VALID_PROFILES}; falls back
|
|
81
|
+
# to {DEFAULT_PROFILE} for unknown values.
|
|
82
|
+
# @param overrides [Hash{String => Symbol}] per-rule severity
|
|
83
|
+
# overrides from `.rigor.yml`'s `severity_overrides:` map.
|
|
84
|
+
# Keys are canonical rule ids; values are
|
|
85
|
+
# {VALID_SEVERITIES} symbols. Family-wildcard keys
|
|
86
|
+
# (`call`) match every rule under that prefix.
|
|
87
|
+
# @return [Symbol] the resolved severity. Returns `:off` to
|
|
88
|
+
# mean "drop the diagnostic entirely".
|
|
89
|
+
def resolve(rule:, authored_severity:, profile: DEFAULT_PROFILE, overrides: {})
|
|
90
|
+
return authored_severity if rule.nil?
|
|
91
|
+
|
|
92
|
+
override = overrides[rule] || family_override(rule, overrides)
|
|
93
|
+
return override.to_sym if override
|
|
94
|
+
|
|
95
|
+
profile_table = PROFILES[profile] || PROFILES.fetch(DEFAULT_PROFILE)
|
|
96
|
+
profile_table.fetch(rule, authored_severity)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def family_override(rule, overrides)
|
|
100
|
+
family = rule.split(".").first
|
|
101
|
+
return nil if family.nil?
|
|
102
|
+
|
|
103
|
+
overrides[family]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private_class_method :family_override
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
|
|
5
|
+
require_relative "configuration/severity_profile"
|
|
6
|
+
|
|
5
7
|
module Rigor
|
|
6
|
-
class Configuration
|
|
8
|
+
class Configuration # rubocop:disable Metrics/ClassLength
|
|
7
9
|
DEFAULT_PATH = ".rigor.yml"
|
|
8
10
|
DEFAULTS = {
|
|
9
11
|
"target_ruby" => "4.0",
|
|
@@ -15,11 +17,19 @@ module Rigor
|
|
|
15
17
|
"fold_platform_specific_paths" => false,
|
|
16
18
|
"cache" => {
|
|
17
19
|
"path" => ".rigor/cache"
|
|
18
|
-
}
|
|
20
|
+
},
|
|
21
|
+
"plugins_io" => {
|
|
22
|
+
"network" => "disabled",
|
|
23
|
+
"allowed_paths" => []
|
|
24
|
+
},
|
|
25
|
+
"severity_profile" => "balanced",
|
|
26
|
+
"severity_overrides" => {}
|
|
19
27
|
}.freeze
|
|
20
28
|
|
|
21
29
|
attr_reader :target_ruby, :paths, :plugins, :cache_path, :disabled_rules,
|
|
22
|
-
:libraries, :signature_paths, :fold_platform_specific_paths
|
|
30
|
+
:libraries, :signature_paths, :fold_platform_specific_paths,
|
|
31
|
+
:plugins_io_network, :plugins_io_allowed_paths,
|
|
32
|
+
:severity_profile, :severity_overrides
|
|
23
33
|
|
|
24
34
|
def self.load(path = DEFAULT_PATH)
|
|
25
35
|
data = if File.exist?(path)
|
|
@@ -31,12 +41,15 @@ module Rigor
|
|
|
31
41
|
new(DEFAULTS.merge(data))
|
|
32
42
|
end
|
|
33
43
|
|
|
34
|
-
def initialize(data = DEFAULTS) # rubocop:disable Metrics/AbcSize
|
|
44
|
+
def initialize(data = DEFAULTS) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
|
|
35
45
|
cache = DEFAULTS.fetch("cache").merge(data.fetch("cache", {}))
|
|
46
|
+
plugins_io = DEFAULTS.fetch("plugins_io").merge(data.fetch("plugins_io", {}))
|
|
36
47
|
|
|
37
48
|
@target_ruby = data.fetch("target_ruby", DEFAULTS.fetch("target_ruby")).to_s
|
|
38
49
|
@paths = Array(data.fetch("paths", DEFAULTS.fetch("paths"))).map(&:to_s)
|
|
39
|
-
@plugins = Array(data.fetch("plugins", DEFAULTS.fetch("plugins"))).map
|
|
50
|
+
@plugins = Array(data.fetch("plugins", DEFAULTS.fetch("plugins"))).map do |entry|
|
|
51
|
+
coerce_plugin_entry(entry)
|
|
52
|
+
end.freeze
|
|
40
53
|
@disabled_rules = Array(data.fetch("disable", DEFAULTS.fetch("disable"))).map(&:to_s).freeze
|
|
41
54
|
@libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
|
|
42
55
|
sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
|
|
@@ -45,6 +58,14 @@ module Rigor
|
|
|
45
58
|
"fold_platform_specific_paths", DEFAULTS.fetch("fold_platform_specific_paths")
|
|
46
59
|
) == true
|
|
47
60
|
@cache_path = cache.fetch("path").to_s
|
|
61
|
+
@plugins_io_network = coerce_network_policy(plugins_io.fetch("network"))
|
|
62
|
+
@plugins_io_allowed_paths = Array(plugins_io.fetch("allowed_paths")).map(&:to_s).freeze
|
|
63
|
+
@severity_profile = coerce_severity_profile(
|
|
64
|
+
data.fetch("severity_profile", DEFAULTS.fetch("severity_profile"))
|
|
65
|
+
)
|
|
66
|
+
@severity_overrides = coerce_severity_overrides(
|
|
67
|
+
data.fetch("severity_overrides", DEFAULTS.fetch("severity_overrides"))
|
|
68
|
+
)
|
|
48
69
|
end
|
|
49
70
|
|
|
50
71
|
def to_h
|
|
@@ -58,8 +79,91 @@ module Rigor
|
|
|
58
79
|
"fold_platform_specific_paths" => fold_platform_specific_paths,
|
|
59
80
|
"cache" => {
|
|
60
81
|
"path" => cache_path
|
|
61
|
-
}
|
|
82
|
+
},
|
|
83
|
+
"plugins_io" => {
|
|
84
|
+
"network" => plugins_io_network.to_s,
|
|
85
|
+
"allowed_paths" => plugins_io_allowed_paths
|
|
86
|
+
},
|
|
87
|
+
"severity_profile" => severity_profile.to_s,
|
|
88
|
+
"severity_overrides" => severity_overrides.to_h { |k, v| [k, v.to_s] }
|
|
62
89
|
}
|
|
63
90
|
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Accepts either `"rigor-foo"` (gem-name shorthand) or
|
|
95
|
+
# `{ "gem" => "rigor-foo", "id" => "foo", "config" => {...} }`
|
|
96
|
+
# (full form). Returns the canonical hash form so the loader
|
|
97
|
+
# works against a single shape.
|
|
98
|
+
def coerce_plugin_entry(entry)
|
|
99
|
+
case entry
|
|
100
|
+
when String
|
|
101
|
+
entry.dup.freeze
|
|
102
|
+
when Hash
|
|
103
|
+
entry.to_h { |k, v| [k.to_s, v] }.freeze
|
|
104
|
+
else
|
|
105
|
+
raise ArgumentError,
|
|
106
|
+
"plugin configuration entry must be a String or Hash, got #{entry.inspect}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Slice 2 only accepts `:disabled` for the network policy. The
|
|
111
|
+
# YAML scalar may arrive as a String (`"disabled"`) or already
|
|
112
|
+
# as the Symbol; coerce to the canonical Symbol shape so the
|
|
113
|
+
# downstream `TrustPolicy` constructor stays strict.
|
|
114
|
+
#
|
|
115
|
+
# The accepted set is duplicated from
|
|
116
|
+
# {Rigor::Plugin::TrustPolicy::VALID_NETWORK_POLICIES} so
|
|
117
|
+
# `Configuration` does not require the plugin namespace at
|
|
118
|
+
# load time (Configuration is loaded before Plugin in
|
|
119
|
+
# `lib/rigor.rb`); the two stay in lockstep via spec.
|
|
120
|
+
VALID_NETWORK_POLICIES = %i[disabled].freeze
|
|
121
|
+
private_constant :VALID_NETWORK_POLICIES
|
|
122
|
+
|
|
123
|
+
def coerce_network_policy(value)
|
|
124
|
+
sym = value.to_sym
|
|
125
|
+
unless VALID_NETWORK_POLICIES.include?(sym)
|
|
126
|
+
raise ArgumentError,
|
|
127
|
+
"plugins_io.network must be one of #{VALID_NETWORK_POLICIES.inspect}, got #{value.inspect}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
sym
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# ADR-8 § "Severity profile" — accepts the canonical Symbol
|
|
134
|
+
# form or its String spelling; rejects unknown profile names
|
|
135
|
+
# so typos fail loudly.
|
|
136
|
+
def coerce_severity_profile(value)
|
|
137
|
+
sym = value.to_sym
|
|
138
|
+
unless SeverityProfile::VALID_PROFILES.include?(sym)
|
|
139
|
+
raise ArgumentError,
|
|
140
|
+
"severity_profile must be one of " \
|
|
141
|
+
"#{SeverityProfile::VALID_PROFILES.inspect}, got #{value.inspect}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
sym
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# ADR-8 § "Severity profile" — `severity_overrides:` is a
|
|
148
|
+
# `{ rule => severity }` map. Keys are canonical rule ids
|
|
149
|
+
# (`call.undefined-method`) or family wildcards (`call`).
|
|
150
|
+
# Values are {SeverityProfile::VALID_SEVERITIES} symbols
|
|
151
|
+
# (`:error` / `:warning` / `:info` / `:off`). Unknown
|
|
152
|
+
# severities raise; unknown rule ids are silently kept (the
|
|
153
|
+
# override is inert until the rule lands).
|
|
154
|
+
def coerce_severity_overrides(value)
|
|
155
|
+
raise ArgumentError, "severity_overrides must be a Hash, got #{value.inspect}" unless value.is_a?(Hash)
|
|
156
|
+
|
|
157
|
+
value.to_h do |k, v|
|
|
158
|
+
sym = v.to_sym
|
|
159
|
+
unless SeverityProfile::VALID_SEVERITIES.include?(sym)
|
|
160
|
+
raise ArgumentError,
|
|
161
|
+
"severity_overrides[#{k.inspect}] must be one of " \
|
|
162
|
+
"#{SeverityProfile::VALID_SEVERITIES.inspect}, got #{v.inspect}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
[k.to_s, sym]
|
|
166
|
+
end.freeze
|
|
167
|
+
end
|
|
64
168
|
end
|
|
65
169
|
end
|
|
@@ -45,15 +45,28 @@ module Rigor
|
|
|
45
45
|
key = normalize_name(class_name)
|
|
46
46
|
return @ancestor_names_cache[key] if @ancestor_names_cache.key?(key)
|
|
47
47
|
|
|
48
|
-
definition = loader.instance_definition(key)
|
|
49
48
|
@ancestor_names_cache[key] =
|
|
50
|
-
if
|
|
51
|
-
|
|
49
|
+
if loader.cache_store
|
|
50
|
+
ancestor_table.fetch(key, [].freeze)
|
|
52
51
|
else
|
|
53
|
-
|
|
52
|
+
compute_ancestor_names(key)
|
|
54
53
|
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def compute_ancestor_names(key)
|
|
57
|
+
definition = loader.instance_definition(key)
|
|
58
|
+
return [].freeze if definition.nil?
|
|
59
|
+
|
|
60
|
+
definition.ancestors.ancestors.map { |ancestor| normalize_name(ancestor.name.to_s) }.uniq.freeze
|
|
55
61
|
rescue StandardError
|
|
56
|
-
|
|
62
|
+
[].freeze
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def ancestor_table
|
|
66
|
+
@ancestor_table ||= begin
|
|
67
|
+
require_relative "../cache/rbs_class_ancestor_table"
|
|
68
|
+
Cache::RbsClassAncestorTable.fetch(loader: loader, store: loader.cache_store)
|
|
69
|
+
end
|
|
57
70
|
end
|
|
58
71
|
|
|
59
72
|
def normalize_name(name)
|