rubocop 1.7.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +4 -3
  4. data/config/default.yml +137 -31
  5. data/config/obsoletion.yml +4 -0
  6. data/lib/rubocop.rb +14 -1
  7. data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
  8. data/lib/rubocop/comment_config.rb +6 -6
  9. data/lib/rubocop/config.rb +5 -2
  10. data/lib/rubocop/config_loader.rb +7 -14
  11. data/lib/rubocop/config_store.rb +12 -1
  12. data/lib/rubocop/cop/base.rb +2 -1
  13. data/lib/rubocop/cop/exclude_limit.rb +26 -0
  14. data/lib/rubocop/cop/gemspec/date_assignment.rb +56 -0
  15. data/lib/rubocop/cop/generator.rb +1 -3
  16. data/lib/rubocop/cop/internal_affairs.rb +5 -1
  17. data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
  18. data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
  19. data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
  20. data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
  21. data/lib/rubocop/cop/layout/class_structure.rb +7 -2
  22. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +38 -18
  23. data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
  24. data/lib/rubocop/cop/layout/line_length.rb +2 -1
  25. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +26 -0
  26. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
  27. data/lib/rubocop/cop/layout/space_before_brackets.rb +19 -16
  28. data/lib/rubocop/cop/lint/debugger.rb +58 -14
  29. data/lib/rubocop/cop/lint/deprecated_constants.rb +80 -0
  30. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +13 -4
  31. data/lib/rubocop/cop/lint/duplicate_require.rb +2 -2
  32. data/lib/rubocop/cop/lint/else_layout.rb +1 -1
  33. data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
  34. data/lib/rubocop/cop/lint/multiple_comparison.rb +4 -4
  35. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
  36. data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
  37. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
  38. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
  39. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
  40. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +50 -0
  41. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +5 -3
  42. data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
  43. data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
  44. data/lib/rubocop/cop/message_annotator.rb +4 -1
  45. data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
  46. data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
  47. data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
  48. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
  49. data/lib/rubocop/cop/mixin/code_length.rb +3 -1
  50. data/lib/rubocop/cop/mixin/comments_help.rb +1 -11
  51. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
  52. data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
  53. data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
  54. data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
  55. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +5 -1
  56. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
  57. data/lib/rubocop/cop/naming/variable_name.rb +2 -0
  58. data/lib/rubocop/cop/naming/variable_number.rb +2 -9
  59. data/lib/rubocop/cop/registry.rb +1 -1
  60. data/lib/rubocop/cop/severity.rb +3 -3
  61. data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
  62. data/lib/rubocop/cop/style/constant_visibility.rb +27 -0
  63. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
  64. data/lib/rubocop/cop/style/double_negation.rb +2 -2
  65. data/lib/rubocop/cop/style/empty_literal.rb +6 -2
  66. data/lib/rubocop/cop/style/endless_method.rb +102 -0
  67. data/lib/rubocop/cop/style/eval_with_location.rb +138 -49
  68. data/lib/rubocop/cop/style/explicit_block_argument.rb +11 -1
  69. data/lib/rubocop/cop/style/exponential_notation.rb +6 -7
  70. data/lib/rubocop/cop/style/float_division.rb +3 -0
  71. data/lib/rubocop/cop/style/format_string_token.rb +18 -2
  72. data/lib/rubocop/cop/style/hash_conversion.rb +81 -0
  73. data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
  74. data/lib/rubocop/cop/style/if_inside_else.rb +22 -10
  75. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
  76. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +4 -0
  77. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
  78. data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
  79. data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
  80. data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
  81. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  82. data/lib/rubocop/cop/style/raise_args.rb +3 -2
  83. data/lib/rubocop/cop/style/redundant_return.rb +1 -1
  84. data/lib/rubocop/cop/style/single_line_methods.rb +32 -2
  85. data/lib/rubocop/cop/style/sole_nested_conditional.rb +29 -5
  86. data/lib/rubocop/cop/style/special_global_vars.rb +3 -3
  87. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  88. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  89. data/lib/rubocop/cop/style/while_until_modifier.rb +2 -4
  90. data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
  91. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  92. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
  93. data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
  94. data/lib/rubocop/magic_comment.rb +30 -1
  95. data/lib/rubocop/options.rb +1 -1
  96. data/lib/rubocop/rspec/expect_offense.rb +5 -2
  97. data/lib/rubocop/runner.rb +1 -0
  98. data/lib/rubocop/target_ruby.rb +47 -11
  99. data/lib/rubocop/version.rb +2 -2
  100. metadata +24 -7
