class RuboCop::ConfigLoader

This class represents the configuration of the RuboCop application and all its cops. A Config is associated with a YAML configuration file from which it was read. Several different Configs can be used during a run of the rubocop program, if files in several directories are inspected.

Constants

DEFAULT_FILE
DOTFILE
PENDING_BANNER
RUBOCOP_HOME

Attributes

debug[RW]
debug?[RW]
default_configuration[W]
disable_pending_cops[RW]
enable_pending_cops[RW]
ignore_parent_exclusion[RW]
ignore_parent_exclusion?[RW]
ignore_unrecognized_cops[RW]
loaded_features[R]

Public Class Methods

add_excludes_from_files(config, config_file) click to toggle source
# File lib/rubocop/config_loader.rb, line 137
def add_excludes_from_files(config, config_file)
  exclusion_file = find_last_file_upwards(DOTFILE, config_file, ConfigFinder.project_root)

  return unless exclusion_file
  return if PathUtil.relative_path(exclusion_file) == PathUtil.relative_path(config_file)

  print 'AllCops/Exclude ' if debug?
  config.add_excludes_from_higher_level(load_file(exclusion_file))
end
add_loaded_features(loaded_features) click to toggle source

@api private Used to add features that were required inside a config or from the CLI using ‘–require`.

# File lib/rubocop/config_loader.rb, line 198
def add_loaded_features(loaded_features)
  @loaded_features.merge(Array(loaded_features))
end
add_missing_namespaces(path, hash) click to toggle source
# File lib/rubocop/config_loader.rb, line 80
def add_missing_namespaces(path, hash)
  # Using `hash.each_key` will cause the
  # `can't add a new key into hash during iteration` error
  hash_keys = hash.keys
  hash_keys.each do |key|
    q = Cop::Registry.qualified_cop_name(key, path)
    next if q == key

    hash[q] = hash.delete(key)
  end
end
clear_options() click to toggle source
# File lib/rubocop/config_loader.rb, line 41
def clear_options
  @debug = nil
  @loaded_features = Set.new
  FileFinder.root_level = nil
end
configuration_file_for(target_dir) click to toggle source

Returns the path of .rubocop.yml searching upwards in the directory structure starting at the given directory where the inspected file is. If no .rubocop.yml is found there, the user’s home directory is checked. If there’s no .rubocop.yml there either, the path to the default file is returned.

# File lib/rubocop/config_loader.rb, line 104
def configuration_file_for(target_dir)
  ConfigFinder.find_config_path(target_dir)
end
configuration_from_file(config_file, check: true) click to toggle source
# File lib/rubocop/config_loader.rb, line 108
def configuration_from_file(config_file, check: true)
  return default_configuration if config_file == DEFAULT_FILE

  config = load_file(config_file, check: check)
  config.validate_after_resolution if check

  if ignore_parent_exclusion?
    print 'Ignoring AllCops/Exclude from parent folders' if debug?
  else
    add_excludes_from_files(config, config_file)
  end

  merge_with_default(config, config_file).tap do |merged_config|
    unless possible_new_cops?(merged_config)
      pending_cops = pending_cops_only_qualified(merged_config.pending_cops)
      warn_on_pending_cops(pending_cops) unless pending_cops.empty?
    end
  end
end
default_configuration() click to toggle source
# File lib/rubocop/config_loader.rb, line 147
def default_configuration
  @default_configuration ||= begin
    print 'Default ' if debug?
    load_file(DEFAULT_FILE)
  end
end
inject_defaults!(project_root) click to toggle source

@api private

# File lib/rubocop/config_loader.rb, line 155
def inject_defaults!(project_root)
  path = File.join(project_root, 'config', 'default.yml')
  config = load_file(path)
  new_config = ConfigLoader.merge_with_default(config, path)
  puts "configuration from #{path}" if debug?
  @default_configuration = new_config
end
load_file(file, check: true) click to toggle source
# File lib/rubocop/config_loader.rb, line 47
def load_file(file, check: true)
  path = file_path(file)

  hash = load_yaml_configuration(path)

  loaded_features = resolver.resolve_requires(path, hash)
  add_loaded_features(loaded_features)

  resolver.override_department_setting_for_cops({}, hash)
  resolver.resolve_inheritance_from_gems(hash)
  resolver.resolve_inheritance(path, hash, file, debug?)
  hash.delete('inherit_from')

  # Adding missing namespaces only after resolving requires & inheritance,
  # since both can introduce new cops that need to be considered here.
  add_missing_namespaces(path, hash)

  Config.create(hash, path, check: check)
end
load_yaml_configuration(absolute_path) click to toggle source
# File lib/rubocop/config_loader.rb, line 67
def load_yaml_configuration(absolute_path)
  file_contents = read_file(absolute_path)
  yaml_code = Dir.chdir(File.dirname(absolute_path)) { ERB.new(file_contents).result }
  yaml_tree = check_duplication(yaml_code, absolute_path)
  hash = yaml_tree_to_hash(yaml_tree) || {}

  puts "configuration from #{absolute_path}" if debug?

  raise(TypeError, "Malformed configuration in #{absolute_path}") unless hash.is_a?(Hash)

  hash
