rubocop 1.66.0 → 1.67.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +15 -6
  4. data/config/internal_affairs.yml +11 -0
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
  6. data/lib/rubocop/cli/command/lsp.rb +2 -2
  7. data/lib/rubocop/comment_config.rb +4 -8
  8. data/lib/rubocop/config.rb +4 -16
  9. data/lib/rubocop/config_loader.rb +14 -8
  10. data/lib/rubocop/config_loader_resolver.rb +3 -3
  11. data/lib/rubocop/config_validator.rb +7 -10
  12. data/lib/rubocop/cop/base.rb +6 -2
  13. data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
  14. data/lib/rubocop/cop/cop.rb +8 -0
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  16. data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
  17. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
  19. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
  20. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
  21. data/lib/rubocop/cop/internal_affairs.rb +16 -0
  22. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  23. data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  25. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
  26. data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
  27. data/lib/rubocop/cop/layout/leading_comment_space.rb +28 -1
  28. data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
  29. data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
  30. data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
  31. data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
  32. data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
  33. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
  34. data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
  35. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
  36. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
  37. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
  38. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
  39. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
  40. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +105 -41
  41. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  42. data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
  43. data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
  44. data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
  45. data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
  46. data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
  47. data/lib/rubocop/cop/offense.rb +2 -2
  48. data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
  49. data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
  50. data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
  51. data/lib/rubocop/cop/style/block_delimiters.rb +14 -1
  52. data/lib/rubocop/cop/style/collection_compact.rb +10 -10
  53. data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
  54. data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
  55. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  56. data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
  57. data/lib/rubocop/cop/style/empty_else.rb +1 -0
  58. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  59. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  60. data/lib/rubocop/cop/style/guard_clause.rb +1 -1
  61. data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
  62. data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
  63. data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
  64. data/lib/rubocop/cop/style/if_with_semicolon.rb +16 -5
  65. data/lib/rubocop/cop/style/lambda.rb +1 -1
  66. data/lib/rubocop/cop/style/magic_comment_format.rb +3 -8
  67. data/lib/rubocop/cop/style/map_into_array.rb +54 -10
  68. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
  69. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  70. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  71. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  72. data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
  73. data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
  74. data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
  75. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  76. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +1 -1
  77. data/lib/rubocop/cop/style/redundant_line_continuation.rb +3 -3
  78. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  79. data/lib/rubocop/cop/style/require_order.rb +1 -1
  80. data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
  81. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
  82. data/lib/rubocop/cop/style/safe_navigation.rb +92 -50
  83. data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
  84. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  85. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  86. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  87. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  88. data/lib/rubocop/cop/team.rb +8 -1
  89. data/lib/rubocop/cop/util.rb +1 -1
  90. data/lib/rubocop/cops_documentation_generator.rb +73 -34
  91. data/lib/rubocop/file_finder.rb +9 -4
  92. data/lib/rubocop/lsp/runtime.rb +2 -0
  93. data/lib/rubocop/lsp/server.rb +0 -1
  94. data/lib/rubocop/rspec/expect_offense.rb +1 -0
  95. data/lib/rubocop/runner.rb +3 -0
  96. data/lib/rubocop/server/cache.rb +6 -1
  97. data/lib/rubocop/server/core.rb +1 -0
  98. data/lib/rubocop/target_ruby.rb +12 -12
  99. data/lib/rubocop/version.rb +3 -1
  100. data/lib/rubocop/yaml_duplication_checker.rb +20 -26
  101. data/lib/rubocop.rb +2 -0
  102. metadata +12 -10
@@ -13,8 +13,10 @@ module RuboCop
13
13
  # return value of `Enumerable#map` is an `Array`. They are not autocorrected
14
14
  # when a return value could be used because these types differ.
15
15
  #
16
- # NOTE: It only detects when the mapping destination is a local variable
17
- # initialized as an empty array and referred to only by the pushing operation.
16
+ # NOTE: It only detects when the mapping destination is either:
17
+ # * a local variable initialized as an empty array and referred to only by the
18
+ # pushing operation;
19
+ # * or, if it is the single block argument to a `[].tap` block.
18
20
  # This is because, if not, it's challenging to statically guarantee that the
