Class: RuboCop::Config

Inherits:
Object
  • Object
show all
Includes:
FileFinder, PathUtil
Defined in:
lib/rubocop/config.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

COMMON_PARAMS =
%w[Exclude Include Severity inherit_mode
AutoCorrect StyleGuide Details].freeze
INTERNAL_PARAMS =
%w[Description StyleGuide VersionAdded
VersionChanged Reference Safe SafeAutoCorrect].freeze
DEFAULT_RUBY_VERSION =

2.2 is the oldest officially supported Ruby version.

2.2
KNOWN_RUBIES =
[2.2, 2.3, 2.4, 2.5, 2.6].freeze
OBSOLETE_RUBIES =
{ 1.9 => '0.50', 2.0 => '0.50', 2.1 => '0.58' }.freeze
RUBY_VERSION_FILENAME =
'.ruby-version'.freeze
DEFAULT_RAILS_VERSION =
5.0
OBSOLETE_COPS =
{
  'Style/FlipFlop' =>
    'The `Style/FlipFlop` cop has been moved to `Lint/FlipFlop`.',
  'Style/TrailingComma' =>
    'The `Style/TrailingComma` cop no longer exists. Please use ' \
    '`Style/TrailingCommaInArguments`, ' \
    '`Style/TrailingCommaInArrayLiteral`, and/or ' \
    '`Style/TrailingCommaInHashLiteral` instead.',
  'Style/TrailingCommaInLiteral' =>
    'The `Style/TrailingCommaInLiteral` cop no longer exists. Please use ' \
    '`Style/TrailingCommaInArrayLiteral` and/or ' \
    '`Style/TrailingCommaInHashLiteral` instead.',
  'Rails/DefaultScope' =>
    'The `Rails/DefaultScope` cop no longer exists.',
  'Lint/InvalidCharacterLiteral' =>
    'The `Lint/InvalidCharacterLiteral` cop has been removed since it ' \
    'was never being actually triggered.',
  'Style/SingleSpaceBeforeFirstArg' =>
    'The `Style/SingleSpaceBeforeFirstArg` cop has been renamed to ' \
    '`Layout/SpaceBeforeFirstArg`.',
  'Lint/RescueWithoutErrorClass' =>
    'The `Lint/RescueWithoutErrorClass` cop has been replaced by ' \
    '`Style/RescueStandardError`.',
  'Lint/SpaceBeforeFirstArg' =>
    'The `Lint/SpaceBeforeFirstArg` cop has been removed, since it was a ' \
    'duplicate of `Layout/SpaceBeforeFirstArg`. Please use ' \
    '`Layout/SpaceBeforeFirstArg` instead.',
  'Layout/SpaceAfterControlKeyword' =>
    'The `Layout/SpaceAfterControlKeyword` cop has been removed. Please ' \
    'use `Layout/SpaceAroundKeyword` instead.',
  'Layout/SpaceBeforeModifierKeyword' =>
    'The `Layout/SpaceBeforeModifierKeyword` cop has been removed. ' \
    'Please use `Layout/SpaceAroundKeyword` instead.',
  'Style/SpaceAfterControlKeyword' =>
    'The `Style/SpaceAfterControlKeyword` cop has been removed. Please ' \
    'use `Layout/SpaceAroundKeyword` instead.',
  'Style/SpaceBeforeModifierKeyword' =>
    'The `Style/SpaceBeforeModifierKeyword` cop has been removed. Please ' \
    'use `Layout/SpaceAroundKeyword` instead.',
  'Style/MethodCallParentheses' =>
    'The `Style/MethodCallParentheses` cop has been renamed to ' \
      '`Style/MethodCallWithoutArgsParentheses`.',
  'Lint/Eval' =>
    'The `Lint/Eval` cop has been renamed to `Security/Eval`.',
  'Style/DeprecatedHashMethods' =>
    'The `Style/DeprecatedHashMethods` cop has been renamed to ' \
      '`Style/PreferredHashMethods`.',
  'Style/AccessorMethodName' =>
    'The `Style/AccessorMethodName` cop has been moved to ' \
      '`Naming/AccessorMethodName`.',
  'Style/AsciiIdentifiers' =>
    'The `Style/AsciiIdentifiers` cop has been moved to ' \
      '`Naming/AccessorMethodName`.',
  'Style/OpMethod' =>
    'The `Style/OpMethod` cop has been renamed and moved to ' \
      '`Naming/BinaryOperatorParameterName`.',
  'Style/ClassAndModuleCamelCase' =>
    'The `Style/ClassAndModuleCamelCase` cop has been renamed to ' \
      '`Naming/ClassAndModuleCamelCase`.',
  'Style/ConstantName' =>
    'The `Style/ConstantName` cop has been renamed to ' \
      '`Naming/ConstantName`.',
  'Style/FileName' =>
    'The `Style/FileName` cop has been renamed to `Naming/FileName`.',
  'Style/MethodName' =>
    'The `Style/MethodName` cop has been renamed to ' \
      '`Naming/MethodName`.',
  'Style/PredicateName' =>
    'The `Style/PredicateName` cop has been renamed to ' \
      '`Naming/PredicateName`.',
  'Style/VariableName' =>
    'The `Style/VariableName` cop has been renamed to ' \
      '`Naming/VariableName`.',
  'Style/VariableNumber' =>
    'The `Style/VariableNumber` cop has been renamed to ' \
      '`Naming/VariableNumber`.',
  'Lint/BlockAlignment' =>
    'The `Lint/BlockAlignment` cop has been renamed to ' \
      '`Layout/BlockAlignment`.',
  'Lint/EndAlignment' =>
    'The `Lint/EndAlignment` cop has been renamed to ' \
      '`Layout/EndAlignment`.',
  'Lint/DefEndAlignment' =>
    'The `Lint/DefEndAlignment` cop has been renamed to ' \
      '`Layout/DefEndAlignment`.',
  'Performance/HashEachMethods' =>
    'The `Performance/HashEachMethods` cop has been removed ' \
      'since it no longer provides performance benefits in ' \
      'modern rubies.',
  'Style/MethodMissing' =>
    'The `Style/MethodMissing` cop has been split into ' \
      '`Style/MethodMissingSuper` and `Style/MissingRespondToMissing`.'
}.freeze
OBSOLETE_PARAMETERS =
[
  {
    cop: 'Layout/SpaceAroundOperators',
    parameter: 'MultiSpaceAllowedForOperators',
    alternative: 'If your intention was to allow extra spaces ' \
                 'for alignment, please use AllowForAlignment: ' \
                 'true instead.'
  },
  {
    cop: 'Style/Encoding',
    parameter: 'EnforcedStyle',
    alternative: 'Style/Encoding no longer supports styles. ' \
                 'The "never" behavior is always assumed.'
  },
  {
    cop: 'Style/Encoding',
    parameter: 'SupportedStyles',
    alternative: 'Style/Encoding no longer supports styles. ' \
                 'The "never" behavior is always assumed.'
  },
  {
    cop: 'Style/Encoding',
    parameter: 'AutoCorrectEncodingComment',
    alternative: 'Style/Encoding no longer supports styles. ' \
                 'The "never" behavior is always assumed.'
  },
  {
    cop: 'Style/IfUnlessModifier',
    parameter: 'MaxLineLength',
    alternative:
      '`Style/IfUnlessModifier: MaxLineLength` has been removed. Use ' \
      '`Metrics/LineLength: Max` instead'
  },
  {
    cop: 'Style/SpaceAroundOperators',
    parameter: 'MultiSpaceAllowedForOperators',
    alternative: 'If your intention was to allow extra spaces ' \
                 'for alignment, please use AllowForAlignment: ' \
                 'true instead.'
  },
  {
    cop: 'Style/WhileUntilModifier',
    parameter: 'MaxLineLength',
    alternative:
      '`Style/WhileUntilModifier: MaxLineLength` has been removed. Use ' \
      '`Metrics/LineLength: Max` instead'
  },
  {
    cop: 'AllCops',
    parameter: 'RunRailsCops',
    alternative: "Use the following configuration instead:\n" \
                 "Rails:\n  Enabled: true"
  },
  {
    cop: 'Layout/CaseIndentation',
    parameter: 'IndentWhenRelativeTo',
    alternative: '`IndentWhenRelativeTo` has been renamed to ' \
                 '`EnforcedStyle`'
  },
  {
    cop: 'Lint/BlockAlignment',
    parameter: 'AlignWith',
    alternative: '`AlignWith` has been renamed to ' \
                 '`EnforcedStyleAlignWith`'
  },
  {
    cop: 'Layout/BlockAlignment',
    parameter: 'AlignWith',
    alternative: '`AlignWith` has been renamed to ' \
                 '`EnforcedStyleAlignWith`'
  },
  {
    cop: 'Lint/EndAlignment',
    parameter: 'AlignWith',
    alternative: '`AlignWith` has been renamed to ' \
                 '`EnforcedStyleAlignWith`'
  },
  {
    cop: 'Layout/EndAlignment',
    parameter: 'AlignWith',
    alternative: '`AlignWith` has been renamed to ' \
                 '`EnforcedStyleAlignWith`'
  },
  {
    cop: 'Lint/DefEndAlignment',
    parameter: 'AlignWith',
    alternative: '`AlignWith` has been renamed to ' \
                 '`EnforcedStyleAlignWith`'
  },
  {
    cop: 'Layout/DefEndAlignment',
    parameter: 'AlignWith',
    alternative: '`AlignWith` has been renamed to ' \
                 '`EnforcedStyleAlignWith`'
  },
  {
    cop: 'Rails/UniqBeforePluck',
    parameter: 'EnforcedMode',
    alternative: '`EnforcedMode` has been renamed to ' \
                 '`EnforcedStyle`'
  }
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from FileFinder

#find_file_upwards, #find_files_upwards, root_level=, root_level?

Methods included from PathUtil

absolute?, hidden_dir?, hidden_file_in_not_hidden_dir?, match_path?, pwd, relative_path, reset_pwd, smart_path

Constructor Details

#initialize(hash = {}, loaded_path = nil) ⇒ Config

Returns a new instance of Config



227
228
229
230
231
232
233
234
235
236
# File 'lib/rubocop/config.rb', line 227

def initialize(hash = {}, loaded_path = nil)
  @loaded_path = loaded_path
  @for_cop = Hash.new do |h, cop|
    qualified_cop_name = Cop::Cop.qualified_cop_name(cop, loaded_path)
    cop_options = self[qualified_cop_name] || {}
    cop_options['Enabled'] = enable_cop?(qualified_cop_name, cop_options)
    h[cop] = cop_options
  end
  @hash = hash
end

Instance Attribute Details

#loaded_pathObject (readonly)

Returns the value of attribute loaded_path



225
226
227
# File 'lib/rubocop/config.rb', line 225

def loaded_path
  @loaded_path
end

Class Method Details

.create(hash, path) ⇒ Object



238
239
240
# File 'lib/rubocop/config.rb', line 238

def self.create(hash, path)
  new(hash, path).check
end

Instance Method Details

#[](key) ⇒ Object



251
252
253
# File 'lib/rubocop/config.rb', line 251

def [](key)
  @hash[key]
end

#[]=(key, value) ⇒ Object



255
256
257
# File 'lib/rubocop/config.rb', line 255

def []=(key, value)
  @hash[key] = value
end

#add_excludes_from_higher_level(highest_config) ⇒ Object



319
320
321
322
323
324
325
326
327
328
329
# File 'lib/rubocop/config.rb', line 319

def add_excludes_from_higher_level(highest_config)
  return unless highest_config.for_all_cops['Exclude']

  excludes = for_all_cops['Exclude'] ||= []
  highest_config.for_all_cops['Exclude'].each do |path|
    unless path.is_a?(Regexp) || absolute?(path)
      path = File.join(File.dirname(highest_config.loaded_path), path)
    end
    excludes << path unless excludes.include?(path)
  end
end

#allowed_camel_case_file?(file) ⇒ Boolean

Returns:

  • (Boolean)


390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/rubocop/config.rb', line 390

def allowed_camel_case_file?(file)
  # Gemspecs are allowed to have dashes because that fits with bundler best
  # practices in the case when the gem is nested under a namespace (e.g.,
  # `bundler-console` conveys `Bundler::Console`).
  return true if File.extname(file) == '.gemspec'

  file_to_include?(file) do |pattern, relative_path, absolute_path|
    pattern.to_s =~ /[A-Z]/ &&
      (match_path?(pattern, relative_path) ||
       match_path?(pattern, absolute_path))
  end
end

#base_dir_for_path_parametersObject

Paths specified in configuration files starting with .rubocop are relative to the directory where that file is. Paths in other config files are relative to the current directory. This is so that paths in config/default.yml, for example, are not relative to RuboCop's config directory since that wouldn't work.



437
438
439
440
441
442
443
444
445
# File 'lib/rubocop/config.rb', line 437

def base_dir_for_path_parameters
  @base_dir_for_path_parameters ||=
    if File.basename(loaded_path).start_with?('.rubocop') &&
       loaded_path != File.join(Dir.home, ConfigLoader::DOTFILE)
      File.expand_path(File.dirname(loaded_path))
    else
      Dir.pwd
    end
end

#checkObject



242
243
244
245
246
247
248
249
# File 'lib/rubocop/config.rb', line 242

def check
  deprecation_check do |deprecation_message|
    warn("#{loaded_path} - #{deprecation_message}")
  end
  validate
  make_excludes_absolute
  self
end

#delete(key) ⇒ Object



259
260
261
# File 'lib/rubocop/config.rb', line 259

def delete(key)
  @hash.delete(key)
end

#deprecation_checkObject



331
332
333
334
335
336
337
338
339
340
# File 'lib/rubocop/config.rb', line 331

def deprecation_check
  %w[Exclude Include].each do |key|
    plural = "#{key}s"
    next unless for_all_cops[plural]

    for_all_cops[key] = for_all_cops[plural] # Stay backwards compatible.
    for_all_cops.delete(plural)
    yield "AllCops/#{plural} was renamed to AllCops/#{key}"
  end
end

#each(&block) ⇒ Object



263
264
265
# File 'lib/rubocop/config.rb', line 263

def each(&block)
  @hash.each(&block)
end

#each_key(&block) ⇒ Object



275
276
277
# File 'lib/rubocop/config.rb', line 275

def each_key(&block)
  @hash.each_key(&block)
end

#file_to_exclude?(file) ⇒ Boolean

Returns:

  • (Boolean)


413
414
415
416
417
418
# File 'lib/rubocop/config.rb', line 413

def file_to_exclude?(file)
  file = File.expand_path(file)
  patterns_to_exclude.any? do |pattern|
    match_path?(pattern, file)
  end
end

#file_to_include?(file) ⇒ Boolean

Returns:

  • (Boolean)


369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/rubocop/config.rb', line 369

def file_to_include?(file)
  relative_file_path = path_relative_to_config(file)

  # Optimization to quickly decide if the given file is hidden (on the top
  # level) and can not be matched by any pattern.
  is_hidden = relative_file_path.start_with?('.') &&
              !relative_file_path.start_with?('..')
  return false if is_hidden && !possibly_include_hidden?

  absolute_file_path = File.expand_path(file)

  patterns_to_include.any? do |pattern|
    if block_given?
      yield pattern, relative_file_path, absolute_file_path
    else
      match_path?(pattern, relative_file_path) ||
        match_path?(pattern, absolute_file_path)
    end
  end
end

#for_all_copsObject



346
347
348
# File 'lib/rubocop/config.rb', line 346

def for_all_cops
  @for_all_cops ||= self['AllCops'] || {}
end

#for_cop(cop) ⇒ Object



342
343
344
# File 'lib/rubocop/config.rb', line 342

def for_cop(cop)
  @for_cop[cop.respond_to?(:cop_name) ? cop.cop_name : cop]
end

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


267
268
269
# File 'lib/rubocop/config.rb', line 267

def key?(key)
  @hash.key?(key)
end

#keysObject



271
272
273
# File 'lib/rubocop/config.rb', line 271

def keys
  @hash.keys
end

#make_excludes_absoluteObject



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/rubocop/config.rb', line 303

def make_excludes_absolute
  each_key do |key|
    validate_section_presence(key)
    next unless self[key]['Exclude']

    self[key]['Exclude'].map! do |exclude_elem|
      if exclude_elem.is_a?(String) && !absolute?(exclude_elem)
        File.expand_path(File.join(base_dir_for_path_parameters,
                                   exclude_elem))
      else
        exclude_elem
      end
    end
  end
end

#map(&block) ⇒ Object



279
280
281
# File 'lib/rubocop/config.rb', line 279

def map(&block)
  @hash.map(&block)
end

#merge(other_hash) ⇒ Object



283
284
285
# File 'lib/rubocop/config.rb', line 283

def merge(other_hash)
  @hash.merge(other_hash)
end

#path_relative_to_config(path) ⇒ Object



428
429
430
# File 'lib/rubocop/config.rb', line 428

def path_relative_to_config(path)
  relative_path(path, base_dir_for_path_parameters)
end

#patterns_to_excludeObject



424
425
426
# File 'lib/rubocop/config.rb', line 424

def patterns_to_exclude
  for_all_cops['Exclude'] || []
end

#patterns_to_includeObject



420
421
422
# File 'lib/rubocop/config.rb', line 420

def patterns_to_include
  for_all_cops['Include'] || []
end

#possibly_include_hidden?Boolean

Returns true if there's a chance that an Include pattern matches hidden files, false if that's definitely not possible.

Returns:

  • (Boolean)


405
406
407
408
409
410
411
# File 'lib/rubocop/config.rb', line 405

def possibly_include_hidden?
  return @possibly_include_hidden if defined?(@possibly_include_hidden)

  @possibly_include_hidden = patterns_to_include.any? do |s|
    s.is_a?(Regexp) || s.start_with?('.') || s.include?('/.')
  end
end

#signatureObject



299
300
301
# File 'lib/rubocop/config.rb', line 299

def signature
  @signature ||= Digest::MD5.hexdigest(to_s)
end

#target_rails_versionObject



467
468
469
470
471
472
473
474
475
476
# File 'lib/rubocop/config.rb', line 467

def target_rails_version
  @target_rails_version ||=
    if for_all_cops['TargetRailsVersion']
      for_all_cops['TargetRailsVersion'].to_f
    elsif target_rails_version_from_bundler_lock_file
      target_rails_version_from_bundler_lock_file
    else
      DEFAULT_RAILS_VERSION
    end
end

#target_ruby_versionObject



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/rubocop/config.rb', line 447

def target_ruby_version
  @target_ruby_version ||= begin
    if for_all_cops['TargetRubyVersion']
      @target_ruby_version_source = :rubocop_yml

      for_all_cops['TargetRubyVersion'].to_f
    elsif target_ruby_version_from_version_file
      @target_ruby_version_source = :ruby_version_file

      target_ruby_version_from_version_file
    elsif target_ruby_version_from_bundler_lock_file
      @target_ruby_version_source = :bundler_lock_file

      target_ruby_version_from_bundler_lock_file
    else
      DEFAULT_RUBY_VERSION
    end
  end
end

#to_hObject



287
288
289
# File 'lib/rubocop/config.rb', line 287

def to_h
  @hash
end

#to_hashObject



291
292
293
# File 'lib/rubocop/config.rb', line 291

def to_hash
  @hash
end

#to_sObject



295
296
297
# File 'lib/rubocop/config.rb', line 295

def to_s
  @to_s ||= @hash.to_s
end

#validateObject



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/rubocop/config.rb', line 350

def validate
  # Don't validate RuboCop's own files. Avoids infinite recursion.
  base_config_path = File.expand_path(File.join(ConfigLoader::RUBOCOP_HOME,
                                                'config'))
  return if File.expand_path(loaded_path).start_with?(base_config_path)

  valid_cop_names, invalid_cop_names = keys.partition do |key|
    ConfigLoader.default_configuration.key?(key)
  end

  reject_obsolete_cops_and_parameters
  warn_about_unrecognized_cops(invalid_cop_names)
  check_target_ruby
  validate_parameter_names(valid_cop_names)
  validate_enforced_styles(valid_cop_names)
  validate_syntax_cop
  reject_mutually_exclusive_defaults
end