class RuboCop::ConfigValidator

Handles validation of configuration, for example cop names, parameter names, and Ruby versions.

Constants

COMMON_PARAMS

@api private

CONFIG_CHECK_AUTOCORRECTS
CONFIG_CHECK_DEPARTMENTS
CONFIG_CHECK_KEYS

@api private

INTERNAL_PARAMS

@api private

NEW_COPS_VALUES

@api private

Attributes

target_ruby[R]

Public Class Methods

new(config) click to toggle source
# File lib/rubocop/config_validator.rb, line 27
def initialize(config)
  @config = config
  @config_obsoletion = ConfigObsoletion.new(config)
  @target_ruby = TargetRuby.new(config)
end

Public Instance Methods

target_ruby_version() click to toggle source
# File lib/rubocop/config_validator.rb, line 63
def target_ruby_version
  target_ruby.version
end
validate() click to toggle source
# File lib/rubocop/config_validator.rb, line 33
def validate
  check_cop_config_value(@config)
  reject_conflicting_safe_settings

  # Don't validate RuboCop's own files further. Avoids infinite recursion.
  return if @config.internal?

  valid_cop_names, invalid_cop_names = @config.keys.partition do |key|
    ConfigLoader.default_configuration.key?(key)
  end

  check_obsoletions

  alert_about_unrecognized_cops(invalid_cop_names)
  validate_new_cops_parameter
  validate_parameter_names(valid_cop_names)
  validate_enforced_styles(valid_cop_names)
  validate_syntax_cop
  reject_mutually_exclusive_defaults
end
validate_after_resolution() click to toggle source

Validations that should only be run after all config resolving has taken place:

  • The target ruby version is only checked once the entire inheritance

chain has been loaded so that only the final value is validated, and any obsolete but overridden values are ignored.

# File lib/rubocop/config_validator.rb, line 59
def validate_after_resolution
  check_target_ruby
end
validate_section_presence(name) click to toggle source
# File lib/rubocop/config_validator.rb, line 67
def validate_section_presence(name)
  return unless @config.key?(name) && @config[name].nil?

  raise ValidationError, "empty section #{name} found in #{smart_loaded_path}"
end

Private Instance Methods

alert_about_unrecognized_cops(invalid_cop_names) click to toggle source
# File lib/rubocop/config_validator.rb, line 104
def alert_about_unrecognized_cops(invalid_cop_names)
  unknown_cops = list_unknown_cops(invalid_cop_names)

  return if unknown_cops.empty?

  if ConfigLoader.ignore_unrecognized_cops
    warn Rainbow('The following cops or departments are not ' \
                 'recognized and will be ignored:').yellow
    warn unknown_cops.join("\n")

    return
  end

  raise ValidationError, unknown_cops.join("\n")
end
check_cop_config_value(hash, parent = nil) click to toggle source

rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

# File lib/rubocop/config_validator.rb, line 254
def check_cop_config_value(hash, parent = nil)
  hash.each do |key, value|
    check_cop_config_value(value, key) if value.is_a?(Hash)

    next unless CONFIG_CHECK_KEYS.include?(key) && value.is_a?(String)

    if key == 'Enabled' && !CONFIG_CHECK_DEPARTMENTS.include?(value)
      supposed_values = 'a boolean'
    elsif key == 'AutoCorrect' && !CONFIG_CHECK_AUTOCORRECTS.include?(value)
      supposed_values = '`always`, `contextual`, `disabled`, or a boolean'
    else
      next
    end

    raise ValidationError, param_error_message(parent, key, value, supposed_values)
  end
end
check_obsoletions() click to toggle source
# File lib/rubocop/config_validator.rb, line 77
def check_obsoletions
  @config_obsoletion.reject_obsolete!
  return unless @config_obsoletion.warnings.any?

  warn Rainbow("Warning: #{@config_obsoletion.warnings.join("\n")}").yellow
end
check_target_ruby() click to toggle source
# File lib/rubocop/config_validator.rb, line 84
def check_target_ruby
  return if target_ruby.supported?

  source = target_ruby.source
  last_version = target_ruby.rubocop_version_with_support

  msg = if last_version
          "RuboCop found unsupported Ruby version #{target_ruby_version} " \
            "in #{source}. #{target_ruby_version}-compatible " \
            "analysis was dropped after version #{last_version}."
        else
          'RuboCop found unknown Ruby version ' \
            "#{target_ruby_version.inspect} in #{source}."
        end

  msg += "\nSupported versions: #{TargetRuby.supported_versions.join(', ')}"

  raise ValidationError, msg
end
each_invalid_parameter(cop_name) { |param, supported_params| ... } click to toggle source
# File lib/rubocop/config_validator.rb, line 195
def each_invalid_parameter(cop_name)
  default_config = ConfigLoader.default_configuration[cop_name]

  @config[cop_name].each_key do |param|
    next if COMMON_PARAMS.include?(param) || default_config.key?(param)

    supported_params = default_config.keys - INTERNAL_PARAMS

    yield param, supported_params
  end