19
21
  # mapping destination variable remains an empty array:
20
22
  #
@@ -42,6 +44,14 @@ module RuboCop
42
44
  # # good
43
45
  # dest = src.map { |e| e * 2 }
44
46
  #
47
+ # # bad
48
+ # [].tap do |dest|
49
+ # src.each { |e| dest << e * 2 }
50
+ # end
51
+ #
52
+ # # good
53
+ # dest = src.map { |e| e * 2 }
54
+ #
45
55
  # # good - contains another operation
46
56
  # dest = []
47
57
  # src.each { |e| dest << e * 2; puts e }
@@ -56,7 +66,7 @@ module RuboCop
56
66
  # @!method each_block_with_push?(node)
57
67
  def_node_matcher :each_block_with_push?, <<-PATTERN
58
68
  [
59
- ^({begin kwbegin} ...)
69
+ ^({begin kwbegin block} ...)
60
70
  ({block numblock} (send !{nil? self} :each) _
61
71
  (send (lvar _) {:<< :push :append} {send lvar begin}))
62
72
  ]
@@ -74,6 +84,16 @@ module RuboCop
74
84
  )
75
85
  PATTERN
76
86
 
87
+ # @!method empty_array_tap(node)
88
+ def_node_matcher :empty_array_tap, <<~PATTERN
89
+ ^^$(
90
+ block
91
+ (send (array) :tap)
92
+ (args (arg _))
93
+ ...
94
+ )
95
+ PATTERN
96
+
77
97
  # @!method lvar_ref?(node, name)
78
98
  def_node_matcher :lvar_ref?, '(lvar %1)'
79
99
 
@@ -89,9 +109,14 @@ module RuboCop
89
109
  return unless each_block_with_push?(node)
90
110
 
91
111
  dest_var = find_dest_var(node)
92
- return unless (asgn = find_closest_assignment(node, dest_var))
93
- return unless empty_array_asgn?(asgn)
94
- return unless dest_used_only_for_mapping?(node, dest_var, asgn)
112
+
113
+ if offending_empty_array_tap?(node, dest_var)
114
+ asgn = dest_var.declaration_node
115
+ else
116
+ return unless (asgn = find_closest_assignment(node, dest_var))
117
+ return unless empty_array_asgn?(asgn)
118
+ return unless dest_used_only_for_mapping?(node, dest_var, asgn)
119
+ end
95
120
 
96
121
  register_offense(node, dest_var, asgn)
97
122
  end
@@ -108,6 +133,15 @@ module RuboCop
108
133
  candidates.find { |v| v.references.any? { |n| n.node.equal?(node) } }
109
134
  end
110
135
 
136
+ def offending_empty_array_tap?(node, dest_var)
137
+ return false unless (tap_block_node = empty_array_tap(dest_var.declaration_node))
138
+
139
+ # A `tap` block only offends if the array push is the only thing in it;
140
+ # otherwise we cannot guarantee that the block variable is still an empty
141
+ # array when pushed to.
142
+ tap_block_node.body == node
143
+ end
144
+
111
145
  def find_closest_assignment(block, dest_var)
112
146
  dest_var.assignments.reverse_each.lazy.map(&:node).find do |node|
113
147
  node.source_range.end_pos < block.source_range.begin_pos
@@ -127,7 +161,13 @@ module RuboCop
127
161
  next if return_value_used?(block)
128
162
 
129
163
  corrector.replace(block.send_node.selector, new_method_name)
130
- remove_assignment(corrector, asgn)
164
+
165
+ if (tap_block_node = empty_array_tap(dest_var.declaration_node))
166
+ remove_tap(corrector, block, tap_block_node)
167
+ else
168
+ remove_assignment(corrector, asgn)
169
+ end
170
+
131
171
  correct_push_node(corrector, block.body)
132
172
  correct_return_value_handling(corrector, block, dest_var)
133
173
  end
@@ -147,10 +187,8 @@ module RuboCop
147
187
  false
