# frozen_string_literal: truerequire'pathname'moduleRuboCop# Handles validation of configuration, for example cop names, parameter# names, and Ruby versions.classConfigValidatorextendForwardable# @api privateCOMMON_PARAMS=%w[Exclude Include Severity inherit_mode AutoCorrect StyleGuide Details].freeze# @api privateINTERNAL_PARAMS=%w[Description StyleGuide
VersionAdded VersionChanged VersionRemoved
Reference Safe SafeAutoCorrect].freeze# @api privateNEW_COPS_VALUES=%w[pending disable enable].freeze# @api privateCONFIG_CHECK_KEYS=%w[Enabled Safe SafeAutoCorrect AutoCorrect].to_set.freezeCONFIG_CHECK_DEPARTMENTS=%w[pending override_department].freezeprivate_constant:CONFIG_CHECK_KEYS,:CONFIG_CHECK_DEPARTMENTSdef_delegators:@config,:smart_loaded_path,:for_all_copsdefinitialize(config)@config=config@config_obsoletion=ConfigObsoletion.new(config)@target_ruby=TargetRuby.new(config)enddefvalidatecheck_cop_config_value(@config)reject_conflicting_safe_settings# Don't validate RuboCop's own files further. Avoids infinite recursion.returnif@config.internal?valid_cop_names,invalid_cop_names=@config.keys.partitiondo|key|ConfigLoader.default_configuration.key?(key)endcheck_obsoletionsalert_about_unrecognized_cops(invalid_cop_names)check_target_rubyvalidate_new_cops_parametervalidate_parameter_names(valid_cop_names)validate_enforced_styles(valid_cop_names)validate_syntax_copreject_mutually_exclusive_defaultsenddeftarget_ruby_versiontarget_ruby.versionenddefvalidate_section_presence(name)returnunless@config.key?(name)&&@config[name].nil?raiseValidationError,"empty section #{name} found in #{smart_loaded_path}"endprivateattr_reader:target_rubydefcheck_obsoletions@config_obsoletion.reject_obsolete!returnunless@config_obsoletion.warnings.any?warnRainbow("Warning: #{@config_obsoletion.warnings.join("\n")}").yellowenddefcheck_target_rubyreturniftarget_ruby.supported?source=target_ruby.sourcelast_version=target_ruby.rubocop_version_with_supportmsg=iflast_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}."endmsg+="\nSupported versions: #{TargetRuby.supported_versions.join(', ')}"raiseValidationError,msgenddefalert_about_unrecognized_cops(invalid_cop_names)unknown_cops=[]invalid_cop_names.eachdo|name|# There could be a custom cop with this name. If so, don't warnnextifCop::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.nextifname=='inherit_mode'message=<<~MESSAGE.rstrip
unrecognized cop or department #{name} found in #{smart_loaded_path}#{suggestion(name)} MESSAGEunknown_cops<<messageendraiseValidationError,unknown_cops.join("\n")ifunknown_cops.any?enddefsuggestion(name)registry=Cop::Registry.globaldepartments=registry.departments.map(&:to_s)suggestions=NameSimilarity.find_similar_names(name,departments+registry.map(&:cop_name))ifsuggestions.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('`, `')}`."ifsuggestions.any?endenddefvalidate_syntax_copsyntax_config=@config['Lint/Syntax']default_config=ConfigLoader.default_configuration['Lint/Syntax']returnunlesssyntax_config&&default_config.merge(syntax_config)!=default_configraiseValidationError,"configuration for Syntax cop found in #{smart_loaded_path}\n"\'It\'s not possible to disable this cop.'enddefvalidate_new_cops_parameternew_cop_parameter=@config.for_all_cops['NewCops']returnifnew_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(', ')}"raiseValidationError,messageenddefvalidate_parameter_names(valid_cop_names)valid_cop_names.eachdo|name|validate_section_presence(name)each_invalid_parameter(name)do|param,supported_params|warnRainbow(<<~MESSAGE).yellow
Warning: #{name} does not support #{param} parameter.
Supported parameters are:
- #{supported_params.join("\n - ")} MESSAGEendendenddefeach_invalid_parameter(cop_name)default_config=ConfigLoader.default_configuration[cop_name]@config[cop_name].each_keydo|param|nextifCOMMON_PARAMS.include?(param)||default_config.key?(param)supported_params=default_config.keys-INTERNAL_PARAMSyieldparam,supported_paramsendenddefvalidate_enforced_styles(valid_cop_names)# rubocop:todo Metrics/AbcSizevalid_cop_names.eachdo|name|styles=@config[name].select{|key,_|key.start_with?('Enforced')}styles.eachdo|style_name,style|supported_key=RuboCop::Cop::Util.to_supported_styles(style_name)valid=ConfigLoader.default_configuration[name][supported_key]nextunlessvalidnextifvalid.include?(style)nextifvalidate_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(', ')}"raiseValidationError,msgendendenddefvalidate_support_and_has_list(name,formats,valid)ConfigLoader.default_configuration[name]['AllowMultipleStyles']&&formats.is_a?(Array)&&formats.all?{|format|valid.include?(format)}enddefreject_mutually_exclusive_defaultsdisabled_by_default=for_all_cops['DisabledByDefault']enabled_by_default=for_all_cops['EnabledByDefault']returnunlessdisabled_by_default&&enabled_by_defaultmsg='Cops cannot be both enabled by default and disabled by default'raiseValidationError,msgenddefreject_conflicting_safe_settings@config.eachdo|name,cop_config|nextunlesscop_config.is_a?(Hash)nextunlesscop_config['Safe']==false&&cop_config['SafeAutoCorrect']==truemsg='Unsafe cops cannot have a safe auto-correction '\"(section #{name} in #{smart_loaded_path})"raiseValidationError,msgendenddefcheck_cop_config_value(hash,parent=nil)hash.eachdo|key,value|check_cop_config_value(value,key)ifvalue.is_a?(Hash)nextunlessCONFIG_CHECK_KEYS.include?(key)&&value.is_a?(String)nextifkey=='Enabled'&&CONFIG_CHECK_DEPARTMENTS.include?(value)raiseValidationError,msg_not_boolean(parent,key,value)endend# FIXME: Handling colors in exception messages like this is ugly.defmsg_not_boolean(parent,key,value)"#{Rainbow('').reset}"\"Property #{Rainbow(key).yellow} of cop #{Rainbow(parent).yellow}"\" is supposed to be a boolean and #{Rainbow(value).yellow} is not."endendend