end
list_unknown_cops(invalid_cop_names) click to toggle source
# File lib/rubocop/config_validator.rb, line 120
    def list_unknown_cops(invalid_cop_names)
      unknown_cops = []
      invalid_cop_names.each do |name|
        # There could be a custom cop with this name. If so, don't warn
        next if Cop::Registry.global.contains_cop_matching?([name])

        # Special case for inherit_mode, which is a directive that we keep in
        # the configuration (even though it's not a cop), because it's easier
        # to do so than to pass the value around to various methods.
        next if name == 'inherit_mode'

        message = <<~MESSAGE.rstrip
          unrecognized cop or department #{name} found in #{smart_loaded_path}
          #{suggestion(name)}
        MESSAGE

        unknown_cops << message
      end

      unknown_cops
    end
param_error_message(parent, key, value, supposed_values) click to toggle source

FIXME: Handling colors in exception messages like this is ugly.

# File lib/rubocop/config_validator.rb, line 274
def param_error_message(parent, key, value, supposed_values)
  "#{Rainbow('').reset}" \
    "Property #{Rainbow(key).yellow} of #{Rainbow(parent).yellow} cop " \
    "is supposed to be #{supposed_values} and #{Rainbow(value).yellow} is not."
end
reject_conflicting_safe_settings() click to toggle source
# File lib/rubocop/config_validator.rb, line 242
def reject_conflicting_safe_settings
  @config.each do |name, cop_config|
    next unless cop_config.is_a?(Hash)
    next unless cop_config['Safe'] == false && cop_config['SafeAutoCorrect'] == true

    msg = 'Unsafe cops cannot have a safe autocorrection ' \
          "(section #{name} in #{smart_loaded_path})"
    raise ValidationError, msg
  end
end
reject_mutually_exclusive_defaults() click to toggle source
# File lib/rubocop/config_validator.rb, line 233
def reject_mutually_exclusive_defaults
  disabled_by_default = for_all_cops['DisabledByDefault']
  enabled_by_default = for_all_cops['EnabledByDefault']
  return unless disabled_by_default && enabled_by_default

  msg = 'Cops cannot be both enabled by default and disabled by default'
  raise ValidationError, msg
end
suggestion(name) click to toggle source
# File lib/rubocop/config_validator.rb, line 142
def suggestion(name)
  registry = Cop::Registry.global
  departments = registry.departments.map(&:to_s)
  suggestions = NameSimilarity.find_similar_names(name, departments + registry.map(&:cop_name))
  if suggestions.any?
    "Did you mean `#{suggestions.join('`, `')}`?"
  else
    # Department names can contain slashes, e.g. Chef/Correctness, but there's no support for
    # the concept of higher level departments in RuboCop. It's a flat structure. So if the user
    # tries to configure a "top level department", we hint that it's the bottom level
    # departments that should be configured.
    suggestions = departments.select { |department| department.start_with?("#{name}/") }
    "#{name} is not a department. Use `#{suggestions.join('`, `')}`." if suggestions.any?
  end
end
validate_enforced_styles(valid_cop_names) click to toggle source
# File lib/rubocop/config_validator.rb, line 207
def validate_enforced_styles(valid_cop_names) # rubocop:todo Metrics/AbcSize
  valid_cop_names.each do |name|
    styles = @config[name].select { |key, _| key.start_with?('Enforced') }

    styles.each do |style_name, style|
      supported_key = RuboCop::Cop::Util.to_supported_styles(style_name)
      valid = ConfigLoader.default_configuration[name][supported_key]

      next unless valid
      next if valid.include?(style)
      next if validate_support_and_has_list(name, style, valid)

      msg = "invalid #{style_name} '#{style}' for #{name} found in " \
            "#{smart_loaded_path}\n" \
            "Valid choices are: #{valid.join(', ')}"
      raise ValidationError, msg
    end
  end
end
validate_new_cops_parameter() click to toggle source
# File lib/rubocop/config_validator.rb, line 169
def validate_new_cops_parameter
  new_cop_parameter = @config.for_all_cops['NewCops']
  return if new_cop_parameter.nil? || NEW_COPS_VALUES.include?(new_cop_parameter)

  message = "invalid #{new_cop_parameter} for `NewCops` found in" \
            "#{smart_loaded_path}\n" \
            "Valid choices are: #{NEW_COPS_VALUES.join(', ')}"

  raise ValidationError, message
end
validate_parameter_names(valid_cop_names) click to toggle source
# File lib/rubocop/config_validator.rb, line 180
    def validate_parameter_names(valid_cop_names)
      valid_cop_names.each do |name|
        validate_section_presence(name)
        each_invalid_parameter(name) do |param, supported_params|
          warn Rainbow(<<~MESSAGE).yellow
            Warning: #{name} does not support #{param} parameter.

            Supported parameters are:

              - #{supported_params.join("\n  - ")}
          MESSAGE
        end
      end
    end
validate_support_and_has_list(name, formats, valid) click to toggle source
# File lib/rubocop/config_validator.rb, line 227
def validate_support_and_has_list(name, formats, valid)
  ConfigLoader.default_configuration[name]['AllowMultipleStyles'] &&
    formats.is_a?(Array) &&
    formats.all? { |format| valid.include?(format) }
end
validate_syntax_cop() click to toggle source
# File lib/rubocop/config_validator.rb, line 158
def validate_syntax_cop
  syntax_config = @config['Lint/Syntax']
  default_config = ConfigLoader.default_configuration['Lint/Syntax']

  return unless syntax_config && default_config.merge(syntax_config) != default_config

  raise ValidationError,
        "configuration for Lint/Syntax cop found in #{smart_loaded_path}\n" \
        'It\'s not possible to disable this cop.'
end