end
merge(base_hash, derived_hash) click to toggle source

Return a recursive merge of two hashes. That is, a normal hash merge, with the addition that any value that is a hash, and occurs in both arguments, will also be merged. And so on.

# File lib/rubocop/config_loader.rb, line 95
def merge(base_hash, derived_hash)
  resolver.merge(base_hash, derived_hash)
end
merge_with_default(config, config_file, unset_nil: true) click to toggle source

Merges the given configuration with the default one.

# File lib/rubocop/config_loader.rb, line 191
def merge_with_default(config, config_file, unset_nil: true)
  resolver.merge_with_default(config, config_file, unset_nil: unset_nil)
end
pending_cops_only_qualified(pending_cops) click to toggle source
# File lib/rubocop/config_loader.rb, line 128
def pending_cops_only_qualified(pending_cops)
  pending_cops.select { |cop| Cop::Registry.qualified_cop?(cop.name) }
end
possible_new_cops?(config) click to toggle source
# File lib/rubocop/config_loader.rb, line 132
def possible_new_cops?(config)
  disable_pending_cops || enable_pending_cops ||
    config.disabled_new_cops? || config.enabled_new_cops?
end
project_root() click to toggle source

Returns the path RuboCop inferred as the root of the project. No file searches will go past this directory. @deprecated Use ‘RuboCop::ConfigFinder.project_root` instead.

# File lib/rubocop/config_loader.rb, line 166
      def project_root
        warn Rainbow(<<~WARNING).yellow, uplevel: 1
          `RuboCop::ConfigLoader.project_root` is deprecated and will be removed in RuboCop 2.0. \
          Use `RuboCop::ConfigFinder.project_root` instead.
        WARNING

        ConfigFinder.project_root
      end
warn_on_pending_cops(pending_cops) click to toggle source
# File lib/rubocop/config_loader.rb, line 175
def warn_on_pending_cops(pending_cops)
  warn Rainbow(PENDING_BANNER).yellow

  pending_cops.each { |cop| warn_pending_cop cop }

  warn Rainbow('For more information: https://docs.rubocop.org/rubocop/versioning.html').yellow
end
warn_pending_cop(cop) click to toggle source
# File lib/rubocop/config_loader.rb, line 183
def warn_pending_cop(cop)
  version = cop.metadata['VersionAdded'] || 'N/A'

  warn Rainbow("#{cop.name}: # new in #{version}").yellow
  warn Rainbow('  Enabled: true').yellow
end

Private Class Methods

check_duplication(yaml_code, absolute_path) click to toggle source
# File lib/rubocop/config_loader.rb, line 212
def check_duplication(yaml_code, absolute_path)
  smart_path = PathUtil.smart_path(absolute_path)
  YAMLDuplicationChecker.check(yaml_code, absolute_path) do |key1, key2|
    value = key1.value
    # .start_line is only available since ruby 2.5 / psych 3.0
    message = if key1.respond_to? :start_line
                line1 = key1.start_line + 1
                line2 = key2.start_line + 1
                "#{smart_path}:#{line1}: " \
                  "`#{value}` is concealed by line #{line2}"
              else
                "#{smart_path}: `#{value}` is concealed by duplicate"
              end
    warn Rainbow(message).yellow
  end
end
file_path(file) click to toggle source
# File lib/rubocop/config_loader.rb, line 204
def file_path(file)
  File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file)
end
read_file(absolute_path) click to toggle source

Read the specified file, or exit with a friendly, concise message on stderr. Care is taken to use the standard OS exit code for a “file not found” error.

# File lib/rubocop/config_loader.rb, line 232
def read_file(absolute_path)
  File.read(absolute_path, encoding: Encoding::UTF_8)
rescue Errno::ENOENT
  raise ConfigNotFoundError, "Configuration file not found: #{absolute_path}"
end
resolver() click to toggle source
# File lib/rubocop/config_loader.rb, line 208
def resolver
  @resolver ||= ConfigLoaderResolver.new
end
yaml_tree_to_hash(yaml_tree) click to toggle source
# File lib/rubocop/config_loader.rb, line 238
def yaml_tree_to_hash(yaml_tree)
  yaml_tree_to_hash!(yaml_tree)
rescue ::StandardError
  if defined?(::SafeYAML)
    raise 'SafeYAML is unmaintained, no longer needed and should be removed'
  end

  raise
end
yaml_tree_to_hash!(yaml_tree) click to toggle source
# File lib/rubocop/config_loader.rb, line 248
def yaml_tree_to_hash!(yaml_tree)
  return nil unless yaml_tree

  # Optimization: Because we checked for duplicate keys, we already have the
  # yaml tree and don't need to parse it again.
  # Also see https://github.com/ruby/psych/blob/v5.1.2/lib/psych.rb#L322-L336
  class_loader = YAML::ClassLoader::Restricted.new(%w[Regexp Symbol], [])
  scanner = YAML::ScalarScanner.new(class_loader)
  visitor = YAML::Visitors::ToRuby.new(scanner, class_loader)
  visitor.accept(yaml_tree)
end