148
188
  when :begin, :kwbegin
149
189
  !node.right_sibling && return_value_used?(parent)
150
- when :block, :numblock
151
- !parent.void_context?
152
190
  else
153
- true
191
+ !parent.respond_to?(:void_context?) || !parent.void_context?
154
192
  end
155
193
  end
156
194
 
@@ -161,6 +199,12 @@ module RuboCop
161
199
  corrector.remove(range)
162
200
  end
163
201
 
202
+ def remove_tap(corrector, node, block_node)
203
+ range = range_between(block_node.source_range.begin_pos, node.source_range.begin_pos)
204
+ corrector.remove(range)
205
+ corrector.remove(range_with_surrounding_space(block_node.loc.end, side: :left))
206
+ end
207
+
164
208
  def correct_push_node(corrector, push_node)
165
209
  range = push_node.source_range
166
210
  arg_range = push_node.first_argument.source_range
@@ -7,6 +7,8 @@ module RuboCop
7
7
  # Style omit_parentheses
8
8
  # rubocop:disable Metrics/ModuleLength, Metrics/CyclomaticComplexity
9
9
  module OmitParentheses
10
+ include RangeHelp
11
+
10
12
  TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
11
13
  OMIT_MSG = 'Omit parentheses for method calls with arguments.'
12
14
  private_constant :OMIT_MSG
@@ -30,10 +32,13 @@ module RuboCop
30
32
  end
31
33
 
32
34
  def autocorrect(corrector, node)
35
+ range = args_begin(node)
33
36
  if parentheses_at_the_end_of_multiline_call?(node)
34
- corrector.replace(args_begin(node), ' \\')
37
+ # Whitespace after line continuation (`\ `) is a syntax error
38
+ with_whitespace = range_with_surrounding_space(range, side: :right, newlines: false)
39
+ corrector.replace(with_whitespace, ' \\')
35
40
  else
36
- corrector.replace(args_begin(node), ' ')
41
+ corrector.replace(range, ' ')
37
42
  end
38
43
  corrector.remove(node.loc.end)
39
44
  end
@@ -47,11 +52,11 @@ module RuboCop
47
52
  node.each_ancestor(:def, :defs).any?(&:endless?) && node.arguments.any?
48
53
  end
49
54
 
50
- def require_parentheses_for_hash_value_omission?(node)
55
+ def require_parentheses_for_hash_value_omission?(node) # rubocop:disable Metrics/PerceivedComplexity
51
56
  return false unless (last_argument = node.last_argument)
52
57
  return false if !last_argument.hash_type? || !last_argument.pairs.last&.value_omission?
53
58
 
54
- node.parent&.conditional? || !last_expression?(node)
59
+ node.parent&.conditional? || node.parent&.single_line? || !last_expression?(node)
55
60
  end
56
61
 
57
62
  # Require hash value omission be enclosed in parentheses to prevent the following issue:
@@ -127,7 +132,7 @@ module RuboCop
127
132
  end
128
133
 
129
134
  def call_in_single_line_inheritance?(node)
130
- node.parent&.class_type? && node.parent&.single_line?
135
+ node.parent&.class_type? && node.parent.single_line?
131
136
  end
132
137
 
133
138
  def call_with_ambiguous_arguments?(node) # rubocop:disable Metrics/PerceivedComplexity
@@ -147,7 +152,7 @@ module RuboCop
147
152
  end
148
153
 
149
154
  def call_in_argument_with_block?(node)
150
- parent = node.parent&.block_type? && node.parent&.parent
155
+ parent = node.parent&.block_type? && node.parent.parent
151
156
  return false unless parent
152
157
 
153
158
  parent.call_type? || parent.super_type? || parent.yield_type?
@@ -211,7 +216,7 @@ module RuboCop
211
216
 
212
217
  def unary_literal?(node)
213
218
  (node.numeric_type? && node.sign?) ||
214
- (node.parent&.send_type? && node.parent&.unary_operation?)
219
+ (node.parent&.send_type? && node.parent.unary_operation?)
215
220
  end
