rubocop 1.5.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +111 -14
  5. data/config/obsoletion.yml +196 -0
  6. data/lib/rubocop.rb +20 -1
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +19 -19
  8. data/lib/rubocop/comment_config.rb +6 -6
  9. data/lib/rubocop/config.rb +8 -5
  10. data/lib/rubocop/config_loader.rb +10 -6
  11. data/lib/rubocop/config_loader_resolver.rb +21 -4
  12. data/lib/rubocop/config_obsoletion.rb +64 -262
  13. data/lib/rubocop/config_obsoletion/changed_enforced_styles.rb +33 -0
  14. data/lib/rubocop/config_obsoletion/changed_parameter.rb +21 -0
  15. data/lib/rubocop/config_obsoletion/cop_rule.rb +34 -0
  16. data/lib/rubocop/config_obsoletion/extracted_cop.rb +44 -0
  17. data/lib/rubocop/config_obsoletion/parameter_rule.rb +44 -0
  18. data/lib/rubocop/config_obsoletion/removed_cop.rb +41 -0
  19. data/lib/rubocop/config_obsoletion/renamed_cop.rb +34 -0
  20. data/lib/rubocop/config_obsoletion/rule.rb +41 -0
  21. data/lib/rubocop/config_obsoletion/split_cop.rb +27 -0
  22. data/lib/rubocop/config_validator.rb +11 -4
  23. data/lib/rubocop/cop/base.rb +17 -15
  24. data/lib/rubocop/cop/cop.rb +2 -2
  25. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +6 -8
  26. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
  27. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  28. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
  29. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +19 -3
  30. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  31. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +26 -0
  32. data/lib/rubocop/cop/layout/line_length.rb +6 -16
  33. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  34. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
  35. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
  36. data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
  37. data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
  38. data/lib/rubocop/cop/layout/space_before_brackets.rb +62 -0
  39. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
  40. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
  41. data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
  42. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
  43. data/lib/rubocop/cop/lint/deprecated_constants.rb +75 -0
  44. data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
  45. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
  46. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
  47. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
  48. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +48 -0
  49. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
  50. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
  51. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  52. data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
  53. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  54. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  55. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
  56. data/lib/rubocop/cop/mixin/comments_help.rb +1 -10
  57. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_help.rb +4 -1
  59. data/lib/rubocop/cop/naming/accessor_method_name.rb +15 -1
  60. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
  61. data/lib/rubocop/cop/naming/variable_name.rb +2 -0
  62. data/lib/rubocop/cop/naming/variable_number.rb +1 -8
  63. data/lib/rubocop/cop/registry.rb +10 -0
  64. data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
  65. data/lib/rubocop/cop/style/character_literal.rb +10 -11
  66. data/lib/rubocop/cop/style/collection_methods.rb +14 -1
  67. data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
  68. data/lib/rubocop/cop/style/empty_literal.rb +6 -2
  69. data/lib/rubocop/cop/style/endless_method.rb +102 -0
  70. data/lib/rubocop/cop/style/float_division.rb +44 -1
  71. data/lib/rubocop/cop/style/for.rb +2 -0
  72. data/lib/rubocop/cop/style/hash_except.rb +95 -0
  73. data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
  74. data/lib/rubocop/cop/style/if_inside_else.rb +8 -3
  75. data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -0
  76. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  77. data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
  78. data/lib/rubocop/cop/style/lambda_call.rb +2 -1
  79. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -0
  80. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
  81. data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
  82. data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
  83. data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
  84. data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
  85. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
  86. data/lib/rubocop/cop/style/perl_backrefs.rb +86 -9
  87. data/lib/rubocop/cop/style/raise_args.rb +5 -2
  88. data/lib/rubocop/cop/style/redundant_argument.rb +21 -2
  89. data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
  90. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  91. data/lib/rubocop/cop/style/redundant_return.rb +1 -1
  92. data/lib/rubocop/cop/style/single_line_block_params.rb +30 -7
  93. data/lib/rubocop/cop/style/single_line_methods.rb +33 -2
  94. data/lib/rubocop/cop/style/sole_nested_conditional.rb +25 -9
  95. data/lib/rubocop/cop/style/special_global_vars.rb +1 -13
  96. data/lib/rubocop/cop/style/string_concatenation.rb +26 -1
  97. data/lib/rubocop/cop/style/string_literals.rb +14 -8
  98. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -3
  99. data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
  100. data/lib/rubocop/cop/util.rb +3 -1
  101. data/lib/rubocop/ext/regexp_node.rb +31 -9
  102. data/lib/rubocop/ext/regexp_parser.rb +21 -3
  103. data/lib/rubocop/formatter/emacs_style_formatter.rb +2 -0
  104. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  105. data/lib/rubocop/formatter/tap_formatter.rb +2 -0
  106. data/lib/rubocop/lockfile.rb +40 -0
  107. data/lib/rubocop/options.rb +9 -9
  108. data/lib/rubocop/rspec/cop_helper.rb +0 -4
  109. data/lib/rubocop/rspec/expect_offense.rb +34 -22
  110. data/lib/rubocop/runner.rb +16 -1
  111. data/lib/rubocop/target_finder.rb +4 -2
  112. data/lib/rubocop/target_ruby.rb +47 -11
  113. data/lib/rubocop/util.rb +16 -0
  114. data/lib/rubocop/version.rb +8 -2
  115. metadata +42 -9
