rubocop 0.40.0 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1014
  3. data/config/default.yml +61 -5
  4. data/config/disabled.yml +6 -0
  5. data/config/enabled.yml +63 -4
  6. data/lib/rubocop.rb +17 -1
  7. data/lib/rubocop/ast_node.rb +56 -42
  8. data/lib/rubocop/ast_node/traversal.rb +3 -3
  9. data/lib/rubocop/cli.rb +14 -9
  10. data/lib/rubocop/comment_config.rb +85 -32
  11. data/lib/rubocop/config.rb +29 -8
  12. data/lib/rubocop/config_loader.rb +1 -1
  13. data/lib/rubocop/cop/cop.rb +1 -1
  14. data/lib/rubocop/cop/corrector.rb +13 -0
  15. data/lib/rubocop/cop/lint/block_alignment.rb +25 -11
  16. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +5 -2
  17. data/lib/rubocop/cop/lint/inherit_exception.rb +69 -0
  18. data/lib/rubocop/cop/lint/percent_string_array.rb +60 -0
  19. data/lib/rubocop/cop/lint/percent_symbol_array.rb +57 -0
  20. data/lib/rubocop/cop/lint/shadowed_exception.rb +95 -0
  21. data/lib/rubocop/cop/lint/useless_access_modifier.rb +28 -13
  22. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +25 -19
  23. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +16 -8
  24. data/lib/rubocop/cop/mixin/if_node.rb +1 -2
  25. data/lib/rubocop/cop/mixin/integer_node.rb +13 -0
  26. data/lib/rubocop/cop/mixin/match_range.rb +26 -0
  27. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +16 -7
  28. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +18 -1
  29. data/lib/rubocop/cop/mixin/negative_conditional.rb +6 -4
  30. data/lib/rubocop/cop/mixin/percent_literal.rb +10 -0
  31. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +24 -6
  32. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +20 -7
  33. data/lib/rubocop/cop/mixin/string_literals_help.rb +2 -2
  34. data/lib/rubocop/cop/mixin/trailing_comma.rb +34 -20
  35. data/lib/rubocop/cop/performance/flat_map.rb +23 -10
  36. data/lib/rubocop/cop/performance/push_splat.rb +47 -0
  37. data/lib/rubocop/cop/performance/redundant_block_call.rb +24 -1
  38. data/lib/rubocop/cop/performance/redundant_merge.rb +3 -5
  39. data/lib/rubocop/cop/performance/sample.rb +15 -11
  40. data/lib/rubocop/cop/rails/exit.rb +62 -0
  41. data/lib/rubocop/cop/rails/output_safety.rb +45 -0
  42. data/lib/rubocop/cop/rails/pluralization_grammar.rb +12 -4
  43. data/lib/rubocop/cop/rails/request_referer.rb +40 -0
  44. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +63 -28
  45. data/lib/rubocop/cop/rails/validation.rb +37 -23
  46. data/lib/rubocop/cop/style/alias.rb +10 -6
  47. data/lib/rubocop/cop/style/bare_percent_literals.rb +18 -7
  48. data/lib/rubocop/cop/style/block_delimiters.rb +15 -22
  49. data/lib/rubocop/cop/style/closing_parenthesis_indentation.rb +19 -8
  50. data/lib/rubocop/cop/style/comment_indentation.rb +13 -5
  51. data/lib/rubocop/cop/style/conditional_assignment.rb +111 -59
  52. data/lib/rubocop/cop/style/documentation.rb +7 -1
  53. data/lib/rubocop/cop/style/each_for_simple_loop.rb +43 -0
  54. data/lib/rubocop/cop/style/each_with_object.rb +25 -14
  55. data/lib/rubocop/cop/style/empty_else.rb +6 -10
  56. data/lib/rubocop/cop/style/extra_spacing.rb +20 -3
  57. data/lib/rubocop/cop/style/file_name.rb +16 -4
  58. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  59. data/lib/rubocop/cop/style/hash_syntax.rb +9 -2
  60. data/lib/rubocop/cop/style/if_unless_modifier.rb +20 -13
  61. data/lib/rubocop/cop/style/implicit_runtime_error.rb +32 -0
  62. data/lib/rubocop/cop/style/infinite_loop.rb +42 -5
  63. data/lib/rubocop/cop/style/lambda.rb +22 -0
  64. data/lib/rubocop/cop/style/method_def_parentheses.rb +12 -4
  65. data/lib/rubocop/cop/style/module_function.rb +28 -6
  66. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +49 -12
  67. data/lib/rubocop/cop/style/mutable_constant.rb +8 -1
  68. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  69. data/lib/rubocop/cop/style/next.rb +43 -31
  70. data/lib/rubocop/cop/style/not.rb +33 -13
  71. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +92 -0
  72. data/lib/rubocop/cop/style/numeric_literals.rb +1 -4
  73. data/lib/rubocop/cop/style/parallel_assignment.rb +26 -8
  74. data/lib/rubocop/cop/style/{deprecated_hash_methods.rb → preferred_hash_methods.rb} +8 -8
  75. data/lib/rubocop/cop/style/redundant_parentheses.rb +29 -19
  76. data/lib/rubocop/cop/style/redundant_self.rb +13 -6
  77. data/lib/rubocop/cop/style/space_after_not.rb +7 -5
  78. data/lib/rubocop/cop/style/space_around_keyword.rb +6 -0
  79. data/lib/rubocop/cop/style/space_around_operators.rb +5 -1
  80. data/lib/rubocop/cop/style/space_before_first_arg.rb +21 -9
  81. data/lib/rubocop/cop/style/space_inside_array_percent_literal.rb +53 -0
  82. data/lib/rubocop/cop/style/space_inside_block_braces.rb +2 -2
  83. data/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb +26 -6
  84. data/lib/rubocop/cop/style/space_inside_percent_literal_delimiters.rb +64 -0
  85. data/lib/rubocop/cop/style/string_literals.rb +37 -8
  86. data/lib/rubocop/cop/style/symbol_array.rb +21 -12
  87. data/lib/rubocop/cop/style/symbol_proc.rb +26 -19
  88. data/lib/rubocop/cop/style/word_array.rb +1 -5
  89. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -6
  90. data/lib/rubocop/cop/team.rb +40 -27
  91. data/lib/rubocop/cop/util.rb +13 -42
  92. data/lib/rubocop/formatter/disabled_config_formatter.rb +37 -14
  93. data/lib/rubocop/formatter/html_formatter.rb +3 -7
  94. data/lib/rubocop/result_cache.rb +18 -4
  95. data/{spec/support → lib/rubocop/rspec}/cop_helper.rb +3 -0
  96. data/lib/rubocop/rspec/host_environment_simulation_helper.rb +33 -0
  97. data/lib/rubocop/rspec/shared_contexts.rb +75 -0
  98. data/lib/rubocop/rspec/shared_examples.rb +101 -0
  99. data/lib/rubocop/rspec/support.rb +9 -0
  100. data/lib/rubocop/runner.rb +2 -2
  101. data/lib/rubocop/string_interpreter.rb +58 -0
  102. data/lib/rubocop/version.rb +1 -1
  103. metadata +27 -7