216
221
 
217
222
  def assigned_before?(node, target)
@@ -43,7 +43,7 @@ module RuboCop
43
43
 
44
44
  return unless bad_rhs?(rhs)
45
45
 
46
- add_offense(node.source_range) do |corrector|
46
+ add_offense(node) do |corrector|
47
47
  if style == :keyword
48
48
  keyword_autocorrect(rhs, corrector)
49
49
  else
@@ -36,7 +36,7 @@ module RuboCop
36
36
  end
37
37
 
38
38
  def modifier?(node)
39
- node&.basic_conditional? && node&.modifier_form?
39
+ node&.basic_conditional? && node.modifier_form?
40
40
  end
41
41
 
42
42
  def autocorrect(corrector, node)
@@ -39,7 +39,7 @@ module RuboCop
39
39
  next if allowed_omission?(nested)
40
40
 
41
41
  message = format(MSG, source: nested.source)
42
- add_offense(nested.source_range, message: message) do |corrector|
42
+ add_offense(nested, message: message) do |corrector|
43
43
  autocorrect(corrector, nested)
44
44
  end
45
45
  end
@@ -46,7 +46,11 @@ module RuboCop
46
46
 
47
47
  message = message(node)
48
48
  add_offense(node, message: message) do |corrector|
49
+ next if part_of_ignored_node?(node)
50
+
49
51
  autocorrect(corrector, node)
52
+
53
+ ignore_node(node)
50
54
  end
51
55
  end
52
56
 
@@ -22,6 +22,9 @@ module RuboCop
22
22
 
23
23
  MSG = 'Redundant dot detected.'
24
24
  RESTRICT_ON_SEND = %i[| ^ & <=> == === =~ > >= < <= << >> + - * / % ** ~ ! != !~].freeze
25
+ INVALID_SYNTAX_ARG_TYPES = %i[
26
+ splat kwsplat forwarded_args forwarded_restarg forwarded_kwrestarg block_pass
27
+ ].freeze
25
28
 
26
29
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
27
30
  def on_send(node)
@@ -29,14 +32,16 @@ module RuboCop
29
32
  return if node.receiver.const_type? || !node.arguments.one?
30
33
 
31
34
  _lhs, _op, rhs = *node
32
- return if !rhs || method_call_with_parenthesized_arg?(rhs) || anonymous_forwarding?(rhs)
35
+ if !rhs || method_call_with_parenthesized_arg?(rhs) || invalid_syntax_argument?(rhs)
36
+ return
37
+ end
33
38
 
34
39
  add_offense(dot) do |corrector|
35
40
  wrap_in_parentheses_if_chained(corrector, node)
36
41
  corrector.replace(dot, ' ')
37
42
 
38
43
  selector = node.loc.selector
39
- corrector.insert_after(selector, ' ') if selector.end_pos == rhs.source_range.begin_pos
44
+ corrector.insert_after(selector, ' ') if insert_space_after?(node)
40
45
  end
41
46
  end
42
47
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
@@ -50,11 +55,10 @@ module RuboCop
50
55
  argument.children.first && argument.parent.parenthesized?
51
56
  end
52
57
 
53
- def anonymous_forwarding?(argument)
54
- return true if argument.forwarded_args_type? || argument.forwarded_restarg_type?
55
- return true if argument.hash_type? && argument.children.first&.forwarded_kwrestarg_type?
58
+ def invalid_syntax_argument?(argument)
59
+ type = argument.hash_type? ? argument.children.first&.type : argument.type
56
60
 
57
- argument.block_pass_type? && argument.source == '&'
61
+ INVALID_SYNTAX_ARG_TYPES.include?(type)
58
62
  end
59
63
 
60
64
  def wrap_in_parentheses_if_chained(corrector, node)
@@ -67,6 +71,21 @@ module RuboCop
67
71
  corrector.insert_after(operator, ' ')
68
72
  corrector.wrap(node, '(', ')')
69
73
  end