@@ -19,12 +19,13 @@ module RuboCop
19
19
  #
20
20
  # # good
21
21
  # result = "Tests #{success ? "PASS" : "FAIL"}"
22
- class StringLiteralsInInterpolation < Cop
22
+ class StringLiteralsInInterpolation < Base
23
23
  include ConfigurableEnforcedStyle
24
24
  include StringLiteralsHelp
25
+ extend AutoCorrector
25
26
 
26
- def autocorrect(node)
27
- StringLiteralCorrector.correct(node, style)
27
+ def autocorrect(corrector, node)
28
+ StringLiteralCorrector.correct(corrector, node, style)
28
29
  end
29
30
 
30
31
  private
@@ -22,11 +22,12 @@ module RuboCop
22
22
  SUPER_TYPES = %i[super zsuper].freeze
23
23
 
24
24
  def_node_matcher :proc_node?, '(send (const {nil? cbase} :Proc) :new)'
25
+ def_node_matcher :symbol_proc_receiver?, '{(send ...) (super ...) zsuper}'
25
26
  def_node_matcher :symbol_proc?, <<~PATTERN
26
- ({block numblock}
27
- ${(send ...) (super ...) zsuper}
28
- ${(args (arg _)) %Integer}
29
- (send (lvar _var) $_))
27
+ {
28
+ (block $#symbol_proc_receiver? $(args (arg _var)) (send (lvar _var) $_))
29
+ (numblock $#symbol_proc_receiver? $1 (send (lvar :_1) $_))
30
+ }
30
31
  PATTERN
31
32
 
32
33
  def self.autocorrect_incompatible_with
@@ -33,7 +33,9 @@ module RuboCop
33
33
  end
34
34
 
35
35
  def add_parentheses(node, corrector)
36
- if node.arguments.empty?
36
+ if !node.respond_to?(:arguments)
37
+ corrector.wrap(node, '(', ')')
38
+ elsif node.arguments.empty?
37
39
  corrector.insert_after(node, '()')
38
40
  else
39
41
  corrector.replace(args_begin(node), '(')
@@ -15,17 +15,39 @@ module RuboCop
15
15
  # see `ext/regexp_parser`.
16
16
  attr_reader :parsed_tree
17
17
 
18
- def assign_properties(*)
19
- super
18
+ if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0')
19
+ def assign_properties(*)
20
+ super
20
21
 
21
- str = with_interpolations_blanked
22
- @parsed_tree = begin
23
- Regexp::Parser.parse(str, options: options)
24
- rescue StandardError
25
- nil
22
+ str = with_interpolations_blanked
23
+ @parsed_tree = begin
24
+ Regexp::Parser.parse(str, options: options)
25
+ rescue StandardError
26
+ nil
27
+ end
28
+ origin = loc.begin.end
29
+ @parsed_tree&.each_expression(true) { |e| e.origin = origin }
30
+ end
31
+ # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped.
32
+ # It's for compatibility with regexp_arser 1.8 and will never be maintained.
33
+ else
34
+ def assign_properties(*)
35
+ super
36
+
37
+ str = with_interpolations_blanked
38
+ begin
39
+ @parsed_tree = Regexp::Parser.parse(str, options: options)
40
+ rescue StandardError
41
+ @parsed_tree = nil
42
+ else
43
+ origin = loc.begin.end
44
+ source = @parsed_tree.to_s
45
+ @parsed_tree.each_expression(true) do |e|
46
+ e.origin = origin
47
+ e.source = source
48
+ end
49
+ end
26
50
  end
27
- origin = loc.begin.end
28
- @parsed_tree&.each_expression(true) { |e| e.origin = origin }
29
51
  end
30
52
 
31
53
  def each_capture(named: ANY)
@@ -22,9 +22,27 @@ module RuboCop
22
22
  module Base
23
23
  attr_accessor :origin
24
24
 
25
- # Shortcut to `loc.expression`
26
- def expression
27
- @expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length)
25
+ if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0')
26
+ # Shortcut to `loc.expression`
27
+ def expression
28
+ @expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length)
29
+ end
30
+ # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped.
31
+ # It's for compatibility with regexp_arser 1.8 and will never be maintained.
32
+ else
33
+ attr_accessor :source
34
+
35
+ def start_index
36
+ # ts is a byte index; convert it to a character index
37
+ @start_index ||= source.byteslice(0, ts).length
38
+ end
39
+
40
+ # Shortcut to `loc.expression`
41
+ def expression
42
+ @expression ||= begin
43
+ origin.adjust(begin_pos: start_index, end_pos: start_index + full_length)
44
+ end
45
+ end
28
46
  end