@@ -28,10 +28,6 @@ module RuboCop
28
28
  # 10_000_00 # typical representation of $10,000 in cents
29
29
  #
30
30
  class NumericLiterals < Base
31
- # The parameter is called MinDigits (meaning the minimum number of
32
- # digits for which an offense can be registered), but essentially it's
33
- # a Max parameter (the maximum number of something that's allowed).
34
- include ConfigurableMax
35
31
  include IntegerNode
36
32
  extend AutoCorrector
37
33
 
@@ -39,6 +35,11 @@ module RuboCop
39
35
  'separate every 3 digits with them.'
40
36
  DELIMITER_REGEXP = /[eE.]/.freeze
41
37
 
38
+ # The parameter is called MinDigits (meaning the minimum number of
39
+ # digits for which an offense can be registered), but essentially it's
40
+ # a Max parameter (the maximum number of something that's allowed).
41
+ exclude_limit 'MinDigits'
42
+
42
43
  def on_int(node)
43
44
  check(node)
44
45
  end
@@ -49,10 +50,6 @@ module RuboCop
49
50
 
50
51
  private
51
52
 
52
- def max_parameter_name
53
- 'MinDigits'
54
- end
55
-
56
53
  def check(node)
57
54
  int = integer_part(node)
58
55
 
@@ -62,7 +59,7 @@ module RuboCop
62
59
 
63
60
  case int
64
61
  when /^\d+$/
65
- return unless (self.max = int.size + 1)
62
+ return unless (self.min_digits = int.size + 1)
66
63
 
67
64
  register_offense(node)
68
65
  when /\d{4}/, short_group_regex
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # These can be replaced by their respective predicate methods.
9
9
  # The cop can also be configured to do the reverse.
10
10
  #
11
- # The cop disregards `#nonzero?` as it its value is truthy or falsey,
11
+ # The cop disregards `#nonzero?` as its value is truthy or falsey,
12
12
  # but not `true` and `false`, and thus not always interchangeable with
13
13
  # `!= 0`.
14
14
  #
@@ -81,11 +81,12 @@ module RuboCop
81
81
  return node.source if message_nodes.size > 1
82
82
 
83
83
  argument = message_nodes.first.source
84
+ exception_class = exception_node.const_name || exception_node.receiver.source
84
85
 
85
86
  if node.parent && requires_parens?(node.parent)
86
- "#{node.method_name}(#{exception_node.const_name}.new(#{argument}))"
87
+ "#{node.method_name}(#{exception_class}.new(#{argument}))"
87
88
  else
88
- "#{node.method_name} #{exception_node.const_name}.new(#{argument})"
89
+ "#{node.method_name} #{exception_class}.new(#{argument})"
89
90
  end
90
91
  end
91
92
 
@@ -66,7 +66,7 @@ module RuboCop
66
66
  end
67
67
 
68
68
  def correct_with_arguments(return_node, corrector)
69
- if return_node.arguments.size > 1
69
+ if return_node.children.size > 1
70
70
  add_brackets(corrector, return_node)
71
71
  elsif hash_without_braces?(return_node.first_argument)
72
72
  add_braces(corrector, return_node.first_argument)
@@ -8,6 +8,10 @@ module RuboCop
8
8
  #
9
9
  # Endless methods added in Ruby 3.0 are also accepted by this cop.
10
10
  #
11
+ # If `Style/EndlessMethod` is enabled with `EnforcedStyle: allow_single_line` or
12
+ # `allow_always`, single-line methods will be auto-corrected to endless
13
+ # methods if there is only one statement in the body.
14
+ #
11
15
  # @example
12
16
  # # bad
13
17
  # def some_method; body end
@@ -47,6 +51,30 @@ module RuboCop
47
51
  private
48
52
 
49
53
  def autocorrect(corrector, node)
54
+ if correct_to_endless?(node.body)
55
+ correct_to_endless(corrector, node)
56
+ else
57
+ correct_to_multiline(corrector, node)
58
+ end
59
+ end
60
+
61
+ def allow_empty?
62
+ cop_config['AllowIfMethodIsEmpty']
63
+ end
64
+
65
+ def correct_to_endless?(body_node)
66
+ return false if target_ruby_version < 3.0
67
+
68
+ endless_method_config = config.for_cop('Style/EndlessMethod')
69
+
70
+ return false unless endless_method_config['Enabled']
71
+ return false if endless_method_config['EnforcedStyle'] == 'disallow'
72
+ return false unless body_node
73
+
74
+ !(body_node.begin_type? || body_node.kwbegin_type?)
75
+ end
76
+
77
+ def correct_to_multiline(corrector, node)
50
78
  each_part(node.body) do |part|