@@ -20,13 +20,13 @@ module RuboCop
20
20
  :arg, :restarg, :blockarg, :shadowarg,
21
21
  :kwrestarg, :zsuper, :lambda, :redo, :retry].freeze
22
22
  ONE_CHILD_NODE = [:splat, :kwsplat, :block_pass, :not, :break, :next,
23
- :return, :preexe, :postexe, :match_current_line,
24
- :defined?, :arg_expr].freeze
23
+ :preexe, :postexe, :match_current_line, :defined?,
24
+ :arg_expr].freeze
25
25
  MANY_CHILD_NODES = [:dstr, :dsym, :xstr, :regexp, :array, :hash, :pair,
26
26
  :irange, :erange, :mlhs, :masgn, :or_asgn, :and_asgn,
27
27
  :undef, :alias, :args, :super, :yield, :or, :and,
28
28
  :while_post, :until_post, :iflipflop, :eflipflop,
29
- :match_with_lvasgn, :begin, :kwbegin].freeze
29
+ :match_with_lvasgn, :begin, :kwbegin, :return].freeze
30
30
  SECOND_CHILD_ONLY = [:lvasgn, :ivasgn, :cvasgn, :gvasgn, :optarg, :kwarg,
31
31
  :kwoptarg].freeze
32
32
 
@@ -7,7 +7,7 @@ module RuboCop
7
7
  class CLI
