rubocop 1.31.0 → 1.32.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +23 -0
  4. data/lib/rubocop/cli.rb +1 -0
  5. data/lib/rubocop/config.rb +1 -1
  6. data/lib/rubocop/config_loader_resolver.rb +1 -1
  7. data/lib/rubocop/cop/base.rb +1 -1
  8. data/lib/rubocop/cop/generator.rb +4 -0
  9. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +60 -0
  10. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  11. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +4 -3
  12. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +3 -3
  13. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +57 -13
  14. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +10 -0
  15. data/lib/rubocop/cop/layout/line_length.rb +2 -0
  16. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +4 -1
  17. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +45 -0
  18. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +7 -0
  19. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -4
  20. data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
  21. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +60 -24
  22. data/lib/rubocop/cop/lint/number_conversion.rb +7 -1
  23. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -0
  24. data/lib/rubocop/cop/lint/require_range_parentheses.rb +57 -0
  25. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +35 -1
  26. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  27. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  28. data/lib/rubocop/cop/mixin/check_line_breakable.rb +4 -0
  29. data/lib/rubocop/cop/mixin/def_node.rb +2 -7
  30. data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +12 -14
  31. data/lib/rubocop/cop/mixin/percent_array.rb +60 -1
  32. data/lib/rubocop/cop/naming/predicate_name.rb +8 -0
  33. data/lib/rubocop/cop/style/class_equality_comparison.rb +22 -0
  34. data/lib/rubocop/cop/style/empty_else.rb +37 -0
  35. data/lib/rubocop/cop/style/empty_heredoc.rb +59 -0
  36. data/lib/rubocop/cop/style/fetch_env_var.rb +10 -177
  37. data/lib/rubocop/cop/style/format_string_token.rb +6 -0
  38. data/lib/rubocop/cop/style/hash_except.rb +1 -1
  39. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +2 -0
  40. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +12 -0
  41. data/lib/rubocop/cop/style/module_function.rb +2 -2
  42. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +9 -0
  43. data/lib/rubocop/cop/style/numeric_predicate.rb +20 -6
  44. data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
  45. data/lib/rubocop/cop/style/semicolon.rb +27 -3
  46. data/lib/rubocop/cop/style/symbol_array.rb +2 -3
  47. data/lib/rubocop/cop/style/symbol_proc.rb +9 -1
  48. data/lib/rubocop/cop/style/trivial_accessors.rb +3 -0
  49. data/lib/rubocop/cop/style/word_array.rb +2 -3
  50. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  51. data/lib/rubocop/formatter.rb +21 -21
  52. data/lib/rubocop/options.rb +3 -6
  53. data/lib/rubocop/rake_task.rb +5 -1
  54. data/lib/rubocop/rspec/shared_contexts.rb +14 -14
  55. data/lib/rubocop/rspec/support.rb +14 -0
  56. data/lib/rubocop/runner.rb +4 -0
  57. data/lib/rubocop/server/client_command/base.rb +1 -1
  58. data/lib/rubocop/version.rb +1 -1
  59. data/lib/rubocop.rb +4 -1
  60. metadata +23 -5
@@ -25,30 +25,37 @@ module RuboCop
25
25
  # to be strictly equivalent to that before the replacement.
26
26
  #
27
27
  # @example
28
- # # bad
29
- # unless FileTest.exist?(path)
30
- # FileUtils.makedirs(path)
28
+ # # bad - race condition with another process may result in an error in `mkdir`
29
+ # unless Dir.exist?(path)
30
+ # FileUtils.mkdir(path)
31
31
  # end
32
32
  #
33
- # if FileTest.exist?(path)
33
+ # # good - atomic and idempotent creation
34
+ # FileUtils.mkdir_p(path)
35
+ #
36
+ # # bad - race condition with another process may result in an error in `remove`
37
+ # if File.exist?(path)
34
38
  # FileUtils.remove(path)
35
39
  # end
36
40
  #