29
47
 
30
48
  # @returns a location map like `parser` does, with:
@@ -27,6 +27,8 @@ module RuboCop
27
27
  "[Todo] #{offense.message}"
28
28
  elsif offense.corrected?
29
29
  "[Corrected] #{offense.message}"
30
+ elsif offense.correctable?
31
+ "[Correctable] #{offense.message}"
30
32
  else
31
33
  offense.message
32
34
  end
@@ -90,6 +90,8 @@ module RuboCop
90
90
  green('[Todo] ')
91
91
  elsif offense.corrected?
92
92
  green('[Corrected] ')
93
+ elsif offense.correctable?
94
+ yellow('[Correctable] ')
93
95
  else
94
96
  ''
95
97
  end
@@ -71,6 +71,8 @@ module RuboCop
71
71
  '[Todo] '
72
72
  elsif offense.corrected?
73
73
  '[Corrected] '
74
+ elsif offense.correctable?
75
+ '[Correctable] '
74
76
  else
75
77
  ''
76
78
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # Encapsulation of a lockfile for use when checking for gems.
5
+ # Does not actually resolve gems, just parses the lockfile.
6
+ # @api private
7
+ class Lockfile
8
+ # Gems that the bundle depends on
9
+ def dependencies
10
+ return [] unless parser
11
+
12
+ parser.dependencies.values
13
+ end
14
+
15
+ # All activated gems, including transitive dependencies
16
+ def gems
17
+ return [] unless parser
18
+
19
+ # `Bundler::LockfileParser` returns `Bundler::LazySpecification` objects
20
+ # which are not resolved, so extract the dependencies from them
21
+ parser.dependencies.values.concat(parser.specs.flat_map(&:dependencies))
22
+ end
23
+
24
+ def includes_gem?(name)
25
+ gems.any? { |gem| gem.name == name }
26
+ end
27
+
28
+ private
29
+
30
+ def parser
31
+ return unless defined?(Bundler) && Bundler.default_lockfile
32
+ return @parser if defined?(@parser)
33
+
34
+ lockfile = Bundler.read_file(Bundler.default_lockfile)
35
+ @parser = lockfile ? Bundler::LockfileParser.new(lockfile) : nil
36
+ rescue Bundler::BundlerError
37
+ nil
38
+ end
39
+ end
40
+ end
@@ -66,7 +66,7 @@ module RuboCop
66
66
  add_configuration_options(opts)