8
8
  include Formatter::TextUtil
9
9
 
10
- class Finished < Exception; end
10
+ class Finished < RuntimeError; end
11
11
 
12
12
  attr_reader :options, :config_store
13
13
 
@@ -25,14 +25,7 @@ module RuboCop
25
25
  act_on_options
26
26
  apply_default_formatter
27
27
 
28
- runner = Runner.new(@options, @config_store)
29
- trap_interrupt(runner)
30
- all_passed = runner.run(paths)
31
- display_warning_summary(runner.warnings)
32
- display_error_summary(runner.errors)
33
- maybe_print_corrected_source
34
-
35
- all_passed && !runner.aborting? && runner.errors.empty? ? 0 : 1
28
+ execute_runner(paths)
36
29
  rescue RuboCop::Error => e
37
30
  $stderr.puts Rainbow("Error: #{e.message}").red
38
31
  return 2
@@ -72,6 +65,18 @@ module RuboCop
72
65
  end
73
66
  end
74
67
 
68
+ def execute_runner(paths)
69
+ runner = Runner.new(@options, @config_store)
70
+
71
+ trap_interrupt(runner)
72
+ all_passed = runner.run(paths)
73
+ display_warning_summary(runner.warnings)
74
+ display_error_summary(runner.errors)
75
+ maybe_print_corrected_source
76
+
77
+ all_passed && !runner.aborting? && runner.errors.empty? ? 0 : 1
78
+ end
79
+
75
80
  def handle_exiting_options
76
81
  return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }
77
82
 
@@ -6,10 +6,17 @@ module RuboCop
6
6
  # and provides a way to check if each cop is enabled at arbitrary line.
7
7
  class CommentConfig
8
8
  UNNEEDED_DISABLE = 'Lint/UnneededDisable'.freeze
9
+
10
+ COP_NAME_PATTERN = '([A-Z][a-z]+/)?(?:[A-Z][a-z]+)+'.freeze
11
+ COP_NAMES_PATTERN = "(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}".freeze
12
+ COPS_PATTERN = "(all|#{COP_NAMES_PATTERN})".freeze
13
+
9
14
  COMMENT_DIRECTIVE_REGEXP = Regexp.new(
10
- '\A# rubocop : ((?:dis|en)able)\b ((?:[\w/]+,? )+)'.gsub(' ', '\s*')
15
+ ('\A# rubocop : ((?:dis|en)able)\b ' + COPS_PATTERN).gsub(' ', '\s*')
11
16
  )
12
17
 
18
+ CopAnalysis = Struct.new(:line_ranges, :start_line_number)
19
+
13
20
  attr_reader :processed_source
14
21
 
15
22
  def initialize(processed_source)
@@ -19,6 +26,8 @@ module RuboCop
19
26
  def cop_enabled_at_line?(cop, line_number)
20
27
  cop = cop.cop_name if cop.respond_to?(:cop_name)
21
28
  disabled_line_ranges = cop_disabled_line_ranges[cop]
29
+ return true unless disabled_line_ranges
30
+
22
31
  disabled_line_ranges.none? { |range| range.include?(line_number) }
23
32
  end
24
33
 
@@ -29,57 +38,101 @@ module RuboCop
29
38
  private
30
39
 
31
40
  def analyze
32
- disabled_line_ranges = Hash.new { |hash, key| hash[key] = [] }
33
- disablement_start_line_numbers = {}
41
+ analyses = Hash.new { |hash, key| hash[key] = CopAnalysis.new([], nil) }
34
42
 
35
43
  each_mentioned_cop do |cop_name, disabled, line, single_line|
36
- if single_line
37
- disabled_line_ranges[cop_name] << (line..line) if disabled
38
- elsif disabled
39
- if disablement_start_line_numbers[cop_name]
40
- # Cop already disabled on this line, so we end the current disabled
41
- # range before we start a new range.
42
- start_line = disablement_start_line_numbers.delete(cop_name)
43
- disabled_line_ranges[cop_name] << (start_line..line)
44
- end
45
- disablement_start_line_numbers[cop_name] = line
46
- else
47
- start_line = disablement_start_line_numbers.delete(cop_name)
48
- disabled_line_ranges[cop_name] << (start_line..line) if start_line
49
- end
44
+ analyses[cop_name] =
45
+ analyze_cop(analyses[cop_name], disabled, line, single_line)
50
46
  end
