Class: RuboCop::ConfigLoaderResolver Private
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | lib/rubocop/config_loader_resolver.rb |
Overview
A help class for ConfigLoader that handles configuration resolution.
Instance Method Summary
-
#apply_preview_defaults(default_configuration, preview)
Internal use only
A cop’s entry in the default configuration may carry a
Previewsection with the defaults it is expected to adopt in the next major release. -
#fix_include_paths(base_config_path, hash, path, key, value)
Internal use only
When one .rubocop.yml file inherits from another .rubocop.yml file, the Include paths in the base configuration are relative to the directory where the base configuration file is.
-
#merge(base_hash, derived_hash, **opts)
Internal use only
Return a recursive merge of two hashes.
-
#merge_with_default(config, config_file, unset_nil:, resolve_preview: true)
Internal use only
Merges the given configuration with the default one.
-
#override_department_setting_for_cops(base_hash, derived_hash)
Internal use only
An
Enabled: truesetting in user configuration for a cop overrides anEnabled: falsesetting for its department. -
#override_enabled_for_disabled_departments(base_hash, derived_hash)
Internal use only
If a cop was previously explicitly enabled, but then superseded by the department being disabled, disable it.
-
#resolve_inheritance(path, hash, file, debug)
Internal use only
Metrics/AbcSize.
- #resolve_inheritance_from_gems(hash) Internal use only
- #resolve_plugins(rubocop_config, plugins) Internal use only
- #resolve_requires(path, hash) Internal use only
- #base_configs(path, inherit_from, file) private Internal use only
- #defaults_with_preview(config, resolve_preview) private Internal use only
- #determine_inherit_mode(hash, key, preview) private Internal use only
- #disabled?(hash, department) ⇒ Boolean private Internal use only
- #duplicate_setting?(base_hash, derived_hash, key, inherited_file) ⇒ Boolean private Internal use only
- #duplicate_setting_warning(opts, key) private Internal use only
- #gem_config_path(gem_name, relative_config_path) private Internal use only
- #handle_disabled_by_default(config, new_default_configuration, base_defaults) private Internal use only
- #inherit_mode_for_default(config, preview) private Internal use only
- #inherited_file(path, inherit_from, file) private Internal use only
-
#inherited_preview?(hash, base_configs) ⇒ Boolean
private
Internal use only
The
AllCops: Previewin effect once inheritance is resolved: the file itself wins, then the files it inherits from, last one first. - #merge_hashes?(base_hash, derived_hash, key) ⇒ Boolean private Internal use only
-
#preview?(config) ⇒ Boolean
private
Internal use only
confighas already been through #handle_disabled_by_default by this point, which returns a plain hash, so readAllCopsdirectly rather than going through Config#for_all_cops. - #remote_config?(file) ⇒ Boolean private Internal use only
- #resolve_default_overrides(config) private Internal use only
- #should_merge?(mode, key) ⇒ Boolean private Internal use only
- #should_override?(mode, key) ⇒ Boolean private Internal use only
- #should_union?(derived_hash, base_hash, root_mode, key) ⇒ Boolean private Internal use only
- #transform(config, &block) private Internal use only
- #warn_on_duplicate_setting(base_hash, derived_hash, key, **opts) private Internal use only
-
#with_preview_exclude_merge(mode, preview)
private
Internal use only
Under
Preview,Excludeis merged rather than replaced, so that excluding one directory does not silently drop the excludes it would have inherited - from the default configuration or from a file named ininherit_from.
Instance Method Details
#apply_preview_defaults(default_configuration, preview)
A cop’s entry in the default configuration may carry a Preview section
with the defaults it is expected to adopt in the next major release. Under
Preview those replace the current defaults. The section is dropped either
way, so the resolved configuration only ever shows what is in effect.
# File 'lib/rubocop/config_loader_resolver.rb', line 182
def apply_preview_defaults(default_configuration, preview) transform(default_configuration) do |params| next params unless params['Preview'].is_a?(Hash) params = params.dup preview_params = params.delete('Preview') preview ? params.merge(preview_params) : params end end
#base_configs(path, inherit_from, file) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 309
def base_configs(path, inherit_from, file) inherit_froms = Array(inherit_from).compact.flat_map do |f| PathUtil.glob?(f) ? Dir.glob(f) : f end configs = inherit_froms.map do |f| ConfigLoader.load_file(inherited_file(path, f, file)) end configs.compact end
#defaults_with_preview(config, resolve_preview) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 194
def defaults_with_preview(config, resolve_preview) defaults = ConfigLoader.default_configuration return [defaults, false] unless resolve_preview preview = preview?(config) [apply_preview_defaults(defaults, preview), preview] end
#determine_inherit_mode(hash, key, preview) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 277
def determine_inherit_mode(hash, key, preview) cop_cfg = hash[key] local_inherit = cop_cfg['inherit_mode'] if cop_cfg.is_a?(Hash) with_preview_exclude_merge(local_inherit || hash['inherit_mode'] || {}, preview) end
#disabled?(hash, department) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 235
def disabled?(hash, department) hash[department].is_a?(Hash) && hash[department]['Enabled'] == false end
#duplicate_setting?(base_hash, derived_hash, key, inherited_file) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 239
def duplicate_setting?(base_hash, derived_hash, key, inherited_file) return false if inherited_file.nil? # Not inheritance resolving merge return false if inherited_file.start_with?('..') # Legitimate override return false if base_hash[key] == derived_hash[key] # Same value return false if PathUtil.remote_file?(inherited_file) # Can't change Gem.path.none? { |dir| inherited_file.start_with?(dir) } # Can change? end
#duplicate_setting_warning(opts, key) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 260
def duplicate_setting_warning(opts, key) "#{PathUtil.smart_path(opts[:file])}: " \ "#{opts[:cop_name]}:#{key} overrides " \ "the same parameter in #{opts[:inherited_file]}" end
#fix_include_paths(base_config_path, hash, path, key, value)
When one .rubocop.yml file inherits from another .rubocop.yml file, the Include paths in the base configuration are relative to the directory where the base configuration file is. For the derived configuration, we need to make those paths relative to where the derived configuration file is.
# File 'lib/rubocop/config_loader_resolver.rb', line 66
def fix_include_paths(base_config_path, hash, path, key, value) return unless File.basename(base_config_path).start_with?('.rubocop') base_dir = File.dirname(base_config_path) derived_dir = File.dirname(path) hash[key]['Include'] = value['Include'].map do |include_path| PathUtil.relative_path(File.join(base_dir, include_path), derived_dir) end end
#gem_config_path(gem_name, relative_config_path) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 368
def gem_config_path(gem_name, relative_config_path) if defined?(Bundler) begin gem = Bundler.load.specs[gem_name].first gem_path = gem.full_gem_path if gem rescue StandardError # The Gemfile has a problem, which could be one of: # - No Gemfile found. Bundler may be loaded manually # - The Gemfile exists but contains an uninstalled git source # - The Gemfile exists but cannot be loaded for some other reason end end gem_path ||= Gem::Specification.find_by_name(gem_name).gem_dir File.join(gem_path, relative_config_path) rescue Gem::LoadError => e raise Gem::LoadError, "Unable to find gem #{gem_name}; is the gem installed? #{e}" end
#handle_disabled_by_default(config, new_default_configuration, base_defaults) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 346
def handle_disabled_by_default(config, new_default_configuration, base_defaults) department_config = config.to_hash.reject { |cop| cop.include?('/') } department_config.each do |dept, dept_params| next unless dept_params['Enabled'] new_default_configuration.each do |cop, params| next unless cop.start_with?("#{dept}/") # Retain original default configuration for cops in the department. params['Enabled'] = base_defaults[cop]['Enabled'] end end transform(config) do |params| { 'Enabled' => true }.merge(params) # Set true if not set. end end
#inherit_mode_for_default(config, preview) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 202
def inherit_mode_for_default(config, preview) with_preview_exclude_merge(config['inherit_mode'] || {}, preview) end
#inherited_file(path, inherit_from, file) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 321
def inherited_file(path, inherit_from, file) if PathUtil.remote_file?(inherit_from) # A remote configuration, e.g. `inherit_from: http://example.com/rubocop.yml`. RemoteConfig.new(inherit_from, ConfigLoader.cache_root) elsif Pathname.new(inherit_from).absolute? # An absolute path to a config, e.g. `inherit_from: /Users/me/rubocop.yml`. # The path may come from `inherit_gem` option, where a gem name is expanded # to an absolute path to that gem. print 'Inheriting ' if ConfigLoader.debug? inherit_from elsif file.is_a?(RemoteConfig) # A path relative to a URL, e.g. `inherit_from: configs/default.yml` # in a config included with `inherit_from: http://example.com/rubocop.yml` file.inherit_from_remote(inherit_from) else # A local relative path, e.g. `inherit_from: default.yml` print 'Inheriting ' if ConfigLoader.debug? File.(inherit_from, File.dirname(path)) end end
#inherited_preview?(hash, base_configs) ⇒ Boolean (private)
The AllCops: Preview in effect once inheritance is resolved: the file
itself wins, then the files it inherits from, last one first. It has to be
known before merging, since the keys are merged in whatever order they
appear and AllCops may well come after the cops whose Exclude it affects.
# File 'lib/rubocop/config_loader_resolver.rb', line 270
def inherited_preview?(hash, base_configs) all_cops = [hash, *base_configs.reverse].map { |config| config['AllCops'] }.find do |params| params.is_a?(Hash) && params.key?('Preview') end preview?('AllCops' => all_cops) end
#merge(base_hash, derived_hash, **opts)
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.
rubocop:disable-next Metrics/AbcSize
# File 'lib/rubocop/config_loader_resolver.rb', line 128
def merge(base_hash, derived_hash, **opts) result = base_hash.merge(derived_hash) keys_appearing_in_both = base_hash.keys & derived_hash.keys keys_appearing_in_both.each do |key| if opts[:unset_nil] && derived_hash[key].nil? result.delete(key) elsif merge_hashes?(base_hash, derived_hash, key) result[key] = merge(base_hash[key], derived_hash[key], **opts) elsif should_union?(derived_hash, base_hash, opts[:inherit_mode], key) result[key] = Array(base_hash[key]) | Array(derived_hash[key]) elsif opts[:debug] warn_on_duplicate_setting(base_hash, derived_hash, key, **opts) end end result end
#merge_hashes?(base_hash, derived_hash, key) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 305
def merge_hashes?(base_hash, derived_hash, key) base_hash[key].is_a?(Hash) && derived_hash[key].is_a?(Hash) end
#merge_with_default(config, config_file, unset_nil:, resolve_preview: true)
Merges the given configuration with the default one. If
AllCops:DisabledByDefault is true, it changes the Enabled params so that
only cops from user configuration are enabled. If
AllCops:EnabledByDefault is true, it changes the Enabled params so that
only cops explicitly disabled in user configuration are disabled.
When the --disable-all-cops or --enable-all-cops CLI option is given,
it takes precedence over the configuration values.
With resolve_preview: false the Preview sections of the default
configuration are left in place and preview is treated as off. That is
what extending the default configuration itself needs: preview gets
applied later, when a project configuration is resolved against it.
# File 'lib/rubocop/config_loader_resolver.rb', line 103
def merge_with_default(config, config_file, unset_nil:, resolve_preview: true) base_defaults, preview = defaults_with_preview(config, resolve_preview) default_configuration = base_defaults disabled_by_default, enabled_by_default = resolve_default_overrides(config) if disabled_by_default || enabled_by_default default_configuration = transform(base_defaults) do |params| params.merge('Enabled' => !disabled_by_default) end end if disabled_by_default config = handle_disabled_by_default(config, default_configuration, base_defaults) end override_enabled_for_disabled_departments(default_configuration, config) opts = { inherit_mode: inherit_mode_for_default(config, preview), unset_nil: unset_nil } Config.new(merge(default_configuration, config, **opts), config_file) end
#override_department_setting_for_cops(base_hash, derived_hash)
An Enabled: true setting in user configuration for a cop overrides an
Enabled: false setting for its department.
# File 'lib/rubocop/config_loader_resolver.rb', line 147
def override_department_setting_for_cops(base_hash, derived_hash) derived_hash.each_key do |key| next unless key =~ %r{(.*)/.*} department = Regexp.last_match(1) next unless disabled?(derived_hash, department) || disabled?(base_hash, department) # The `override_department` setting for the `Enabled` parameter is an # internal setting that's not documented in the manual. It will cause a # cop to be enabled later, when logic surrounding enabled/disabled it # run, even though its department is disabled. derived_hash[key]['Enabled'] = 'override_department' if derived_hash[key]['Enabled'] end end
#override_enabled_for_disabled_departments(base_hash, derived_hash)
If a cop was previously explicitly enabled, but then superseded by the department being disabled, disable it.
# File 'lib/rubocop/config_loader_resolver.rb', line 164
def override_enabled_for_disabled_departments(base_hash, derived_hash) cops_to_disable = derived_hash.each_key.with_object([]) do |key, cops| next unless disabled?(derived_hash, key) cops.concat(base_hash.keys.grep(Regexp.new("^#{key}/"))) end cops_to_disable.each do |cop_name| next unless base_hash.dig(cop_name, 'Enabled') == true derived_hash.replace(merge({ cop_name => { 'Enabled' => false } }, derived_hash)) end end
#preview?(config) ⇒ Boolean (private)
config has already been through #handle_disabled_by_default by this
point, which returns a plain hash, so read AllCops directly rather than
going through Config#for_all_cops.
# File 'lib/rubocop/config_loader_resolver.rb', line 221
def preview?(config) return ConfigLoader.preview unless ConfigLoader.preview.nil? (config['AllCops'] || {})['Preview'] == true end
#remote_config?(file) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 342
def remote_config?(file) file.is_a?(RemoteConfig) end
#resolve_default_overrides(config) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 227
def resolve_default_overrides(config) if ConfigLoader.disabled_by_default || ConfigLoader.enabled_by_default [ConfigLoader.disabled_by_default, ConfigLoader.enabled_by_default] else [config.for_all_cops['DisabledByDefault'], config.for_all_cops['EnabledByDefault']] end end
#resolve_inheritance(path, hash, file, debug)
Metrics/AbcSize
# File 'lib/rubocop/config_loader_resolver.rb', line 38
def resolve_inheritance(path, hash, file, debug) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize inherited_files = Array(hash['inherit_from']) base_configs = base_configs(path, inherited_files, file) preview = inherited_preview?(hash, base_configs) base_configs.each_with_index.reverse_each do |base_config, index| override_department_setting_for_cops(base_config, hash) override_enabled_for_disabled_departments(base_config, hash) base_config.each do |k, v| next unless v.is_a?(Hash) only_base_has_include = v.key?('Include') && !hash.dig(k, 'Include') if hash.key?(k) v = merge(v, hash[k], cop_name: k, file: file, debug: debug, inherited_file: inherited_files[index], inherit_mode: determine_inherit_mode(hash, k, preview)) end hash[k] = v fix_include_paths(base_config.loaded_path, hash, path, k, v) if only_base_has_include end end end
#resolve_inheritance_from_gems(hash)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 76
def resolve_inheritance_from_gems(hash) gems = hash.delete('inherit_gem') (gems || {}).each_pair do |gem_name, config_path| if gem_name == 'rubocop' raise ArgumentError, "can't inherit configuration from the rubocop gem" end hash['inherit_from'] = Array(hash['inherit_from']) Array(config_path).reverse_each do |path| # Put gem configuration first so local configuration overrides it. hash['inherit_from'].unshift gem_config_path(gem_name, path) end end end
#resolve_plugins(rubocop_config, plugins)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 11
def resolve_plugins(rubocop_config, plugins) plugins = Array(plugins) - ConfigLoader.loaded_plugins.map { |plugin| plugin.about.name } return if plugins.empty? Plugin.integrate_plugins(rubocop_config, plugins) end
#resolve_requires(path, hash)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 18
def resolve_requires(path, hash) config_dir = File.dirname(path) hash.delete('require').tap do |loaded_features| Array(loaded_features).each do |feature| if Plugin.plugin_capable?(feature) # NOTE: Compatibility for before plugins style. warn Rainbow(<<~MESSAGE).yellow #{feature} extension supports plugin, specify `plugins: #{feature}` instead of `require: #{feature}` in #{path}. For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html. MESSAGE rubocop_config = Config.create(hash, path, check: false) resolve_plugins(rubocop_config, feature) else FeatureLoader.load(config_directory_path: config_dir, feature: feature) end end end end
#should_merge?(mode, key) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 297
def should_merge?(mode, key) mode && mode['merge']&.include?(key) end
#should_override?(mode, key) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 301
def should_override?(mode, key) mode && mode['override']&.include?(key) end
#should_union?(derived_hash, base_hash, root_mode, key) ⇒ Boolean (private)
# File 'lib/rubocop/config_loader_resolver.rb', line 283
def should_union?(derived_hash, base_hash, root_mode, key) return false unless base_hash[key].is_a?(Array) || derived_hash[key].is_a?(Array) derived_mode = derived_hash['inherit_mode'] return false if should_override?(derived_mode, key) return true if should_merge?(derived_mode, key) base_mode = base_hash['inherit_mode'] return false if should_override?(base_mode, key) return true if should_merge?(base_mode, key) should_merge?(root_mode, key) end
#transform(config, &block) (private)
[ GitHub ]#warn_on_duplicate_setting(base_hash, derived_hash, key, **opts) (private)
[ GitHub ]# File 'lib/rubocop/config_loader_resolver.rb', line 248
def warn_on_duplicate_setting(base_hash, derived_hash, key, **opts) # If the file being considered is remote, don't bother checking for duplicates return if remote_config?(opts[:file]) return unless duplicate_setting?(base_hash, derived_hash, key, opts[:inherited_file]) inherit_mode = opts[:inherit_mode]['merge'] || opts[:inherit_mode]['override'] return if base_hash[key].is_a?(Array) && inherit_mode&.include?(key) puts duplicate_setting_warning(opts, key) end
#with_preview_exclude_merge(mode, preview) (private)
Under Preview, Exclude is merged rather than replaced, so that excluding
one directory does not silently drop the excludes it would have inherited -
from the default configuration or from a file named in inherit_from. An
explicit inherit_mode still wins, in either direction.
# File 'lib/rubocop/config_loader_resolver.rb', line 210
def with_preview_exclude_merge(mode, preview) return mode unless preview return mode if Array(mode['override']).include?('Exclude') return mode if Array(mode['merge']).include?('Exclude') mode.merge('merge' => Array(mode['merge']) + ['Exclude']) end