class RuboCop::ConfigLoader
directories are inspected.
during a run of the rubocop program, if files in several
file from which it was read. Several different Configs can be used
and all its cops. A Config is associated with a YAML configuration
This class represents the configuration of the RuboCop application
def add_excludes_from_files(config, config_file)
def add_excludes_from_files(config, config_file) found_files = find_files_upwards(DOTFILE, config_file) + [find_user_dotfile, find_user_xdg_config].compact return if found_files.empty? return if PathUtil.relative_path(found_files.last) == PathUtil.relative_path(config_file) print 'AllCops/Exclude ' if debug? config.add_excludes_from_higher_level(load_file(found_files.last)) end
def add_inheritance_from_auto_generated_file
def add_inheritance_from_auto_generated_file file_string = " #{AUTO_GENERATED_FILE}" config_file = options_config || DOTFILE if File.exist?(config_file) files = Array(load_yaml_configuration(config_file)['inherit_from']) return if files.include?(AUTO_GENERATED_FILE) files.unshift(AUTO_GENERATED_FILE) file_string = "\n - " + files.join("\n - ") if files.size > 1 rubocop_yml_contents = existing_configuration(config_file) end write_config_file(config_file, file_string, rubocop_yml_contents) puts "Added inheritance from `#{AUTO_GENERATED_FILE}` in `#{DOTFILE}`." end
def add_missing_namespaces(path, hash)
def add_missing_namespaces(path, hash) hash.keys.each do |key| q = Cop::Cop.qualified_cop_name(key, path) next if q == key hash[q] = hash.delete(key) end end
def check_cop_config_value(hash, parent = nil)
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 %w[Enabled Safe SafeAutoCorrect AutoCorrect].include?(key) && value.is_a?(String) abort( "Property #{Rainbow(key).yellow} of cop #{Rainbow(parent).yellow}" \ " is supposed to be a boolean and #{Rainbow(value).yellow} is not." ) end end
def check_duplication(yaml_code, absolute_path)
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
def clear_options
def clear_options @debug = @auto_gen_config = @options_config = nil FileFinder.root_level = nil end
def configuration_file_for(target_dir)
user's home directory is checked. If there's no .rubocop.yml
inspected file is. If no .rubocop.yml is found there, the
directory structure starting at the given directory where the
Returns the path of .rubocop.yml searching upwards in the
def configuration_file_for(target_dir) find_project_dotfile(target_dir) || find_user_dotfile || find_user_xdg_config || DEFAULT_FILE end
def configuration_from_file(config_file)
def configuration_from_file(config_file) config = load_file(config_file) return config if config_file == DEFAULT_FILE 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) end
def default_configuration
def default_configuration @default_configuration ||= begin print 'Default ' if debug? load_file(DEFAULT_FILE) end end
def existing_configuration(config_file)
def existing_configuration(config_file) IO.read(config_file, encoding: Encoding::UTF_8) .sub(/^inherit_from: *[^\n]+/, '') .sub(/^inherit_from: *(\n *- *[^\n]+)+/, '') end
def expand_path(path)
def expand_path(path) File.expand_path(path) rescue ArgumentError # Could happen because HOME or ID could not be determined. Fall back on # using the path literally in that case. path end
def find_project_dotfile(target_dir)
def find_project_dotfile(target_dir) find_file_upwards(DOTFILE, target_dir) end
def find_user_dotfile
def find_user_dotfile return unless ENV.key?('HOME') file = File.join(Dir.home, DOTFILE) return file if File.exist?(file) end
def find_user_xdg_config
def find_user_xdg_config xdg_config_home = expand_path(ENV.fetch('XDG_CONFIG_HOME', '~/.config')) xdg_config = File.join(xdg_config_home, 'rubocop', XDG_CONFIG) return xdg_config if File.exist?(xdg_config) end
def load_file(file)
def load_file(file) path = File.absolute_path(file.is_a?(RemoteConfig) ? file.file : file) hash = load_yaml_configuration(path) # Resolve requires first in case they define additional cops resolver.resolve_requires(path, hash) add_missing_namespaces(path, hash) resolver.resolve_inheritance_from_gems(hash) resolver.resolve_inheritance(path, hash, file, debug?) hash.delete('inherit_from') Config.create(hash, path) end
def load_yaml_configuration(absolute_path)
def load_yaml_configuration(absolute_path) yaml_code = read_file(absolute_path) check_duplication(yaml_code, absolute_path) hash = yaml_safe_load(yaml_code, absolute_path) || {} puts "configuration from #{absolute_path}" if debug? unless hash.is_a?(Hash) raise(TypeError, "Malformed configuration in #{absolute_path}") end check_cop_config_value(hash) hash end
def merge(base_hash, derived_hash)
with the addition that any value that is a hash, and occurs in both
Return a recursive merge of two hashes. That is, a normal hash merge,
def merge(base_hash, derived_hash) resolver.merge(base_hash, derived_hash) end
def merge_with_default(config, config_file, unset_nil: true)
so that only cops explicitly disabled in user configuration are
If AllCops::EnabledByDefault is true, it changes the Enabled params
that only cops from user configuration are enabled.
AllCops:DisabledByDefault is true, it changes the Enabled params so
Merges the given configuration with the default one. If
def merge_with_default(config, config_file, unset_nil: true) resolver.merge_with_default(config, config_file, unset_nil: unset_nil) end
def read_file(absolute_path)
stderr. Care is taken to use the standard OS exit code for a "file not
Read the specified file, or exit with a friendly, concise message on
def read_file(absolute_path) IO.read(absolute_path, encoding: Encoding::UTF_8) rescue Errno::ENOENT raise ConfigNotFoundError, "Configuration file not found: #{absolute_path}" end
def resolver
def resolver @resolver ||= ConfigLoaderResolver.new end
def write_config_file(file_name, file_string, rubocop_yml_contents)
def write_config_file(file_name, file_string, rubocop_yml_contents) File.open(file_name, 'w') do |f| f.write "inherit_from:#{file_string}\n" f.write "\n#{rubocop_yml_contents}" if rubocop_yml_contents =~ /\S/ end end
def yaml_safe_load(yaml_code, filename)
def yaml_safe_load(yaml_code, filename) if defined?(SafeYAML) && SafeYAML.respond_to?(:load) SafeYAML.load(yaml_code, filename, whitelisted_tags: %w[!ruby/regexp]) # Ruby 2.6+ elsif Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0') YAML.safe_load( yaml_code, permitted_classes: [Regexp, Symbol], permitted_symbols: [], aliases: true, filename: filename ) else YAML.safe_load(yaml_code, [Regexp, Symbol], [], true, filename) end end