37
- # # good
38
- # FileUtils.mkdir_p(path)
39
- #
40
- # FileUtils.rm_rf(path)
41
+ # # good - atomic and idempotent removal
42
+ # FileUtils.rm_f(path)
41
43
  #
42
44
  class NonAtomicFileOperation < Base
43
45
  extend AutoCorrector
44
46
  include Alignment
45
47
  include RangeHelp
46
48
 
47
- MSG = 'Remove unnecessary existence checks `%<receiver>s.%<method_name>s`.'
48
- MAKE_METHODS = %i[makedirs mkdir mkdir_p mkpath].freeze
49
- REMOVE_METHODS = %i[remove remove_dir remove_entry remove_entry_secure delete unlink
50
- remove_file rm rm_f rm_r rm_rf rmdir rmtree safe_unlink].freeze
51
- RESTRICT_ON_SEND = (MAKE_METHODS + REMOVE_METHODS).freeze
49
+ MSG_REMOVE_FILE_EXIST_CHECK = 'Remove unnecessary existence check ' \
50
+ '`%<receiver>s.%<method_name>s`.'
51
+ MSG_CHANGE_FORCE_METHOD = 'Use atomic file operation method `FileUtils.%<method_name>s`.'
52
+ MAKE_FORCE_METHODS = %i[makedirs mkdir_p mkpath].freeze
53
+ MAKE_METHODS = %i[mkdir].freeze
54
+ REMOVE_FORCE_METHODS = %i[rm_f rm_rf].freeze
55
+ REMOVE_METHODS = %i[remove remove_dir remove_entry remove_entry_secure
56
+ delete unlink remove_file rm rmdir safe_unlink].freeze
57
+ RESTRICT_ON_SEND = (MAKE_METHODS + MAKE_FORCE_METHODS + REMOVE_METHODS +
58
+ REMOVE_FORCE_METHODS).freeze
52
59
 
53
60
  # @!method send_exist_node(node)
54
61
  def_node_search :send_exist_node, <<-PATTERN
@@ -71,50 +78,79 @@ module RuboCop
71
78
  PATTERN
72
79
 
73
80
  def on_send(node)
74
- return unless node.parent&.if_type?
81
+ return unless if_node_child?(node)
75
82
  return if explicit_not_force?(node)
76
83
  return unless (exist_node = send_exist_node(node.parent).first)
77
84
  return unless exist_node.first_argument == node.first_argument
78
85
 
79
- offense(node, exist_node)
86
+ register_offense(node, exist_node)
80
87
  end
81
88
 
82
89
  private
83
90
 
84
- def offense(node, exist_node)
91
+ def if_node_child?(node)
92
+ return false unless (parent = node.parent)
93
+
94
+ parent.if_type? && !allowable_use_with_if?(parent)
95
+ end
96
+
97
+ def allowable_use_with_if?(if_node)
98
+ if_node.condition.and_type? || if_node.condition.or_type? || if_node.else_branch
99
+ end
100
+
101
+ def register_offense(node, exist_node)
102
+ unless force_method?(node)
103
+ add_offense(node,
104
+ message: format(MSG_CHANGE_FORCE_METHOD,
105
+ method_name: replacement_method(node)))
106
+ end
107
+
85
108
  range = range_between(node.parent.loc.keyword.begin_pos,
86
109
  exist_node.loc.expression.end_pos)
87
-
88
- add_offense(range, message: message(exist_node)) do |corrector|
110
+ add_offense(range, message: message_remove_file_exist_check(exist_node)) do |corrector|
89
111
  autocorrect(corrector, node, range)
90
112
  end
91
113
  end
92
114
 
93
- def message(node)
115
+ def message_remove_file_exist_check(node)
94
116
  receiver, method_name = receiver_and_method_name(node)
95
- format(MSG, receiver: receiver, method_name: method_name)
117
+ format(MSG_REMOVE_FILE_EXIST_CHECK, receiver: receiver, method_name: method_name)
96
118
  end
