Module: Rigor::ConfigAudit

Defined in:
lib/rigor/config_audit.rb

Overview

Audits a loaded Configuration for the class of mistake where a configured value silently resolves to nothing — a typo'd or moved path, an unknown library name, an inert rule id. The shared failure mode is that the loader filters the bad entry without a word, so the only symptom is downstream and confusing: missing signatures turn every call into the types they were meant to cover into a high-confidence call.undefined-method, and an unrecognised suppression token leaves the rule firing as if the disable: line were never written. This module surfaces each such entry up front so the cause is visible instead of inferred.

Every check is held to the same bar that SignaturePathAudit set: it mirrors the loader's own acceptance test, so a warning means the loader really did load nothing, and it never fires on a setup that works. In particular the rule-token check only flags a token under a built-in family (call, flow, …) — a plugin- or rbs_extended.* rule id (whose family Rigor cannot statically enumerate, since a node_rule block emits any rule: string it likes) is left alone, so disabling a plugin rule is never mistaken for a typo.

Defined Under Namespace

Classes: Warning

Class Method Summary collapse

Class Method Details

.add_missing_dir(warnings, path, project_root, kind, key) ⇒ Object



176
177
178
179
180
181
# File 'lib/rigor/config_audit.rb', line 176

def self.add_missing_dir(warnings, path, project_root, kind, key)
  return if path.nil? || File.directory?(File.expand_path(path, project_root))

  warnings << Warning.new(kind: kind, message: "#{key}: #{path.inspect} is not a directory",
                          fields: { "path" => path })
end

.add_missing_file(warnings, path, project_root, kind, key) ⇒ Object



183
184
185
186
187
# File 'lib/rigor/config_audit.rb', line 183

def self.add_missing_file(warnings, path, project_root, kind, key)
  return if path.nil? || File.file?(File.expand_path(path, project_root))

  warnings << Warning.new(kind: kind, message: "#{key}: #{path.inspect} does not exist", fields: { "path" => path })
end

.explicit_path_warnings(configuration, project_root) ⇒ Object

Explicitly-configured bundler / rbs-collection paths that do not exist. Only the explicit form is audited (a nil value means auto-detection, which finding nothing is normal); messages stay factual about the path rather than guessing the fallback.



165
166
167
168
169
170
171
172
173
174
# File 'lib/rigor/config_audit.rb', line 165

def self.explicit_path_warnings(configuration, project_root)
  warnings = []
  add_missing_dir(warnings, configuration.bundler_bundle_path, project_root,
                  :bundler_bundle_path, "bundler.bundle_path")
  add_missing_file(warnings, configuration.bundler_lockfile, project_root,
                   :bundler_lockfile, "bundler.lockfile")
  add_missing_file(warnings, configuration.rbs_collection_lockfile, project_root,
                   :rbs_collection_lockfile, "rbs_collection.lockfile")
  warnings
end

.inert_builtin_token?(token) ⇒ Boolean

True when token looks like a built-in rule id but matches none — its first segment is a built-in family yet it is neither a bare family wildcard nor a known canonical id. A token whose family is not built-in (a plugin / rbs_extended.* rule, or a bare legacy alias) is deliberately NOT flagged: it may resolve at run time, so under-warning is the FP-safe choice.

Returns:

  • (Boolean)


153
154
155
156
157
158
159
160
# File 'lib/rigor/config_audit.rb', line 153

def self.inert_builtin_token?(token)
  family = token.split(".").first
  return false unless Analysis::CheckRules::RULE_FAMILIES.include?(family)
  return false if token == family
  return false if Analysis::CheckRules::ALL_RULES.include?(token)

  true
end

.library_warnings(configuration) ⇒ Object

libraries: entries RBS does not recognise. Uses the same RBS::EnvironmentLoader#has_library? guard the loader filters through (Environment::RbsLoader build_env_for), so a flagged name is exactly one the loader skipped. Fails soft: if RBS itself raises, no warning rather than a crash.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rigor/config_audit.rb', line 107

def self.library_warnings(configuration)
  configured = Array(configuration.libraries)
  return [] if configured.empty?

  loader = ::RBS::EnvironmentLoader.new
  configured.reject { |lib| loader.has_library?(library: lib.to_s, version: nil) }.map do |lib|
    Warning.new(
      kind: :library,
      message: "libraries: #{lib.to_s.inspect} is not an available RBS library (no signatures loaded from it)",
      fields: { "name" => lib.to_s }
    )
  end