51
47
 
52
- disablement_start_line_numbers.each do |cop_name, start_line|
53
- disabled_line_ranges[cop_name] << (start_line..Float::INFINITY)
48
+ analyses.each_with_object({}) do |element, hash|
49
+ cop_name, analysis = *element
50
+ hash[cop_name] = cop_line_ranges(analysis)
54
51
  end
52
+ end
55
53
 
56
- disabled_line_ranges
54
+ def analyze_cop(analysis, disabled, line, single_line)
55
+ if single_line
56
+ analyze_single_line(analysis, line, disabled)
57
+ elsif disabled
58
+ analyze_disabled(analysis, line)
59
+ else
60
+ analyze_rest(analysis, line)
61
+ end
57
62
  end
58
63
 
59
- def each_mentioned_cop
60
- return if processed_source.comments.nil?
64
+ def analyze_single_line(analysis, line, disabled)
65
+ return analysis unless disabled
61
66
 
62
- processed_source.comments.each do |comment|
63
- match = comment.text.match(COMMENT_DIRECTIVE_REGEXP)
64
- next unless match
67
+ CopAnalysis.new(analysis.line_ranges + [(line..line)],
68
+ analysis.start_line_number)
69
+ end
70
+
71
+ def analyze_disabled(analysis, line)
72
+ if (start_line = analysis.start_line_number)
73
+ # Cop already disabled on this line, so we end the current disabled
74
+ # range before we start a new range.
75
+ return CopAnalysis.new(analysis.line_ranges + [start_line..line], line)
76
+ end
77
+
78
+ CopAnalysis.new(analysis.line_ranges, line)
79
+ end
80
+
81
+ def analyze_rest(analysis, line)
82
+ if (start_line = analysis.start_line_number)
83
+ return CopAnalysis.new(analysis.line_ranges + [start_line..line], nil)
84
+ end
85
+
86
+ CopAnalysis.new(analysis.line_ranges, nil)
87
+ end
65
88
 
66
- switch, cops_string = match.captures
89
+ def cop_line_ranges(analysis)
90
+ return analysis.line_ranges unless analysis.start_line_number
67
91
 
68
- cop_names =
69
- cops_string == 'all' ? all_cop_names : cops_string.split(/,\s*/)
92
+ analysis.line_ranges + [(analysis.start_line_number..Float::INFINITY)]
93
+ end
70
94
 
71
- disabled = (switch == 'disable')
95
+ def each_mentioned_cop
96
+ each_directive do |comment, cop_names, disabled|
72
97
  comment_line_number = comment.loc.expression.line
73
98
  single_line = !comment_only_line?(comment_line_number)
74
99
 
75
100
  cop_names.each do |cop_name|
76
- cop_name = Cop::Cop.qualified_cop_name(cop_name.strip,
77
- processed_source.buffer.name)
78
- yield cop_name, disabled, comment_line_number, single_line
101
+ yield qualified_cop_name(cop_name), disabled, comment_line_number,
102
+ single_line
79
103
  end
80
104
  end
81
105
  end
82
106
 
107
+ def each_directive
108
+ return if processed_source.comments.nil?
109
+
110
+ processed_source.comments.each do |comment|
111
+ directive = directive_parts(comment)
112
+ next unless directive
113
+
114
+ yield comment, *directive
115
+ end
116
+ end
117
+
118
+ def directive_parts(comment)
119
+ match = comment.text.match(COMMENT_DIRECTIVE_REGEXP)
120
+ return unless match
121
+
122
+ switch, cops_string = match.captures
123
+
124
+ cop_names =
125
+ cops_string == 'all' ? all_cop_names : cops_string.split(/,\s*/)
126
+
127
+ disabled = (switch == 'disable')
128
+
129
+ [cop_names, disabled]
130
+ end
131
+
132
+ def qualified_cop_name(cop_name)
133
+ Cop::Cop.qualified_cop_name(cop_name.strip, processed_source.buffer.name)
134
+ end
135
+
83
136
  def all_cop_names
84
137
  @all_cop_names ||= Cop::Cop.all.map(&:cop_name).reject do |cop_name|
85
138
  cop_name == UNNEEDED_DISABLE
@@ -194,6 +194,19 @@ module RuboCop
194
194
  end