97
119
 
98
120
  def autocorrect(corrector, node, range)
99
121
  corrector.remove(range)
122
+ autocorrect_replace_method(corrector, node)
123
+ corrector.remove(node.parent.loc.end) if node.parent.multiline?
124
+ end
125
+
126
+ def autocorrect_replace_method(corrector, node)
127
+ return if force_method?(node)
128
+
100
129
  corrector.replace(node.child_nodes.first.loc.name, 'FileUtils')
101
130
  corrector.replace(node.loc.selector, replacement_method(node))
102
- corrector.remove(node.parent.loc.end) if node.parent.multiline?
103
131
  end
104
132
 
105
133
  def replacement_method(node)
106
- return node.method_name if force_option?(node)
107
-
108
134
  if MAKE_METHODS.include?(node.method_name)
109
135
  'mkdir_p'
110
136
  elsif REMOVE_METHODS.include?(node.method_name)
111
- 'rm_rf'
137
+ 'rm_f'
138
+ else
139
+ node.method_name
112
140
  end
113
141
  end
114
142
 
143
+ def force_method?(node)
144
+ force_method_name?(node) || force_option?(node)
145
+ end
146
+
115
147
  def force_option?(node)
116
148
  node.arguments.any? { |arg| force?(arg) }
117
149
  end
150
+
151
+ def force_method_name?(node)
152
+ (MAKE_FORCE_METHODS + REMOVE_FORCE_METHODS).include?(node.method_name)
153
+ end
118
154
  end
119
155
  end
120
156
  end
@@ -16,7 +16,8 @@ module RuboCop
16
16
  # NOTE: Some values cannot be converted properly using one of the `Kernel`
17
17
  # method (for instance, `Time` and `DateTime` values are allowed by this
18
18
  # cop by default). Similarly, Rails' duration methods do not work well
19
- # with `Integer()` and can be ignored with `IgnoredMethods`.
19
+ # with `Integer()` and can be ignored with `IgnoredMethods`. By default,
20
+ # there are no methods to ignored.
20
21
  #
21
22
  # @safety
22
23
  # Autocorrection is unsafe because it is not guaranteed that the
@@ -45,6 +46,11 @@ module RuboCop
45
46
  # foo.try { |i| Float(i) }
46
47
  # bar.send { |i| Complex(i) }
47
48
  #
49
+ # @example IgnoredMethods: [] (default)
50
+ #
51
+ # # bad
52
+ # 10.minutes.to_i
53
+ #
48
54
  # @example IgnoredMethods: [minutes]
49
55
  #
50
56
  # # good
@@ -35,6 +35,13 @@ module RuboCop
35
35
  # # good - without `&.` this will always return `true`
36
36
  # foo&.respond_to?(:to_a)
37
37
  #
38
+ # @example AllowedMethods: [foo?]
39
+ # # bad
40
+ # do_something if attrs&.foo?(:[])
41
+ #
42
+ # # good
43
+ # do_something if attrs&.bar?(:[])
44
+ #
38
45
  class RedundantSafeNavigation < Base
39
46
  include AllowedMethods
40
47
  include RangeHelp
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks that a range literal is enclosed in parentheses when the end of the range is
7
+ # at a line break.
8
+ #
9
+ # NOTE: The following is maybe intended for `(42..)`. But, compatible is `42..do_something`.
10
+ # So, this cop does not provide autocorrection because it is left to user.
11
+ #
12
+ # [source,ruby]
13
+ # ----
14
+ # case condition
15
+ # when 42..
16
+ # do_something
17
+ # end
18
+ # ----
19
+ #
20
+ # @example
21
+ #
22
+ # # bad - Represents `(1..42)`, not endless range.
23
+ # 1..
24
+ # 42
25
+ #
26
+ # # good - It's incompatible, but your intentions when using endless range may be:
27
+ # (1..)
28
+ # 42
29
+ #
30
+ # # good
31
+ # 1..42
32
+ #
33
+ # # good
34
+ # (1..42)
35
+ #
36
+ # # good
37
+ # (1..
38
+ # 42)
39
+ #
40
+ class RequireRangeParentheses < Base
41
+ MSG = 'Wrap the endless range literal `%<range>s` to avoid precedence ambiguity.'
42
+
43
+ def on_irange(node)
44
+ return if node.parent&.begin_type?
45
+ return unless node.begin && node.end
46
+ return if same_line?(node.begin, node.end)
47
+
48
+ message = format(MSG, range: "#{node.begin.source}#{node.loc.operator.source}")
49
+
50
+ add_offense(node, message: message)
51
+ end
52
+
53
+ alias on_erange on_irange
54
+ end
55
+ end
56
+ end
57
+ end
@@ -25,6 +25,7 @@ module RuboCop
25
25
  # x&.foo || bar