67
67
  add_formatting_options(opts)
68
68
 
69
- option(opts, '-r', '--require FILE') { |f| require f }
69
+ option(opts, '-r', '--require FILE') { |f| require_feature(f) }
70
70
 
71
71
  add_severity_option(opts)
72
72
  add_flags_with_optional_args(opts)
@@ -92,14 +92,7 @@ module RuboCop
92
92
  raise OptionArgumentError, message
93
93
  end
94
94
 
95
- @options[:"#{option}"] =
96
- if list.empty?
97
- ['']
98
- else
99
- list.split(',').map do |c|
100
- Cop::Registry.qualified_cop_name(c, "--#{option} option")
101
- end
102
- end
95
+ @options[:"#{option}"] = list.empty? ? [''] : list.split(',')
103
96
  end
104
97
  end
105
98
 
@@ -239,6 +232,13 @@ module RuboCop
239
232
  long_opt[2..-1].sub('[no-]', '').sub(/ .*/, '')
240
233
  .tr('-', '_').gsub(/[\[\]]/, '').to_sym
241
234
  end
235
+
236
+ def require_feature(file)
237
+ # If any features were added on the CLI from `--require`,
238
+ # add them to the config.
239
+ ConfigLoader.add_loaded_features(file)
240
+ require file
241
+ end
242
242
  end
243
243
 
244
244
  # Validates option arguments and the options' compatibility with each other.
@@ -9,10 +9,6 @@ module CopHelper
9
9
  let(:ruby_version) { 2.4 }
10
10
  let(:rails_version) { false }
11
11
 
12
- def inspect_source_file(source)
13
- Tempfile.open('tmp') { |f| inspect_source(source, f) }
14
- end
15
-
16
12
  def inspect_source(source, file = nil)
17
13
  RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
18
14
  RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
@@ -111,28 +111,9 @@ module RuboCop
111
111
  source
112
112
  end
113
113
 
114
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
115
114
  def expect_offense(source, file = nil, severity: nil, **replacements)
116
- source = format_offense(source, **replacements)
117
- RuboCop::Formatter::DisabledConfigFormatter
118
- .config_to_allow_offenses = {}
119
- RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
120
- cop.instance_variable_get(:@options)[:auto_correct] = true
121
-
122
- expected_annotations = AnnotatedSource.parse(source)
123
-
124
- if expected_annotations.plain_source == source
125
- raise 'Use `expect_no_offenses` to assert that no offenses are found'
126
- end
127
-
128
- @processed_source = parse_source(expected_annotations.plain_source,
129
- file)
130
-
131
- unless @processed_source.valid_syntax?
132
- raise 'Error parsing example code: ' \
133
- "#{@processed_source.diagnostics.map(&:render).join("\n")}"
134
- end
135
-
115
+ expected_annotations = parse_annotations(source, **replacements)
116
+ @processed_source = parse_processed_source(expected_annotations.plain_source, file)
136
117
  @offenses = _investigate(cop, @processed_source)
137
118
  actual_annotations =
138
119
  expected_annotations.with_offense_annotations(@offenses)
