Module: Pronto::RubyCritic::ConfigLoader

Defined in:
lib/pronto/rubycritic/config_loader.rb

Constant Summary collapse

RECOGNISED_SECTIONS =

Top-level keys that SmellFilter expects to be sub-mappings. A non-Hash value here (e.g. ‘reek: false`, `complexity: 10`) is a user typo; we drop it with a warning rather than letting it crash SmellFilter#dig.

%w[reek flay flog complexity churn].freeze

Class Method Summary collapse

Class Method Details

.first_non_blank(*values) ⇒ Object



79
80
81
# File 'lib/pronto/rubycritic/config_loader.rb', line 79

def first_non_blank(*values)
  values.find { |v| !v.nil? && !v.to_s.strip.empty? }
end

.load_pronto_configObject



53
54
55
56
57
58
# File 'lib/pronto/rubycritic/config_loader.rb', line 53

def load_pronto_config
  Pronto::ConfigFile.new.to_h
rescue StandardError => e
  warn("pronto-rubycritic: could not load .pronto.yml: #{e.class}: #{e.message}")
  {}
end

.load_runner_config(filename, cwd: Dir.pwd) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/pronto/rubycritic/config_loader.rb', line 16

def load_runner_config(filename, cwd: Dir.pwd)
  path = File.join(cwd, filename)
  return {} unless File.exist?(path)

  # aliases are deliberately NOT permitted: YAML anchors/aliases are not
  # used by this gem's config schema, and enabling them exposes a
  # billion-laughs expansion DoS on a config file that, in CI, can be
  # supplied by an untrusted pull request.
  parsed = YAML.safe_load_file(path, permitted_classes: [Symbol])
  return {} if parsed.nil?
  return validate_sections(parsed, filename) if parsed.is_a?(Hash)

  # YAML like ":\n:\n- [" parses to a Symbol / Array / scalar — not a
  # Hash. SmellFilter expects a Hash (calls #dig). Reject anything else.
  warn("pronto-rubycritic: #{filename} must be a YAML mapping (Hash); " \
       "got #{parsed.class}. Ignoring.")
  {}
rescue Psych::Exception => e
  # Covers SyntaxError, DisallowedClass, and AliasesNotEnabled (the last
  # one fires when a config uses a YAML alias, which we intentionally
  # disable above).
  warn("pronto-rubycritic: invalid YAML in #{filename}: #{e.class}: #{e.message}")
  {}
end

.pronto_severity(pronto_config) ⇒ Object

Reads severity_level from .pronto.yml’s ‘rubycritic:` section without assuming the section is a Hash — a non-Hash section (e.g. `rubycritic: x`) would otherwise raise TypeError from Hash#dig and be swallowed upstream.



74
75
76
77
# File 'lib/pronto/rubycritic/config_loader.rb', line 74

def pronto_severity(pronto_config)
  section = pronto_config['rubycritic'] if pronto_config.is_a?(Hash)
  section.is_a?(Hash) ? section['severity_level'] : nil
end

.resolve_severity(env_value:, pronto_config:, valid_levels:, default:) ⇒ Object



60
61
62
63
64
65
66
67
68
69
# File 'lib/pronto/rubycritic/config_loader.rb', line 60

def resolve_severity(env_value:, pronto_config:, valid_levels:, default:)
  raw = first_non_blank(env_value, pronto_severity(pronto_config))
  return default if raw.nil?

  sym = raw.to_s.strip.downcase.to_sym
  return sym if valid_levels.include?(sym)

  warn("pronto-rubycritic: invalid severity #{raw.inspect}, falling back to #{default}.")
  default
end

.validate_sections(config, filename) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/pronto/rubycritic/config_loader.rb', line 41

def validate_sections(config, filename)
  config.each_with_object({}) do |(key, value), acc|
    if RECOGNISED_SECTIONS.include?(key) && !value.is_a?(Hash)
      warn("pronto-rubycritic: #{filename}: section '#{key}' must be a mapping " \
           "(got #{value.class}); ignoring it.")
      next
    end

    acc[key] = value
  end
end