26
26
  class SafeNavigationChain < Base
27
27
  include NilMethods
28
+ extend AutoCorrector
28
29
  extend TargetRubyVersion
29
30
 
30
31
  minimum_target_ruby_version 2.3
@@ -48,12 +49,45 @@ module RuboCop
48
49
  Parser::Source::Range.new(node.source_range.source_buffer,
49
50
  safe_nav.source_range.end_pos,
50
51
  method_chain.source_range.end_pos)
51
- add_offense(location)
52
+ add_offense(location) do |corrector|
53
+ autocorrect(corrector, offense_range: location, send_node: method_chain)
54
+ end
52
55
  end
53
56
  end
54
57
 
55
58
  private
56
59
 
60
+ # @param [Parser::Source::Range] offense_range
61
+ # @param [RuboCop::AST::SendNode] send_node
62
+ # @return [String]
63
+ def add_safe_navigation_operator(offense_range:, send_node:)
64
+ source = \
65
+ if send_node.method?(:[]) || send_node.method?(:[]=)
66
+ format(
67
+ '%<method_name>s(%<arguments>s)',
68
+ arguments: send_node.arguments.map(&:source).join(', '),
69
+ method_name: send_node.method_name
70
+ )
71
+ else
72
+ offense_range.source.dup
73
+ end
74
+ source.prepend('.') unless send_node.dot?
75
+ source.prepend('&')
76
+ end
77
+
78
+ # @param [RuboCop::Cop::Corrector] corrector
79
+ # @param [Parser::Source::Range] offense_range
80
+ # @param [RuboCop::AST::SendNode] send_node
81
+ def autocorrect(corrector, offense_range:, send_node:)
82
+ corrector.replace(
83
+ offense_range,
84
+ add_safe_navigation_operator(
85
+ offense_range: offense_range,
86
+ send_node: send_node
87
+ )
88
+ )
89
+ end
90
+
57
91
  def method_chain(node)
58
92
  chain = node
59
93
  chain = chain.parent if chain.send_type? && chain.parent&.call_type?
@@ -15,6 +15,7 @@ module RuboCop
15
15
  #
16
16
  # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
17
17
  # for backwards compatibility. Please use `IgnoredMethods` instead.
18
+ # By default, there are no methods to ignored.
18
19
  #
19
20
  # @example CountAsOne: ['array', 'heredoc']
20
21
  #
@@ -13,6 +13,7 @@ module RuboCop
13
13
  #
14
14
  # NOTE: The `ExcludedMethods` configuration is deprecated and only kept
15
15
  # for backwards compatibility. Please use `IgnoredMethods` instead.
16
+ # By default, there are no methods to ignored.
16
17
  #
17
18
  # @example CountAsOne: ['array', 'heredoc']
18
19
  #
@@ -46,6 +46,8 @@ module RuboCop
46
46
  if node.send_type?
47
47
  args = process_args(node.arguments)
48
48
  return extract_breakable_node_from_elements(node, args, max)
49
+ elsif node.def_type?
50
+ return extract_breakable_node_from_elements(node, node.arguments, max)
49
51
  elsif node.array_type? || node.hash_type?