195
195
  end
196
196
 
197
+ def target_ruby_version
198
+ @target_ruby_version ||=
199
+ if File.file?('.ruby-version')
200
+ @target_ruby_version_source = :dot_ruby_version
201
+
202
+ File.read('.ruby-version').to_f
203
+ else
204
+ @target_ruby_version_source = :rubocop_yml
205
+
206
+ for_all_cops['TargetRubyVersion']
207
+ end
208
+ end
209
+
197
210
  private
198
211
 
199
212
  def warn_about_unrecognized_cops(invalid_cop_names)
@@ -272,14 +285,22 @@ module RuboCop
272
285
  end
273
286
 
274
287
  def check_target_ruby
275
- target = for_all_cops['TargetRubyVersion']
276
- return unless target
277
-
278
- unless KNOWN_RUBIES.include?(target)
279
- raise ValidationError, "Unknown Ruby version #{target.inspect} found " \
280
- 'in `TargetRubyVersion` parameter (in ' \
281
- "#{loaded_path}).\nKnown versions: " \
282
- "#{KNOWN_RUBIES.join(', ')}"
288
+ return unless target_ruby_version
289
+
290
+ unless KNOWN_RUBIES.include?(target_ruby_version)
291
+ msg = "Unknown Ruby version #{target_ruby_version.inspect} found "
292
+
293
+ msg +=
294
+ case @target_ruby_version_source
295
+ when :dot_ruby_version
296
+ 'in `.ruby-version`.'
297
+ when :rubocop_yml
298
+ "in `TargetRubyVersion` parameter (in #{loaded_path})." \
299
+ end
300
+
301
+ msg += "\nKnown versions: #{KNOWN_RUBIES.join(', ')}"
302
+
303
+ raise ValidationError, msg
283
304
  end
284
305
  end
285
306
  end
@@ -139,7 +139,7 @@ module RuboCop
139
139
  end
140
140
 
141
141
  def load_yaml_configuration(absolute_path)
142
- yaml_code = IO.read(absolute_path)
142
+ yaml_code = IO.read(absolute_path, encoding: 'UTF-8')
143
143
  # At one time, there was a problem with the psych YAML engine under
144
144
  # Ruby 1.9.3. YAML.load_file would crash when reading empty .yml files
145
145
  # or files that only contained comments and blank lines. This problem
@@ -193,7 +193,7 @@ module RuboCop
193
193
  end
194
194
 
195
195
  def target_ruby_version
196
- @config.for_all_cops['TargetRubyVersion']
196
+ @config.target_ruby_version
197
197
  end
198
198
 
199
199
  def parse(source, path = nil)
@@ -118,6 +118,19 @@ module RuboCop
118
118
  range.begin_pos + size)
119
119
  @source_rewriter.remove(to_remove)
120
120
  end
121
+
122
+ # Removes `size` characters from the end of the given range.
123
+ # If `size` is greater than the size of `range`, the removed region can
124
+ # overrun the beginning of `range`.
125
+ #
126
+ # @param [Parser::Source::Range] range
127
+ # @param [Integer] size
128
+ def remove_trailing(range, size)
129
+ to_remove = Parser::Source::Range.new(range.source_buffer,
130
+ range.end_pos - size,
131
+ range.end_pos)
132
+ @source_rewriter.remove(to_remove)
133
+ end
121
134
  end
122
135
  end
123
136
  end
@@ -63,15 +63,7 @@ module RuboCop
63
63
 
64
64
  def start_for_block_node(block_node)
65
65
  # Which node should we align the 'end' with?
66
- result = block_node
67
-
68
- while (parent = result.parent)
69
- break if !parent || !parent.loc
70
- break if parent.loc.line != block_node.loc.line &&
71
- !parent.masgn_type?
72
- break unless block_end_align_target?(parent, result)
73
- result = parent
74
- end
66
+ result = block_end_align_target(block_node)
75
67
 
76
68
  # In offense message, we want to show the assignment LHS rather than
77
69
  # the entire assignment
@@ -79,6 +71,17 @@ module RuboCop
79
71
  result
80
72
  end
81
73
 
74
+ def block_end_align_target(node)
75
+ while (parent = node.parent)
76
+ break if !parent || !parent.loc
77
+ break if parent.loc.line != node.loc.line && !parent.masgn_type?
78
+ break unless block_end_align_target?(parent, node)
79
+ node = parent
80
+ end
81
+
82
+ node
83
+ end
84
+
82
85
  def check_block_alignment(start_node, block_node)
