rubocop 1.66.0 → 1.67.0

Sign up to get free protection for your applications and to get access to all the features.
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