50
52
  return extract_breakable_node_from_elements(node, node.children, max)
51
53
  end
@@ -216,6 +218,8 @@ module RuboCop
216
218
 
217
219
  # @api private
218
220
  def already_on_multiple_lines?(node)
221
+ return node.first_line != node.arguments.last.last_line if node.def_type?
222
+
219
223
  node.first_line != node.last_line
220
224
  end
221
225
  end
@@ -5,8 +5,7 @@ module RuboCop
5
5
  # Common functionality for checking def nodes.
6
6
  module DefNode
7
7
  extend NodePattern::Macros
8
-
9
- NON_PUBLIC_MODIFIERS = %w[private protected].freeze
8
+ include VisibilityHelp
10
9
 
11
10
  private
12
11
 
@@ -15,11 +14,7 @@ module RuboCop
15
14
  end
16
15
 
17
16
  def preceding_non_public_modifier?(node)
18
- stripped_source_upto(node.first_line).any? { |line| NON_PUBLIC_MODIFIERS.include?(line) }
19
- end
20
-
21
- def stripped_source_upto(index)
22
- processed_source[0..index].map(&:strip)
17
+ node_visibility(node) != :public
23
18
  end
24
19
 
25
20
  # @!method non_public_modifier?(node)
@@ -26,7 +26,7 @@ module RuboCop
26
26
  def check_first(first, left_brace, left_parenthesis, offset)
27
27
  actual_column = first.source_range.column
28
28
 
29
- indent_base_column, indent_base_type = indent_base(left_brace, left_parenthesis)
29
+ indent_base_column, indent_base_type = indent_base(left_brace, first, left_parenthesis)
30
30
  expected_column = indent_base_column + configured_indentation_width + offset
31
31
 
32
32
  @column_delta = expected_column - actual_column
@@ -47,11 +47,12 @@ module RuboCop
47
47
  end
48
48
  end
49
49
 
50
- def indent_base(left_brace, left_parenthesis)
50
+ def indent_base(left_brace, first, left_parenthesis)
51
51
  return [left_brace.column, :left_brace_or_bracket] if style == brace_alignment_style
52
52
 
53
- pair = hash_pair_where_value_beginning_with(left_brace)
54
- if pair && key_and_value_begin_on_same_line?(pair) && pair.right_sibling
53
+ pair = hash_pair_where_value_beginning_with(left_brace, first)
54
+ if pair && key_and_value_begin_on_same_line?(pair) &&
55
+ right_sibling_begins_on_subsequent_line?(pair)
55
56
  return [pair.loc.column, :parent_hash_key]
56
57
  end
57
58
 
@@ -62,23 +63,20 @@ module RuboCop
62
63
  [left_brace.source_line =~ /\S/, :start_of_line]
63
64
  end
64
65
 
65
- def hash_pair_where_value_beginning_with(left_brace)
66
- node = node_beginning_with(left_brace)
67
- node.parent&.pair_type? ? node.parent : nil
68
- end
66
+ def hash_pair_where_value_beginning_with(left_brace, first)
67
+ return unless first && first.parent.loc.begin == left_brace
69
68
 
70
- def node_beginning_with(left_brace)
71
- processed_source.ast.each_descendant do |node|
72
- if node.loc.is_a?(Parser::Source::Map::Collection) && (node.loc.begin == left_brace)
73
- break node
74
- end
75
- end
69
+ first.parent&.parent&.pair_type? ? first.parent.parent : nil
76
70
  end
77
71
 
78
72
  def key_and_value_begin_on_same_line?(pair)
79
73
  same_line?(pair.key, pair.value)
80
74
  end
81
75
 
76
+ def right_sibling_begins_on_subsequent_line?(pair)
77
+ pair.right_sibling && (pair.last_line < pair.right_sibling.first_line)
78
+ end
79
+
82
80
  def detected_styles(actual_column, offset, left_parenthesis, left_brace)