83
86
  end_loc = block_node.loc.end
84
87
  return unless begins_its_line?(end_loc)
@@ -91,19 +94,30 @@ module RuboCop
91
94
  compute_do_source_line_column(block_node, end_loc)
92
95
  return unless do_source_line_column
93
96
 
97
+ offense(block_node, start_loc, end_loc, do_source_line_column)
98
+ end
99
+
100
+ def offense(block_node, start_loc, end_loc, do_source_line_column)
94
101
  error_source_line_column = if style == :start_of_block
95
102
  do_source_line_column
96
103
  else
97
104
  loc_to_source_line_column(start_loc)
98
105
  end
99
106
 
100
- fmt = format(
107
+ message = format_message(start_loc, end_loc, do_source_line_column,
108
+ error_source_line_column)
109
+
110
+ add_offense(block_node, end_loc, message)
111
+ end
112
+
113
+ def format_message(start_loc, end_loc, do_source_line_column,
114
+ error_source_line_column)
115
+ format(
101
116
  MSG,
102
117
  format_source_line_column(loc_to_source_line_column(end_loc)),
103
118
  format_source_line_column(error_source_line_column),
104
119
  alt_start_msg(start_loc, do_source_line_column)
105
120
  )
106
- add_offense(block_node, end_loc, fmt)
107
121
  end
108
122
 
109
123
  def compute_do_source_line_column(node, end_loc)
@@ -34,13 +34,12 @@ module RuboCop
34
34
 
35
35
  def offending_node?(node)
36
36
  return false unless called_on_string?(node)
37
- return false unless sprintf?(node) || format?(node) || percent?(node)
37
+ return false unless method_with_format_args?(node)
38
38
  return false if named_mode?(node) || node_with_splat_args?(node)
39
39
 
40
40
  num_of_format_args, num_of_expected_fields = count_matches(node)
41
41
 
42
42
  num_of_format_args != :unknown &&
43
- num_of_expected_fields != :unknown &&
44
43
  num_of_expected_fields != num_of_format_args
45
44
  end
46
45
 
@@ -53,6 +52,10 @@ module RuboCop
53
52
  end
54
53
  end
55
54
 
55
+ def method_with_format_args?(node)
56
+ sprintf?(node) || format?(node) || percent?(node)
57
+ end
58
+
56
59
  def named_mode?(node)
57
60
  receiver_node, _method_name, *args = *node
58
61
 
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module RuboCop
5
+ module Cop
6
+ module Lint
7
+ # This cop looks for error classes inheriting from `Exception`.
8
+ # It is configurable to suggest using either `RuntimeError` (default) or
9
+ # `StandardError` instead.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ #
15
+ # class C < Exception; end
16
+ #
17
+ # @example
18
+ #
19
+ # # EnforcedStyle: runtime_error (default)
20
+ #
21
+ # # good
22
+ #
23
+ # class C < RuntimeError; end
24
+ #
25
+ # @example
26
+ #
27
+ # # EnforcedStyle: standard_error
28
+ #
29
+ # # good
30
+ #
31
+ # class C < StandardError; end
32
+ class InheritException < Cop
33
+ include ConfigurableEnforcedStyle
34
+
35
+ MSG = 'Inherit from `%s` instead of `Exception`.'.freeze
36
+ PREFERRED_BASE_CLASS = {
37
+ runtime_error: 'RuntimeError',
38
+ standard_error: 'StandardError'
39
+ }.freeze
40
+
41
+ def on_class(node)
42
+ _class, base_class, _body = *node
43
+
44
+ return if base_class.nil?
45
+
46
+ check(base_class)
47
+ end
48
+
49
+ private
50
+
51
+ def check(node)
52
+ if node.const_name == 'Exception'
53
+ add_offense(node, :expression, format(MSG, preferred_base_class))
54
+ end
55
+ end
56
+
57
+ def autocorrect(node)
58
+ lambda do |corrector|
59
+ corrector.replace(node.loc.expression, preferred_base_class)
60
+ end
61
+ end
62
+
63
+ def preferred_base_class
64
+ PREFERRED_BASE_CLASS[style]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end