rescue StandardError
  []
end

.rule_token_warnings(configuration) ⇒ Object

disable: tokens and severity_overrides: keys that name no rule. Restricted to tokens under a built-in family so a plugin rule id is never mis-flagged (see the module docstring).



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/rigor/config_audit.rb', line 126

def self.rule_token_warnings(configuration)
  disable = Array(configuration.disabled_rules).filter_map do |token|
    next unless inert_builtin_token?(token.to_s)

    Warning.new(
      kind: :disabled_rule,
      message: "disable: #{token.to_s.inspect} is not a recognized rule id; the suppression has no effect",
      fields: { "token" => token.to_s }
    )
  end
  overrides = configuration.severity_overrides.keys.filter_map do |key|
    next unless inert_builtin_token?(key.to_s)

    Warning.new(
      kind: :severity_override,
      message: "severity_overrides: #{key.to_s.inspect} is not a recognized rule id; the override has no effect",
      fields: { "token" => key.to_s }
    )
  end
  disable + overrides
end

.signature_path_warnings(configuration) ⇒ Object

signature_paths: entries that resolve to nothing — delegated to SignaturePathAudit, which mirrors the loader's directory? + recursive **/*.rbs acceptance test.



93
94
95
96
97
98
99
100
101
# File 'lib/rigor/config_audit.rb', line 93

def self.signature_path_warnings(configuration)
  SignaturePathAudit.warnings(configuration.signature_paths).map do |entry|
    Warning.new(
      kind: :signature_path,
      message: entry.message,
      fields: { "path" => entry.path, "status" => entry.status.to_s, "rbs_file_count" => entry.rbs_file_count }
    )
  end
end

.unknown_key_warnings(configuration) ⇒ Object

Top-level keys the loader does not own. The archetypal case is a typo — excludee: for exclude: — which the loader drops in silence, so the exclusion never applies and the run reports errors from the very files the user meant to skip, with nothing anywhere saying the key did nothing. That is exactly this module's class of mistake, and it was the one whole-key instance of it going unwarned.

A reserved namespace (Rigor::Configuration::RESERVED_NAMESPACES) is unknown on purpose — another implementation reads it from the same file — so it is exempt. Rigor::Configuration#unknown_keys already applies that exemption; warning here would push users to delete the key their other tool needs.

Top level only, and deliberately. A nested check would have to name each group's known keys, and DEFAULTS cannot supply them: DEFAULTS["dependencies"] carries source_inference and budget_per_gem but not budget_overrun_strategy, which is real (Configuration::Dependencies reads it), documented, and schema-declared — so a DEFAULTS-keyed nested check would flag a working config. severity_overrides: is an open map of rule ids besides. Nested unknown keys are the schema tier's job: every nested object in schemas/rigor-config.schema.json is additionalProperties: false, and config_schema_spec keeps it complete (ADR-99, docs/internal-spec/config.md).



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rigor/config_audit.rb', line 70

def self.unknown_key_warnings(configuration)
  configuration.unknown_keys.map do |key|
    suggestion = suggest_config_key(key)
    hint = suggestion ? " Did you mean `#{suggestion}`?" : ""
    Warning.new(
      kind: :unknown_key,
      message: "`#{key}` is not a recognized configuration key; it has no effect.#{hint}",
      fields: { "key" => key, "suggestion" => suggestion }
    )
  end
end

.warnings(configuration, project_root: Dir.pwd) ⇒ Array<Warning>

Parameters:

  • configuration (Rigor::Configuration)
  • project_root (String) (defaults to: Dir.pwd)

    the directory the run's relative bundler / collection paths resolve against (the CLI's CWD), used only by the explicit-path checks.

Returns:



43
44
45
46
47
48
49
# File 'lib/rigor/config_audit.rb', line 43

def self.warnings(configuration, project_root: Dir.pwd)
  unknown_key_warnings(configuration) +
    signature_path_warnings(configuration) +
    library_warnings(configuration) +
    rule_token_warnings(configuration) +
    explicit_path_warnings(configuration, project_root)
end