83
81
  base_column = actual_column - configured_indentation_width - offset
84
82
  detected_styles_for_column(base_column, left_parenthesis, left_brace)
@@ -44,13 +44,26 @@ module RuboCop
44
44
  no_acceptable_style! if brackets_required
45
45
 
46
46
  bracketed_array = build_bracketed_array(node)
47
- message = format(self.class::ARRAY_MSG, prefer: bracketed_array)
47
+ message = build_message_for_bracketed_array(bracketed_array)
48
48
 
49
49
  add_offense(node, message: message) do |corrector|
50
50
  corrector.replace(node, bracketed_array)
51
51
  end
52
52
  end
53
53
 
54
+ # @param [String] preferred_array_code
55
+ # @return [String]
56
+ def build_message_for_bracketed_array(preferred_array_code)
57
+ format(
58
+ self.class::ARRAY_MSG,
59
+ prefer: if preferred_array_code.include?("\n")
60
+ 'an array literal `[...]`'
61
+ else
62
+ "`#{preferred_array_code}`"
63
+ end
64
+ )
65
+ end
66
+
54
67
  def check_bracketed_array(node, literal_prefix)
55
68
  return if allowed_bracket_array?(node)
56
69
 
@@ -63,6 +76,52 @@ module RuboCop
63
76
  percent_literal_corrector.correct(corrector, node, literal_prefix)
64
77
  end
65
78
  end
79
+
80
+ # @param [RuboCop::AST::ArrayNode] node
81
+ # @param [Array<String>] elements
82
+ # @return [String]
83
+ def build_bracketed_array_with_appropriate_whitespace(elements:, node:)
84
+ [
85
+ '[',
86
+ whitespace_leading(node),
87
+ elements.join(",#{whitespace_between(node)}"),
88
+ whitespace_trailing(node),
89
+ ']'
90
+ ].join
91
+ end
92
+
93
+ # Provides whitespace between elements for building a bracketed array.
94
+ # %w[ a b c ]
95
+ # ^^^
96
+ # @param [RuboCop::AST::ArrayNode] node
97
+ # @return [String]
98
+ def whitespace_between(node)
99
+ if node.children.length >= 2
100
+ node.source[
101
+ node.children[0].loc.expression.end_pos...node.children[1].loc.expression.begin_pos
102
+ ]
103
+ else
104
+ ' '
105
+ end
106
+ end
107
+
108
+ # Provides leading whitespace for building a bracketed array.
109
+ # %w[ a b c ]
110
+ # ^^
111
+ # @param [RuboCop::AST::ArrayNode] node
112
+ # @return [String]
113
+ def whitespace_leading(node)
114
+ node.source[node.loc.begin.end_pos...node.children[0].loc.expression.begin_pos]
115
+ end
116
+
117
+ # Provides trailing whitespace for building a bracketed array.
118
+ # %w[ a b c ]
119
+ # ^^^^
120
+ # @param [RuboCop::AST::ArrayNode] node
121
+ # @return [String]
122
+ def whitespace_trailing(node)
123
+ node.source[node.children[-1].loc.expression.end_pos...node.loc.end.begin_pos]
124
+ end
66
125
  end
67
126
  end
68
127
  end
@@ -4,6 +4,8 @@ module RuboCop
4
4
  module Cop
5
5
  module Naming
6
6
  # Makes sure that predicates are named properly.
7
+ # `is_a?` method is allowed by default.
8
+ # These are customizable with `AllowedMethods` option.
7
9
  #
8
10
  # @example
9
11
  # # bad
@@ -27,6 +29,12 @@ module RuboCop
27
29
  # # good
28
30
  # def value?
29
31
  # end
32
+ #
33
+ # @example AllowedMethods: ['is_a?'] (default)
34
+ # # good
35
+ # def is_a?(value)
36
+ # end
37
+ #
30
38
  class PredicateName < Base
31
39
  include AllowedMethods
32
40
 
@@ -5,6 +5,8 @@ module RuboCop
5
5
  module Style
