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.3 is the oldest officially supported Ruby version.

2.3
KNOWN_RUBIES =
[2.3, 2.4, 2.5, 2.6].freeze
OBSOLETE_RUBIES =
{
  1.9 => '0.50', 2.0 => '0.50', 2.1 => '0.58', 2.2 => '0.69'
}.freeze
RUBY_VERSION_FILENAME =
'.ruby-version'
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/FirstParameterIndentation' =>
    'The `Layout/FirstParameterIndentation` cop has been renamed to ' \
    '`Layout/IndentFirstArgument`.',
  'Layout/IndentArray' =>
    'The `Layout/IndentArray` cop has been renamed to ' \
    '`Layout/IndentFirstArrayElement`.',
  'Layout/IndentHash' =>
    'The `Layout/IndentHash` cop has been renamed to ' \
    '`Layout/IndentFirstHashElement`.',
  '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`.',
  '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?, chdir, 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



234
235
236
237
238
239
240
241
242
243
# File 'lib/rubocop/config.rb', line 234

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



232
233
234
# File 'lib/rubocop/config.rb', line 232

def loaded_path
  @loaded_path
end

Class Method Details

.create(hash, path) ⇒ Object



245
246
247
# File 'lib/rubocop/config.rb', line 245

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

Instance Method Details

#[](key) ⇒ Object



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

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

#[]=(key, value) ⇒ Object



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

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

#add_excludes_from_higher_level(highest_config) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
# File 'lib/rubocop/config.rb', line 326

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



397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/rubocop/config.rb', line 397

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.



444
445
446
447
448
449
450
451
452
# File 'lib/rubocop/config.rb', line 444

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



249
250
251
252
253
254
255
256
# File 'lib/rubocop/config.rb', line 249

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

#delete(key) ⇒ Object



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

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

#deprecation_checkObject



338
339
340
341
342
343
344
345
346
347
# File 'lib/rubocop/config.rb', line 338

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



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

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

#each_key(&block) ⇒ Object



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

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

#file_to_exclude?(file) ⇒ Boolean



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

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



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/rubocop/config.rb', line 376

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



353
354
355
# File 'lib/rubocop/config.rb', line 353

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

#for_cop(cop) ⇒ Object



349
350
351
# File 'lib/rubocop/config.rb', line 349

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

#key?(key) ⇒ Boolean



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

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

#keysObject



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

def keys
  @hash.keys
end

#make_excludes_absoluteObject



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/rubocop/config.rb', line 310

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



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

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

#merge(other_hash) ⇒ Object



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

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

#path_relative_to_config(path) ⇒ Object



435
436
437
# File 'lib/rubocop/config.rb', line 435

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

#patterns_to_excludeObject



431
432
433
# File 'lib/rubocop/config.rb', line 431

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

#patterns_to_includeObject



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

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.



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

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



306
307
308
# File 'lib/rubocop/config.rb', line 306

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

#target_rails_versionObject



474
475
476
477
478
479
480
481
482
483
# File 'lib/rubocop/config.rb', line 474

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



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/rubocop/config.rb', line 454

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



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

def to_h
  @hash
end

#to_hashObject



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

def to_hash
  @hash
end

#to_sObject



302
303
304
# File 'lib/rubocop/config.rb', line 302

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

#validateObject



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/rubocop/config.rb', line 357

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