51
79
  LineBreakCorrector.break_line_before(
52
80
  range: part, node: node, corrector: corrector,
@@ -62,8 +90,10 @@ module RuboCop
62
90
  move_comment(node, corrector)
63
91
  end
64
92
 
65
- def allow_empty?
66
- cop_config['AllowIfMethodIsEmpty']
93
+ def correct_to_endless(corrector, node)
94
+ arguments = node.arguments.any? ? node.arguments.source : '()'
95
+ replacement = "def #{node.method_name}#{arguments} = #{node.body.source}"
96
+ corrector.replace(node, replacement)
67
97
  end
68
98
 
69
99
  def each_part(body)
@@ -63,13 +63,13 @@ module RuboCop
63
63
  end
64
64
 
65
65
  def autocorrect(corrector, node, if_branch)
66
+ corrector.wrap(node.condition, '(', ')') if node.condition.or_type?
67
+
66
68
  if node.unless?
67
69
  corrector.replace(node.loc.keyword, 'if')
68
70
  corrector.insert_before(node.condition, '!')
69
71
  end
70
72
 
71
- corrector.wrap(node.condition, '(', ')') if node.condition.or_type?
72
-
73
73
  and_operator = if_branch.unless? ? ' && !' : ' && '
74
74
  if if_branch.modifier_form?
75
75
  correct_for_guard_condition_style(corrector, node, if_branch, and_operator)
@@ -80,8 +80,11 @@ module RuboCop
80
80
  end
81
81
 
82
82
  def correct_for_guard_condition_style(corrector, node, if_branch, and_operator)
83
+ outer_condition = node.condition
84
+ correct_outer_condition(corrector, outer_condition)
85
+
83
86
  condition = if_branch.condition
84
- corrector.insert_after(node.condition, replacement_condition(and_operator, condition))
87
+ corrector.insert_after(outer_condition, replacement_condition(and_operator, condition))
85
88
 
86
89
  range = range_between(if_branch.loc.keyword.begin_pos, condition.source_range.end_pos)
87
90
  corrector.remove(range_with_surrounding_space(range: range, newlines: false))
@@ -100,14 +103,35 @@ module RuboCop
100
103
  def correct_for_comment(corrector, node, if_branch)
101
104
  return if config.for_cop('Style/IfUnlessModifier')['Enabled']
102
105
 
103
- comments = processed_source.comments_before_line(if_branch.source_range.line)
106
+ comments = processed_source.ast_with_comments[if_branch]
104
107
  comment_text = comments.map(&:text).join("\n") << "\n"
105
108
 
106
109
  corrector.insert_before(node.loc.keyword, comment_text) unless comments.empty?
107
110
  end
108
111
 
112
+ def correct_outer_condition(corrector, condition)
113
+ return unless requrie_parentheses?(condition)
114
+
115
+ end_pos = condition.loc.selector.end_pos
116
+ begin_pos = condition.first_argument.source_range.begin_pos
117
+ return if end_pos > begin_pos
118
+
119
+ corrector.replace(range_between(end_pos, begin_pos), '(')
120
+ corrector.insert_after(condition.last_argument.source_range, ')')
121
+ end
122
+
123
+ def requrie_parentheses?(condition)
124
+ condition.send_type? && !condition.arguments.empty? && !condition.parenthesized?
125
+ end
126
+
127
+ def arguments_range(node)
128
+ range_between(
129
+ node.first_argument.source_range.begin_pos, node.last_argument.source_range.end_pos
130
+ )
131
+ end
132
+
109
133
  def wrap_condition?(node)
110
- node.or_type? ||
134
+ node.and_type? || node.or_type? ||
111
135
  (node.send_type? && node.arguments.any? && !node.parenthesized?)
112
136
  end
113
137
 
@@ -81,13 +81,13 @@ module RuboCop
81
81
  }
82
82
 
83
83
  PERL_VARS =
84
- Hash[ENGLISH_VARS.flat_map { |k, vs| vs.map { |v| [v, [k]] } }]
84
+ ENGLISH_VARS.flat_map { |k, vs| vs.map { |v| [v, [k]] } }.to_h
85
85
 