6
6
  # Enforces the use of `Object#instance_of?` instead of class comparison
7
7
  # for equality.
8
+ # `==`, `equal?`, and `eql?` methods are ignored by default.
9
+ # These are customizable with `IgnoredMethods` option.
8
10
  #
9
11
  # @example
10
12
  # # bad
@@ -16,6 +18,26 @@ module RuboCop
16
18
  # # good
17
19
  # var.instance_of?(Date)
18
20
  #
21
+ # @example IgnoreMethods: [] (default)
22
+ # # good
23
+ # var.instance_of?(Date)
24
+ #
25
+ # # bad
26
+ # var.class == Date
27
+ # var.class.equal?(Date)
28
+ # var.class.eql?(Date)
29
+ # var.class.name == 'Date'
30
+ #
31
+ # @example IgnoreMethods: [`==`]
32
+ # # good
33
+ # var.instance_of?(Date)
34
+ # var.class == Date
35
+ # var.class.name == 'Date'
36
+ #
37
+ # # bad
38
+ # var.class.equal?(Date)
39
+ # var.class.eql?(Date)
40
+ #
19
41
  class ClassEqualityComparison < Base
20
42
  include RangeHelp
21
43
  include IgnoredMethods
@@ -89,6 +89,41 @@ module RuboCop
89
89
  # if condition
90
90
  # statement
91
91
  # end
92
+ #
93
+ # @example AllowComments: false (default)
94
+ #
95
+ # # bad
96
+ # if condition
97
+ # statement
98
+ # else
99
+ # # something comment
100
+ # nil
101
+ # end
102
+ #
103
+ # # bad
104
+ # if condition
105
+ # statement
106
+ # else
107
+ # # something comment
108
+ # end
109
+ #
110
+ # @example AllowComments: true
111
+ #
112
+ # # good
113
+ # if condition
114
+ # statement
115
+ # else
116
+ # # something comment
117
+ # nil
118
+ # end
119
+ #
120
+ # # good
121
+ # if condition
122
+ # statement
123
+ # else
124
+ # # something comment
125
+ # end
126
+ #
92
127
  class EmptyElse < Base
93
128
  include OnNormalIfUnless
94
129
  include ConfigurableEnforcedStyle
@@ -108,6 +143,8 @@ module RuboCop
108
143
  private
109
144
 
110
145
  def check(node)
146
+ return if cop_config['AllowComments'] && comment_in_else?(node.loc)
147
+
111
148
  empty_check(node) if empty_style?
112
149
  nil_check(node) if nil_style?
113
150
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for using empty heredoc to reduce redundancy.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # <<~EOS
12
+ # EOS
13
+ #
14
+ # <<-EOS
15
+ # EOS
16
+ #
17
+ # <<EOS
18
+ # EOS
19
+ #
20
+ # # good
21
+ # ''
22
+ #
23
+ # # bad
24
+ # do_something(<<~EOS)
25
+ # EOS
26
+ #
27
+ # do_something(<<-EOS)
28
+ # EOS
29
+ #
30
+ # do_something(<<EOS)
31
+ # EOS
32
+ #
33
+ # # good
34
+ # do_something('')
35
+ #
36
+ class EmptyHeredoc < Base
37
+ include Heredoc
38
+ include RangeHelp
39
+ extend AutoCorrector
40
+
41
+ MSG = 'Use an empty string literal instead of heredoc.'
42
+
43
+ def on_heredoc(node)
44
+ heredoc_body = node.loc.heredoc_body
45
+
46
+ return unless heredoc_body.source.empty?
47
+
48
+ add_offense(node) do |corrector|
49
+ heredoc_end = node.loc.heredoc_end
50
+
51
+ corrector.replace(node, "''")
52
+ corrector.remove(range_by_whole_lines(heredoc_body, include_final_newline: true))
53
+ corrector.remove(range_by_whole_lines(heredoc_end, include_final_newline: true))
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end