74
+
75
+ def insert_space_after?(node)
76
+ _lhs, op, rhs = *node
77
+ selector = node.loc.selector
78
+
79
+ return true if selector.end_pos == rhs.source_range.begin_pos
80
+ return false if node.parent&.call_type? # if chained, a space is already added
81
+
82
+ # For `/` operations, if the RHS starts with a `(` without space,
83
+ # add one to avoid a syntax error.
84
+ range = selector.end.join(rhs.source_range.begin)
85
+ return true if op == :/ && range.source == '('
86
+
87
+ false
88
+ end
70
89
  end
71
90
  end
72
91
  end
@@ -68,6 +68,10 @@ module RuboCop
68
68
 
69
69
  MSG = 'Redundant `begin` block detected.'
70
70
 
71
+ def self.autocorrect_incompatible_with
72
+ [Style::BlockDelimiters]
73
+ end
74
+
71
75
  # @!method offensive_kwbegins(node)
72
76
  def_node_search :offensive_kwbegins, <<~PATTERN
73
77
  [(kwbegin ...) !#allowable_kwbegin?]
@@ -94,7 +94,7 @@ module RuboCop
94
94
  end
95
95
 
96
96
  def use_hash_key_assignment?(else_branch)
97
- else_branch&.send_type? && else_branch&.method?(:[]=)
97
+ else_branch&.send_type? && else_branch.method?(:[]=)
98
98
  end
99
99
 
100
100
  def use_hash_key_access?(node)
@@ -16,7 +16,7 @@ module RuboCop
16
16
  # "#{foo} bar".dup
17
17
  #
18
18
  # # good
19
- # "#{foo} bar"
19
+ # "#{foo} bar"
20
20
  #
21
21
  class RedundantInterpolationUnfreeze < Base
22
22
  include FrozenStringLiteral
@@ -122,7 +122,7 @@ module RuboCop
122
122
  end
123
123
 
124
124
  def redundant_line_continuation?(range)
125
- return true unless (node = find_node_for_line(range.line))
125
+ return true unless (node = find_node_for_line(range.last_line))
126
126
  return false if argument_newline?(node)
127
127
 
128
128
  source = node.parent ? node.parent.source : node.source
@@ -160,9 +160,9 @@ module RuboCop
160
160
  end
161
161
  # rubocop:enable Metrics/AbcSize
162
162
 
163
- def find_node_for_line(line)
163
+ def find_node_for_line(last_line)
164
164
  processed_source.ast.each_node do |node|
165
- return node if same_line?(node, line)
165
+ return node if node.respond_to?(:expression) && node.expression&.last_line == last_line
166
166
  end
167
167
  end
168
168
 
@@ -160,7 +160,7 @@ module RuboCop
160
160
  return if node.semantic_operator? && begin_node.parent
161
161
  return if node.multiline? && allow_in_multiline_conditions?
162
162
  return if ALLOWED_NODE_TYPES.include?(begin_node.parent&.type)
163
- return if begin_node.parent&.if_type? && begin_node.parent&.ternary?
163
+ return if begin_node.parent&.if_type? && begin_node.parent.ternary?
164
164
 
165
165
  'a logical expression'
166
166
  elsif node.respond_to?(:comparison_method?) && node.comparison_method?
@@ -103,7 +103,7 @@ module RuboCop
103
103
  next unless sibling.is_a?(AST::Node)
104
104
 
105
105
  sibling = sibling_node(sibling)
106
- break unless sibling&.send_type? && sibling&.method?(node.method_name)
106
+ break unless sibling&.send_type? && sibling.method?(node.method_name)
107
107
  break unless sibling.arguments? && !sibling.receiver
108
108
  break unless in_same_section?(sibling, node)
109
109
  break unless node.first_argument.str_type? && sibling.first_argument.str_type?
@@ -75,7 +75,7 @@ module RuboCop
75
75
 
76
76
  corrector.remove(range_between(operation.source_range.end_pos, node.source_range.end_pos))
77
77
  corrector.insert_before(operation, "begin\n#{node_indentation}")
78
- corrector.insert_after(operation, <<~RESCUE_CLAUSE.chop)
78
+ corrector.insert_after(heredoc_end(operation) || operation, <<~RESCUE_CLAUSE.chop)
79
79
 
80
80
  #{node_offset}rescue
81
81
  #{node_indentation}#{rescue_args.source}
@@ -92,6 +92,18 @@ module RuboCop
92
92
  end
93
93
  [node_indentation, node_offset]
94
94
  end
95
+
96
+ def heredoc_end(node)
97
+ return unless node.call_type?
98
+
99
+ heredoc = node.arguments.reverse.find do |argument|
100
+ argument.respond_to?(:heredoc?) && argument.heredoc?
101
+ end
102
+
103
+ return unless heredoc
104
+
105
+ heredoc.loc.heredoc_end
106
+ end
95
107
  end
96
108
  end
97
109
  end
@@ -3,7 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Style
6
- # Checks if `return` or `return nil` is used in predicate method definitions.
6
+ # Checks for predicate method definitions that return `nil`.
7
+ # A predicate method should only return a boolean value.
7
8
  #
8
9
  # @safety
9
10
  # Autocorrection is marked as unsafe because the change of the return value
@@ -31,6 +32,24 @@ module RuboCop
31
32
  # do_something?
32
33
  # end
33
34
  #
35
+ # # bad
36
+ # def foo?
37
+ # if condition
38
+ # nil
39
+ # else
40
+ # true
41
+ # end
42
+ # end
43
+ #
44
+ # # good
45
+ # def foo?
46
+ # if condition
47
+ # false
48
+ # else
49
+ # true
50
+ # end
51
+ # end
52
+ #
34
53
  # @example AllowedMethods: ['foo?']
35
54
  # # good
36
55
  # def foo?
@@ -64,24 +83,25 @@ module RuboCop
64
83
  return if allowed_method?(node.method_name) || matches_allowed_pattern?(node.method_name)
65
84
  return unless (body = node.body)
66
85
 
67
- body.each_descendant(:return) do |return_node|
68
- register_offense(return_node, 'return false') if return_nil?(return_node)
69
- end
70
-
71
- return unless (nil_node = nil_node_at_the_end_of_method_body(body))
86
+ body.each_descendant(:return) { |return_node| handle_return(return_node) }
72
87
 
73
- register_offense(nil_node, 'false')
88
+ handle_implicit_return_values(body)
74
89
  end
75
90
  alias on_defs on_def
76
91
 
77
92
  private
78
93
 
79
- def nil_node_at_the_end_of_method_body(body)
80
- return body if body.nil_type?
81
- return unless body.begin_type?
82
- return unless (last_child = body.children.last)
94
+ def last_node_of_type(node, type)
95
+ return unless node
96
+ return node if node_type?(node, type)
97
+ return unless node.begin_type?
98
+ return unless (last_child = node.children.last)
99
+
100
+ last_child if last_child.is_a?(AST::Node) && node_type?(last_child, type)
101
+ end
83
102
 
84
- last_child if last_child.is_a?(AST::Node) && last_child.nil_type?
103
+ def node_type?(node, type)
104
+ node.type == type.to_sym
85
105
  end
86
106
 
87
107
  def register_offense(offense_node, replacement)
@@ -89,6 +109,28 @@ module RuboCop
89
109
  corrector.replace(offense_node, replacement)
90
110
  end
91
111
  end
112
+
113
+ def handle_implicit_return_values(node)
114
+ handle_if(last_node_of_type(node, :if))
115
+ handle_nil(last_node_of_type(node, :nil))
116
+ end
117
+
118
+ def handle_return(return_node)
119
+ register_offense(return_node, 'return false') if return_nil?(return_node)
120
+ end
121
+
122
+ def handle_nil(nil_node)
123
+ return unless nil_node
124
+
125
+ register_offense(nil_node, 'false')
126
+ end
127
+
128
+ def handle_if(if_node)
129
+ return unless if_node
130
+
131
+ handle_implicit_return_values(if_node.if_branch)
132
+ handle_implicit_return_values(if_node.else_branch)
133
+ end
92
134
  end
93
135
  end
94
136
  end