123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Plugin::ConfigurationIntegrator Private

Relationships & Source Files
Inherits: Object
Defined in: lib/rubocop/plugin/configuration_integrator.rb

Overview

A class for integrating plugin configurations into RuboCop. Handles configuration merging, validation, and compatibility for plugins.

Class Method Summary

Class Method Details

.combine_rubocop_configs(default_config, runner_context, plugins) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 44

def combine_rubocop_configs(default_config, runner_context, plugins)
  fake_out_rubocop_default_configuration(default_config) do |fake_config|
    all_cop_keys_configured_by_plugins = []

    plugins.reduce(fake_config) do |combined_config, plugin|
      RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, combined_config)

      print 'Plugin ' if ConfigLoader.debug

      plugin_config, plugin_config_path = load_plugin_rubocop_config(plugin, runner_context)

      plugin_config['AllCops'], all_cop_keys_configured_by_plugins = merge_all_cop_settings(
        combined_config['AllCops'], plugin_config['AllCops'],
        all_cop_keys_configured_by_plugins
      )

      plugin_config.make_excludes_absolute

      merge_plugin_config_with_default(plugin_config, plugin_config_path)
    end
  end
end

.create_context(rubocop_config) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 27

def create_context(rubocop_config)
  LintRoller::Context.new(
    runner: :rubocop,
    runner_version: Version.version,
    engine: :rubocop,
    engine_version: Version.version,
    target_ruby_version: rubocop_config.target_ruby_version
  )
end

.fake_out_rubocop_default_configuration(default_config) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 92

def fake_out_rubocop_default_configuration(default_config)
  orig_default_config = ConfigLoader.instance_variable_get(:@default_configuration)

  result = yield default_config

  ConfigLoader.instance_variable_set(:@default_configuration, orig_default_config)

  result
end

.integrate_plugins_into_rubocop_config(rubocop_config, plugins)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 13

def integrate_plugins_into_rubocop_config(rubocop_config, plugins)
  default_config = ConfigLoader.default_configuration
  runner_context = create_context(rubocop_config)

  validate_plugins!(plugins, runner_context)

  plugin_config = combine_rubocop_configs(default_config, runner_context, plugins).to_h

  merge_plugin_config_into_all_cops!(default_config, plugin_config)
  merge_plugin_config_into_default_config!(default_config, plugin_config)
end

.load_plugin_rubocop_config(plugin, runner_context) (private)

rubocop:disable-next Metrics/AbcSize

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 103

def load_plugin_rubocop_config(plugin, runner_context)
  rules = plugin.rules(runner_context)

  case rules.type
  when :path
    config = ConfigLoader.load_file(rules.value, check: false)
    warn_about_obsolete_config(plugin, config)
    [config, rules.value]
  when :object
    path = plugin.method(:rules).source_location[0]
    [Config.create(rules.value, path, check: true), path]
  when :error
    error_message = rules.value.respond_to?(:message) ? rules.value.message : rules.value

    raise "Plugin `#{plugin_name(plugin)}' failed to load with error: #{error_message}"
  end
end

.merge_all_cop_settings(existing_all_cops, new_all_cops, already_configured_keys) (private)

This is how we ensure "first-in wins": plugins can override AllCops settings that are set by RuboCop’s default configuration, but once a plugin sets an AllCop setting, they have exclusive first-in-wins rights to that setting.

The one exception to this are array fields, because we don’t want to overwrite the AllCops defaults but rather munge the arrays (existing | new) to allow plugins to add to the array, for example Include and Exclude paths and patterns.

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 149

def merge_all_cop_settings(existing_all_cops, new_all_cops, already_configured_keys)
  return [existing_all_cops, already_configured_keys] unless new_all_cops.is_a?(Hash)

  combined_all_cops = existing_all_cops.dup
  combined_configured_keys = already_configured_keys.dup

  new_all_cops.each do |key, value|
    if combined_all_cops[key].is_a?(Array) && value.is_a?(Array)
      combined_all_cops[key] |= value
      combined_configured_keys |= [key]
    elsif !combined_configured_keys.include?(key)
      combined_all_cops[key] = value
      combined_configured_keys << key
    end
  end

  [combined_all_cops, combined_configured_keys]
end

.merge_plugin_config_into_all_cops!(rubocop_config, plugin_config) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 78

def merge_plugin_config_into_all_cops!(rubocop_config, plugin_config)
  rubocop_config['AllCops'].merge!(plugin_config['AllCops'])
end

.merge_plugin_config_into_default_config!(default_config, plugin_config) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 82

def merge_plugin_config_into_default_config!(default_config, plugin_config)
  plugin_config.each do |key, value|
    default_config[key] = if default_config[key].is_a?(Hash)
                            resolver.merge(default_config[key], value)
                          else
                            value
                          end
  end
end

.merge_plugin_config_with_default(plugin_config, plugin_config_path) (private)

unset_nil: false keeps AllCops keys declared with a nil value by a plugin (e.g. TargetRailsVersion: ~ of rubocop-rails); .merge_all_cop_settings carries such keys into each subsequent plugin’s config, where unset_nil: true would delete them from the combined configuration. Preview is left to be applied when a project configuration is resolved against the defaults.

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 72

def merge_plugin_config_with_default(plugin_config, plugin_config_path)
  ConfigLoader.merge_with_default(
    plugin_config, plugin_config_path, unset_nil: false, resolve_preview: false
  )
end

.plugin_name(plugin) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 137

def plugin_name(plugin)
  plugin.about&.name || plugin.inspect
end

.resolver (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 168

def resolver
  @resolver ||= ConfigLoaderResolver.new
end

.validate_plugins!(plugins, runner_context) (private)

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 37

def validate_plugins!(plugins, runner_context)
  unsupported_plugins = plugins.reject { |plugin| plugin.supported?(runner_context) }
  return if unsupported_plugins.none?

  raise Plugin::NotSupportedError, unsupported_plugins
end

.warn_about_obsolete_config(plugin, config) (private)

Plugin configuration is loaded without validation, so an obsolete key in a plugin’s defaults used to surface only later, against the user’s own file, once the two had been merged. Report it here, while the path is still the plugin’s, and name the plugin: the file lives inside an installed gem, so the fix is to report it upstream. It is a warning whatever the rule’s severity: a stale plugin is not the user’s mistake and must not stop the run.

[ GitHub ]

  
# File 'lib/rubocop/plugin/configuration_integrator.rb', line 127

def warn_about_obsolete_config(plugin, config)
  messages = ConfigObsoletion.new(config).messages
  return if messages.empty?

  warn Rainbow(<<~WARNING).yellow
    Warning: obsolete configuration found in the `#{plugin_name(plugin)}` plugin:
    #{messages.join("\n")}
  WARNING
end