@@ -143,7 +124,14 @@ module RuboCop
143
124
  @offenses
144
125
  end
145
126
 
146
- def expect_correction(correction, loop: true)
127
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
128
+ def expect_correction(correction, loop: true, source: nil)
129
+ if source
130
+ expected_annotations = parse_annotations(source, raise_error: false)
131
+ @processed_source = parse_processed_source(expected_annotations.plain_source)
132
+ _investigate(cop, @processed_source)
133
+ end
134
+
147
135
  raise '`expect_correction` must follow `expect_offense`' unless @processed_source
148
136
 
149
137
  iteration = 0
@@ -192,6 +180,30 @@ module RuboCop
192
180
  expect(actual_annotations.to_s).to eq(source)
193
181
  end
194
182
 
183
+ def parse_annotations(source, raise_error: true, **replacements)
184
+ set_formatter_options
185
+
186
+ source = format_offense(source, **replacements)
187
+ annotations = AnnotatedSource.parse(source)
188
+ return annotations unless raise_error && annotations.plain_source == source
189
+
190
+ raise 'Use `expect_no_offenses` to assert that no offenses are found'
191
+ end
192
+
193
+ def parse_processed_source(source, file = nil)
194
+ processed_source = parse_source(source, file)
195
+ return processed_source if processed_source.valid_syntax?
196
+
197
+ raise 'Error parsing example code: ' \
198
+ "#{processed_source.diagnostics.map(&:render).join("\n")}"
199
+ end
200
+
201
+ def set_formatter_options
202
+ RuboCop::Formatter::DisabledConfigFormatter.config_to_allow_offenses = {}
203
+ RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
204
+ cop.instance_variable_get(:@options)[:auto_correct] = true
205
+ end
206
+
195
207
  # Parsed representation of code annotated with the `^^^ Message` style
196
208
  class AnnotatedSource
197
209
  ANNOTATION_PATTERN = /\A\s*(\^+|\^{}) /.freeze
@@ -323,11 +323,16 @@ module RuboCop
323
323
  Cop::Team.mobilize(mobilized_cop_classes(config), config, @options)
324
324
  end
325
325
 
326
- def mobilized_cop_classes(config)
326
+ def mobilized_cop_classes(config) # rubocop:disable Metrics/AbcSize
327
327
  @mobilized_cop_classes ||= {}.compare_by_identity
328
328
  @mobilized_cop_classes[config] ||= begin
329
329
  cop_classes = Cop::Registry.all
330
330
 
331
+ # `@options[:only]` and `@options[:except]` are not qualified until
332
+ # needed so that the Registry can be fully loaded, including any
333
+ # cops added by `require`s.
334
+ qualify_option_cop_names
335
+
331
336
  OptionsValidator.new(@options).validate_cop_options
332
337
 
333
338
  if @options[:only]
@@ -342,6 +347,16 @@ module RuboCop
342
347
  end
343
348
  end
344
349
 
350
+ def qualify_option_cop_names
351
+ %i[only except].each do |option|
352
+ next unless @options[option]
353
+
354
+ @options[option].map! do |cop_name|
355
+ Cop::Registry.qualified_cop_name(cop_name, "--#{option} option")
356
+ end
357
+ end
358
+ end
359
+
345
360
  def filter_cop_classes(cop_classes, config)
346
361
  # use only cops that link to a style guide if requested
347
362
  return unless style_guide_cops_only?(config)
@@ -58,7 +58,7 @@ module RuboCop
58
58
  base_dir = base_dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
59
59
  all_files = find_files(base_dir, File::FNM_DOTMATCH)
60
60
  # use file.include? for performance optimization
61
- hidden_files = all_files.select { |file| file.include?(HIDDEN_PATH_SUBSTRING) }
61
+ hidden_files = all_files.select { |file| file.include?(HIDDEN_PATH_SUBSTRING) }.sort
62
62
  base_dir_config = @config_store.for(base_dir)