86
86
  ENGLISH_VARS.merge!(
87
- Hash[ENGLISH_VARS.flat_map { |_, vs| vs.map { |v| [v, [v]] } }]
87
+ ENGLISH_VARS.flat_map { |_, vs| vs.map { |v| [v, [v]] } }.to_h
88
88
  )
89
89
  PERL_VARS.merge!(
90
- Hash[PERL_VARS.flat_map { |_, vs| vs.map { |v| [v, [v]] } }]
90
+ PERL_VARS.flat_map { |_, vs| vs.map { |v| [v, [v]] } }.to_h
91
91
  )
92
92
  ENGLISH_VARS.each_value(&:freeze).freeze
93
93
  PERL_VARS.each_value(&:freeze).freeze
@@ -116,7 +116,7 @@ module RuboCop
116
116
  parts.map do |part|
117
117
  if part.str_type?
118
118
  if single_quoted?(part)
119
- part.value.gsub('\\') { '\\\\' }
119
+ part.value.gsub(/(\\|")/, '\\\\\&')
120
120
  else
121
121
  part.value.inspect[1..-2]
122
122
  end
@@ -120,7 +120,7 @@ module RuboCop
120
120
  if condition.begin_type?
121
121
  condition.to_a.any? { |x| complex_condition?(x) }
122
122
  else
123
- non_complex_expression?(condition) ? false : true
123
+ !non_complex_expression?(condition)
124
124
  end
125
125
  end
126
126
 
@@ -41,12 +41,10 @@ module RuboCop
41
41
  'having a single-line body.'
42
42
 
43
43
  def on_while(node)
44
- return unless node.multiline? && single_line_as_modifier?(node)
44
+ return unless single_line_as_modifier?(node)
45
45
 
46
46
  add_offense(node.loc.keyword, message: format(MSG, keyword: node.keyword)) do |corrector|
47
- oneline = "#{node.body.source} #{node.keyword} #{node.condition.source}"
48
-
49
- corrector.replace(node, oneline)
47
+ corrector.replace(node, to_modifier_form(node))
50
48
  end
51
49
  end
52
50
  alias on_until on_while
@@ -23,6 +23,7 @@ module RuboCop
23
23
 
24
24
  def minimum_severity_to_fail
25
25
  @minimum_severity_to_fail ||= begin
26
+ # Unless given explicitly as `fail_level`, `:info` severity offenses do not fail
26
27
  name = options.fetch(:fail_level, :refactor)
27
28
  RuboCop::Cop::Severity.new(name)
28
29
  end
@@ -63,7 +63,7 @@ module RuboCop
63
63
  # rubocop:enable Metrics/AbcSize
64
64
 
65
65
  def ordered_offense_counts(offense_counts)
66
- Hash[offense_counts.sort_by { |k, v| [-v, k] }]
66
+ offense_counts.sort_by { |k, v| [-v, k] }.to_h
67
67
  end
68
68
 
69
69
  def total_offense_count(offense_counts)
@@ -13,6 +13,7 @@ module RuboCop
13
13
  include PathUtil
14
14
 
15
15
  COLOR_FOR_SEVERITY = {
16
+ info: :gray,
16
17
  refactor: :yellow,
17
18
  convention: :yellow,
18
19
  warning: :magenta,
@@ -76,7 +77,7 @@ module RuboCop
76
77
  end
77
78
 
78
79
  def colored_severity_code(offense)
79
- color = COLOR_FOR_SEVERITY[offense.severity.name]
80
+ color = COLOR_FOR_SEVERITY.fetch(offense.severity.name)
80
81
  colorize(offense.severity.code, color)
81
82
  end
82
83
 
@@ -51,7 +51,7 @@ module RuboCop
51
51
  # rubocop:enable Metrics/AbcSize
52
52
 
53
53
  def ordered_offense_counts(offense_counts)
54
- Hash[offense_counts.sort_by { |k, v| [-v, k] }]
54
+ offense_counts.sort_by { |k, v| [-v, k] }.to_h
55
55
  end
56
56
 
57
57
  def total_offense_count(offense_counts)
@@ -27,7 +27,7 @@ module RuboCop
27
27
  end
28
28
 
29
29
  def any?
30
- frozen_string_literal_specified? || encoding_specified?
30
+ frozen_string_literal_specified? || encoding_specified? || shareable_constant_value_specified?
31
31
  end
32
32
 
33
33
  # Does the magic comment enable the frozen string literal feature.
@@ -46,6 +46,10 @@ module RuboCop
46
46
  [true, false].include?(frozen_string_literal)
47
47
  end
48
48
 
49
+ def valid_shareable_constant_value?
50
+ %w[none literal experimental_everything experimental_copy].include?(shareable_constant_values)
51
+ end
52
+
49
53
  # Was a magic comment for the frozen string literal found?
50
54
  #
51
55
  # @return [Boolean]
@@ -53,6 +57,13 @@ module RuboCop
53
57
  specified?(frozen_string_literal)
54
58
  end
55
59
 
60
+ # Was a shareable_constant_value specified?
61
+ #
62
+ # @return [Boolean]
63
+ def shareable_constant_value_specified?
64
+ specified?(shareable_constant_value)
65
+ end
66
+
56
67
  # Expose the `frozen_string_literal` value coerced to a boolean if possible.
57
68
  #
58
69
  # @return [Boolean] if value is `true` or `false`
@@ -69,6 +80,13 @@ module RuboCop
69
80
  end
70
81
  end
71
82
 
83
+ # Expose the `shareable_constant_value` value coerced to a boolean if possible.
84
+ #
85
+ # @return [String] for shareable_constant_value config
86
+ def shareable_constant_value
87
+ extract_shareable_constant_value
88
+ end
89
+
72
90
  def encoding_specified?
73
91
  specified?(encoding)
74
92
  end
@@ -146,6 +164,10 @@ module RuboCop
146
164
  def extract_frozen_string_literal
147
165
  match('frozen[_-]string[_-]literal')
148
166
  end
167
+
168
+ def extract_shareable_constant_value
169
+ match('shareable[_-]constant[_-]values')
170
+ end
149
171
  end
150
172
 
151
173
  # Wrapper for Vim style magic comments.
@@ -176,6 +198,9 @@ module RuboCop
176
198
 
177
199
  # Vim comments cannot specify frozen string literal behavior.
178
200
  def frozen_string_literal; end
201
+
202
+ # Vim comments cannot specify shareable constant values behavior.
203
+ def shareable_constant_value; end
179
204
  end
180
205
 
181
206
  # Wrapper for regular magic comments not bound to an editor.
@@ -209,6 +234,10 @@ module RuboCop
209
234
  def extract_frozen_string_literal
210
235
  extract(/\A\s*#\s*frozen[_-]string[_-]literal:\s*(#{TOKEN})\s*\z/io)
211
236
  end
237
+
238
+ def extract_shareable_constant_value
239
+ extract(/\A\s*#\s*shareable[_-]constant[_-]value:\s*(#{TOKEN})\s*\z/io)
240
+ end
212
241
  end
213
242
  end
214
243
  end
@@ -470,7 +470,7 @@ module RuboCop
470
470
  'This option applies to the previously',
471
471
  'specified --format, or the default format',
472
472
  'if no format is specified.'],
473
- fail_level: ['Minimum severity (A/R/C/W/E/F) for exit',
473
+ fail_level: ['Minimum severity (A/I/R/C/W/E/F) for exit',
474
474
  'with error code.'],
475
475
  display_time: 'Display elapsed time in seconds.',
476
476
  display_only_failed: ['Only output offense messages. Omit passing',
@@ -111,9 +111,12 @@ module RuboCop
111
111
  source
112
112
  end
113
113
 
114
- def expect_offense(source, file = nil, severity: nil, **replacements)
114
+ def expect_offense(source, file = nil, severity: nil, chomp: false, **replacements)
115
115
  expected_annotations = parse_annotations(source, **replacements)
116
- @processed_source = parse_processed_source(expected_annotations.plain_source, file)
116
+ source = expected_annotations.plain_source
117
+ source = source.chomp if chomp
118
+
119
+ @processed_source = parse_processed_source(source, file)
117
120
  @offenses = _investigate(cop, @processed_source)
118
121
  actual_annotations =
119
122
  expected_annotations.with_offense_annotations(@offenses)
@@ -390,6 +390,7 @@ module RuboCop
390
390
 
391
391
  def minimum_severity_to_fail
392
392
  @minimum_severity_to_fail ||= begin
393
+ # Unless given explicitly as `fail_level`, `:info` severity offenses do not fail
393
394
  name = @options[:fail_level] || :refactor
394
395
  RuboCop::Cop::Severity.new(name)
395
396
  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)