63
63
 
64
64
  target_files = all_files.select do |file|
@@ -70,7 +70,9 @@ module RuboCop
70
70
 
71
71
  def to_inspect?(file, hidden_files, base_dir_config)
72
72
  return false if base_dir_config.file_to_exclude?(file)
73
- return true if !hidden_files.include?(file) && ruby_file?(file)
73
+ return true if !hidden_files.bsearch do |hidden_file|
74
+ file <=> hidden_file
75
+ end && ruby_file?(file)
74
76
 
75
77
  base_dir_config.file_to_include?(file)
76
78
  end
@@ -44,33 +44,61 @@ module RuboCop
44
44
  # The target ruby version may be found in a .ruby-version file.
45
45
  # @api private
46
46
  class RubyVersionFile < Source
47
- FILENAME = '.ruby-version'
47
+ RUBY_VERSION_FILENAME = '.ruby-version'
48
+ RUBY_VERSION_PATTERN = /\A(?:ruby-)?(?<version>\d+\.\d+)/.freeze
48
49
 
49
50
  def name
50
- "`#{FILENAME}`"
51
+ "`#{RUBY_VERSION_FILENAME}`"
51
52
  end
52
53
 
53
54
  private
54
55
 
56
+ def filename
57
+ RUBY_VERSION_FILENAME
58
+ end
59
+
60
+ def pattern
61
+ RUBY_VERSION_PATTERN
62
+ end
63
+
55
64
  def find_version
56
- file = ruby_version_file
65
+ file = version_file
57
66
  return unless file && File.file?(file)
58
67
 
59
- # rubocop:disable Lint/MixedRegexpCaptureTypes
60
- # `(ruby-)` is not a capture type.
61
- File.read(file).match(/\A(ruby-)?(?<version>\d+\.\d+)/) do |md|
62
- # rubocop:enable Lint/MixedRegexpCaptureTypes
68
+ File.read(file).match(pattern) do |md|
63
69
  md[:version].to_f
64
70
  end
65
71
  end
66
72
 
67
- def ruby_version_file
68
- @ruby_version_file ||=
69
- @config.find_file_upwards(FILENAME,
73
+ def version_file
74
+ @version_file ||=
75
+ @config.find_file_upwards(filename,
70
76
  @config.base_dir_for_path_parameters)
71
77
  end
72
78
  end
73
79
 
80
+ # The target ruby version may be found in a .tool-versions file, in a line
81
+ # starting with `ruby`.
82
+ # @api private
83
+ class ToolVersionsFile < RubyVersionFile
84
+ TOOL_VERSIONS_FILENAME = '.tool-versions'
85
+ TOOL_VERSIONS_PATTERN = /\Aruby (?:ruby-)?(?<version>\d+\.\d+)/.freeze
86
+
87
+ def name
88
+ "`#{TOOL_VERSIONS_FILENAME}`"
89
+ end
90
+
91
+ private
92
+
93
+ def filename
94
+ TOOL_VERSIONS_FILENAME
95
+ end
96
+
97
+ def pattern
98
+ TOOL_VERSIONS_PATTERN
99
+ end
100
+ end
101
+
74
102
  # The lock file of Bundler may identify the target ruby version.
75
103
  # @api private
76
104
  class BundlerLockFile < Source
@@ -194,7 +222,15 @@ module RuboCop
194
222
  KNOWN_RUBIES
195
223
  end
196
224
 
197
- SOURCES = [RuboCopConfig, RubyVersionFile, BundlerLockFile, GemspecFile, Default].freeze
225
+ SOURCES = [
226
+ RuboCopConfig,
227
+ RubyVersionFile,
228
+ ToolVersionsFile,
229
+ BundlerLockFile,
230
+ GemspecFile,
231
+ Default
232
+ ].freeze
233
+
198
234
  private_constant :SOURCES
199
235
 
200
236
  def initialize(config)