rubocop 1.77.0 → 1.80.2

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -3
  3. data/config/default.yml +36 -20
  4. data/exe/rubocop +1 -8
  5. data/lib/rubocop/cli.rb +17 -1
  6. data/lib/rubocop/config_loader.rb +1 -38
  7. data/lib/rubocop/cop/correctors/alignment_corrector.rb +6 -3
  8. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  9. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -1
  10. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  11. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  12. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
  13. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
  14. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
  15. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -0
  16. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  17. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  18. data/lib/rubocop/cop/lint/duplicate_methods.rb +25 -4
  19. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  20. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -1
  21. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -2
  22. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  23. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  24. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +4 -4
  25. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  26. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  27. data/lib/rubocop/cop/lint/self_assignment.rb +5 -4
  28. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  29. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  30. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  31. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  32. data/lib/rubocop/cop/naming/method_name.rb +127 -13
  33. data/lib/rubocop/cop/naming/predicate_method.rb +30 -4
  34. data/lib/rubocop/cop/security/eval.rb +2 -1
  35. data/lib/rubocop/cop/security/open.rb +1 -0
  36. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  37. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  38. data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
  39. data/lib/rubocop/cop/style/array_intersect.rb +98 -34
  40. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  41. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  42. data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
  43. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  44. data/lib/rubocop/cop/style/exponential_notation.rb +1 -0
  45. data/lib/rubocop/cop/style/hash_conversion.rb +8 -9
  46. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  47. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  48. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  49. data/lib/rubocop/cop/style/it_block_parameter.rb +3 -1
  50. data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
  51. data/lib/rubocop/cop/style/map_to_set.rb +1 -3
  52. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
  53. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +16 -0
  54. data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
  55. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  56. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  57. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -9
  58. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  59. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  60. data/lib/rubocop/cop/style/redundant_parentheses.rb +28 -11
  61. data/lib/rubocop/cop/style/safe_navigation.rb +20 -1
  62. data/lib/rubocop/cop/style/single_line_methods.rb +7 -4
  63. data/lib/rubocop/cop/style/sole_nested_conditional.rb +30 -1
  64. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  65. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  66. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  67. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  68. data/lib/rubocop/cop/variable_force.rb +25 -8
  69. data/lib/rubocop/cops_documentation_generator.rb +1 -0
  70. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  71. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  72. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  73. data/lib/rubocop/lsp/routes.rb +35 -6
  74. data/lib/rubocop/pending_cops_reporter.rb +56 -0
  75. data/lib/rubocop/result_cache.rb +14 -12
  76. data/lib/rubocop/runner.rb +6 -4
  77. data/lib/rubocop/server/cache.rb +4 -2
  78. data/lib/rubocop/server/client_command/base.rb +10 -0
  79. data/lib/rubocop/server/client_command/exec.rb +2 -1
  80. data/lib/rubocop/server/client_command/start.rb +11 -1
  81. data/lib/rubocop/target_finder.rb +9 -9
  82. data/lib/rubocop/version.rb +1 -1
  83. data/lib/rubocop.rb +3 -0
  84. metadata +9 -6
@@ -6,6 +6,10 @@ module RuboCop
6
6
  # Checks for duplicated instance (or singleton) method
7
7
  # definitions.
8
8
  #
9
+ # NOTE: Aliasing a method to itself is allowed, as it indicates that
10
+ # the developer intends to suppress Ruby's method redefinition warnings.
11
+ # See https://bugs.ruby-lang.org/issues/13574.
12
+ #
9
13
  # @example
10
14
  #
11
15
  # # bad
@@ -40,6 +44,18 @@ module RuboCop
40
44
  #
41
45
  # alias bar foo
42
46
  #
47
+ # # good
48
+ # alias foo foo
49
+ # def foo
50
+ # 1
51
+ # end
52
+ #
53
+ # # good
54
+ # alias_method :foo, :foo
55
+ # def foo
56
+ # 1
57
+ # end
58
+ #
43
59
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
44
60
  #
45
61
  # # good
@@ -113,11 +129,13 @@ module RuboCop
113
129
 
114
130
  # @!method method_alias?(node)
115
131
  def_node_matcher :method_alias?, <<~PATTERN
116
- (alias (sym $_name) sym)
132
+ (alias (sym $_name) (sym $_original_name))
117
133
  PATTERN
118
134
 
119
135
  def on_alias(node)
120
- return unless (name = method_alias?(node))
136
+ name, original_name = method_alias?(node)
137
+ return unless name && original_name
138
+ return if name == original_name
121
139
  return if node.ancestors.any?(&:if_type?)
122
140
 
123
141
  found_instance_method(node, name)
@@ -125,7 +143,7 @@ module RuboCop
125
143
 
126
144
  # @!method alias_method?(node)
127
145
  def_node_matcher :alias_method?, <<~PATTERN
128
- (send nil? :alias_method (sym $_name) _)
146
+ (send nil? :alias_method (sym $_name) (sym $_original_name))
129
147
  PATTERN
130
148
 
131
149
  # @!method delegate_method?(node)
@@ -140,7 +158,10 @@ module RuboCop
140
158
  def_node_matcher :sym_name, '(sym $_name)'
141
159
 
142
160
  def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
143
- if (name = alias_method?(node))
161
+ name, original_name = alias_method?(node)
162
+
163
+ if name && original_name
164
+ return if name == original_name
144
165
  return if node.ancestors.any?(&:if_type?)
145
166
 
146
167
  found_instance_method(node, name)
@@ -24,8 +24,6 @@ module RuboCop
24
24
 
25
25
  MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
26
26
 
27
- OCTAL_DIGITS_AFTER_ESCAPE = 2
28
-
29
27
  def on_regexp(node)
30
28
  each_repeated_character_class_element_loc(node) do |loc|
31
29
  add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
@@ -40,9 +38,9 @@ module RuboCop
40
38
 
41
39
  seen = Set.new
42
40
  group_expressions(node, expr.expressions) do |group|
43
- group_source = group.map(&:to_s).join
41
+ group_source = group.to_s
44
42
 
45
- yield source_range(group) if seen.include?(group_source)
43
+ yield group.expression if seen.include?(group_source)
46
44
 
47
45
  seen << group_source
48
46
  end
@@ -52,40 +50,13 @@ module RuboCop
52
50
  private
53
51
 
54
52
  def group_expressions(node, expressions)
55
- # Create a mutable list to simplify state tracking while we iterate.
56
- expressions = expressions.to_a
57
-
58
- until expressions.empty?
59
- # With we may need to compose a group of multiple expressions.
60
- group = [expressions.shift]
61
- next if within_interpolation?(node, group.first)
62
-
63
- # With regexp_parser < 2.7 escaped octal sequences may be up to 3
64
- # separate expressions ("\\0", "0", "1").
65
- pop_octal_digits(group, expressions) if escaped_octal?(group.first.to_s)
66
-
67
- yield(group)
68
- end
69
- end
70
-
71
- def pop_octal_digits(current_child, expressions)
72
- OCTAL_DIGITS_AFTER_ESCAPE.times do
73
- next_child = expressions.first
74
- break unless octal?(next_child.to_s)
53
+ expressions.each do |expression|
54
+ next if within_interpolation?(node, expression)
75
55
 
76
- current_child << expressions.shift
56
+ yield(expression)
77
57
  end
78
58
  end
79
59
 
80
- def source_range(children)
81
- return children.first.expression if children.size == 1
82
-
83
- range_between(
84
- children.first.expression.begin_pos,
85
- children.last.expression.begin_pos + children.last.to_s.length
86
- )
87
- end
88
-
89
60
  def skip_expression?(expr)
90
61
  expr.type != :set || expr.token == :intersection
91
62
  end
@@ -99,14 +70,6 @@ module RuboCop
99
70
  interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
100
71
  end
101
72
 
102
- def escaped_octal?(string)
103
- string.length == 2 && string[0] == '\\' && octal?(string[1])
104
- end
105
-
106
- def octal?(char)
107
- ('0'..'7').cover?(char)
108
- end
109
-
110
73
  def interpolation_locs(node)
111
74
  @interpolation_locs ||= {}
112
75
 
@@ -54,6 +54,18 @@ module RuboCop
54
54
  end
55
55
  end
56
56
 
57
+ def on_or(node)
58
+ return unless node.lhs.falsey_literal?
59
+
60
+ add_offense(node.lhs) do |corrector|
61
+ # Don't autocorrect `'foo' && return` because having `return` as
62
+ # the leftmost node can lead to a void value expression syntax error.
63
+ next if node.rhs.type?(:return, :break, :next)
64
+
65
+ corrector.replace(node, node.rhs.source)
66
+ end
67
+ end
68
+
57
69
  def on_if(node)
58
70
  cond = condition(node)
59
71
 
@@ -123,7 +135,9 @@ module RuboCop
123
135
  # rubocop:enable Metrics/AbcSize
124
136
 
125
137
  def on_case(case_node)
126
- if case_node.condition
138
+ if (cond = case_node.condition)
139
+ return if !cond.falsey_literal? && !cond.truthy_literal?
140
+
127
141
  check_case(case_node)
128
142
  else
129
143
  case_node.when_branches.each do |when_node|
@@ -52,10 +52,9 @@ module RuboCop
52
52
  each_missing_enable do |cop, line_range|
53
53
  next if acceptable_range?(cop, line_range)
54
54
 
55
- range = source_range(processed_source.buffer, line_range.min, 0..0)
56
55
  comment = processed_source.comment_at_line(line_range.begin)
57
56
 
58
- add_offense(range, message: message(cop, comment))
57
+ add_offense(comment, message: message(cop, comment))
59
58
  end
60
59
  end
61
60
 
@@ -45,6 +45,7 @@ module RuboCop
45
45
  #
46
46
  class NumericOperationWithConstantResult < Base
47
47
  extend AutoCorrector
48
+
48
49
  MSG = 'Numeric operation with a constant result detected.'
49
50
  RESTRICT_ON_SEND = %i[* / **].freeze
50
51
 
@@ -20,6 +20,15 @@ module RuboCop
20
20
  # In the example below, the safe navigation operator (`&.`) is unnecessary
21
21
  # because `NilClass` has methods like `respond_to?` and `is_a?`.
22
22
  #
23
+ # The `InferNonNilReceiver` option specifies whether to look into previous code
24
+ # paths to infer if the receiver can't be nil. This check is unsafe because the receiver
25
+ # can be redefined between the safe navigation call and previous regular method call.
26
+ # It does the inference only in the current scope, e.g. within the same method definition etc.
27
+ #
28
+ # The `AdditionalNilMethods` option specifies additional custom methods which are
29
+ # defined on `NilClass`. When `InferNonNilReceiver` is set, they are used to determine
30
+ # whether the receiver can be nil.
31
+ #
23
32
  # @safety
24
33
  # This cop is unsafe, because autocorrection can change the return type of
25
34
  # the expression. An offending expression that previously could return `nil`
@@ -33,6 +42,20 @@ module RuboCop
33
42
  # CamelCaseConst.do_something
34
43
  #
35
44
  # # bad
45
+ # foo.to_s&.strip
46
+ # foo.to_i&.zero?
47
+ # foo.to_f&.zero?
48
+ # foo.to_a&.size
49
+ # foo.to_h&.size
50
+ #
51
+ # # good
52
+ # foo.to_s.strip
53
+ # foo.to_i.zero?
54
+ # foo.to_f.zero?
55
+ # foo.to_a.size
56
+ # foo.to_h.size
57
+ #
58
+ # # bad
36
59
  # do_something if attrs&.respond_to?(:[])
37
60
  #
38
61
  # # good
@@ -81,17 +104,59 @@ module RuboCop
81
104
  # do_something if attrs.nil_safe_method(:[])
82
105
  # do_something if attrs&.not_nil_safe_method(:[])
83
106
  #
107
+ # @example InferNonNilReceiver: false (default)
108
+ # # good
109
+ # foo.bar
110
+ # foo&.baz
111
+ #
112
+ # @example InferNonNilReceiver: true
113
+ # # bad
114
+ # foo.bar
115
+ # foo&.baz # would raise on previous line if `foo` is nil
116
+ #
117
+ # # good
118
+ # foo.bar
119
+ # foo.baz
120
+ #
121
+ # # bad
122
+ # if foo.condition?
123
+ # foo&.bar
124
+ # end
125
+ #
126
+ # # good
127
+ # if foo.condition?
128
+ # foo.bar
129
+ # end
130
+ #
131
+ # # good (different scopes)
132
+ # def method1
133
+ # foo.bar
134
+ # end
135
+ #
136
+ # def method2
137
+ # foo&.bar
138
+ # end
139
+ #
140
+ # @example AdditionalNilMethods: [present?]
141
+ # # good
142
+ # foo.present?
143
+ # foo&.bar
144
+ #
84
145
  class RedundantSafeNavigation < Base
85
146
  include AllowedMethods
86
147
  extend AutoCorrector
87
148
 
88
149
  MSG = 'Redundant safe navigation detected, use `.` instead.'
89
150
  MSG_LITERAL = 'Redundant safe navigation with default literal detected.'
151
+ MSG_NON_NIL = 'Redundant safe navigation on non-nil receiver (detected by analyzing ' \
152
+ 'previous code/method invocations).'
90
153
 
91
154
  NIL_SPECIFIC_METHODS = (nil.methods - Object.new.methods).to_set.freeze
92
155
 
93
156
  SNAKE_CASE = /\A[[:digit:][:upper:]_]+\z/.freeze
94
157
 
158
+ GUARANTEED_INSTANCE_METHODS = %i[to_s to_i to_f to_a to_h].freeze
159
+
95
160
  # @!method respond_to_nil_specific_method?(node)
96
161
  def_node_matcher :respond_to_nil_specific_method?, <<~PATTERN
97
162
  (csend _ :respond_to? (sym %NIL_SPECIFIC_METHODS))
@@ -111,15 +176,27 @@ module RuboCop
111
176
 
112
177
  # rubocop:disable Metrics/AbcSize
113
178
  def on_csend(node)
179
+ range = node.loc.dot
180
+
181
+ if infer_non_nil_receiver?
182
+ checker = Lint::Utils::NilReceiverChecker.new(node.receiver, additional_nil_methods)
183
+
184
+ if checker.cant_be_nil?
185
+ add_offense(range, message: MSG_NON_NIL) { |corrector| corrector.replace(range, '.') }
186
+ return
187
+ end
188
+ end
189
+
114
190
  unless assume_receiver_instance_exists?(node.receiver)
115
- return unless check?(node) && allowed_method?(node.method_name)
191
+ return if !guaranteed_instance?(node.receiver) && !check?(node)
116
192
  return if respond_to_nil_specific_method?(node)
117
193
  end
118
194
 
119
- range = node.loc.dot
120
195
  add_offense(range) { |corrector| corrector.replace(range, '.') }
121
196
  end
197
+ # rubocop:enable Metrics/AbcSize
122
198
 
199
+ # rubocop:disable Metrics/AbcSize
123
200
  def on_or(node)
124
201
  conversion_with_default?(node) do |send_node|
125
202
  range = send_node.loc.dot.begin.join(node.source_range.end)
@@ -142,7 +219,20 @@ module RuboCop
142
219
  receiver.self_type? || (receiver.literal? && !receiver.nil_type?)
143
220
  end
144
221
 
222
+ def guaranteed_instance?(node)
223
+ receiver = if node.any_block_type?
224
+ node.send_node
225
+ else
226
+ node
227
+ end
228
+ return false unless receiver.send_type?
229
+
230
+ GUARANTEED_INSTANCE_METHODS.include?(receiver.method_name)
231
+ end
232
+
145
233
  def check?(node)
234
+ return false unless allowed_method?(node.method_name)
235
+
146
236
  parent = node.parent
147
237
  return false unless parent
148
238
 
@@ -154,6 +244,15 @@ module RuboCop
154
244
  def condition?(parent, node)
155
245
  (parent.conditional? || parent.post_condition_loop?) && parent.condition == node
156
246
  end
247
+
248
+ def infer_non_nil_receiver?
249
+ cop_config['InferNonNilReceiver']
250
+ end
251
+
252
+ def additional_nil_methods
253
+ @additional_nil_methods ||=
254
+ Array(cop_config.fetch('AdditionalNilMethods', []).map(&:to_sym))
255
+ end
157
256
  end
158
257
  end
159
258
  end
@@ -185,9 +185,9 @@ module RuboCop
185
185
  (hash (pair (sym :exception) false))
186
186
  PATTERN
187
187
 
188
- # rubocop:disable Metrics/AbcSize
188
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
189
189
  def on_send(node)
190
- return if hash_or_set_with_block?(node)
190
+ return if node.arguments.any? || hash_or_set_with_block?(node)
191
191
 
192
192
  receiver = find_receiver(node)
193
193
  return unless literal_receiver?(node, receiver) ||
@@ -198,10 +198,10 @@ module RuboCop
198
198
  message = format(MSG, method: node.method_name)
199
199
 
200
200
  add_offense(node.loc.selector, message: message) do |corrector|
201
- corrector.remove(node.loc.dot.join(node.loc.selector))
201
+ corrector.remove(node.loc.dot.join(node.loc.end || node.loc.selector))
202
202
  end
203
203
  end
204
- # rubocop:enable Metrics/AbcSize
204
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
205
205
  alias on_csend on_send
206
206
 
207
207
  private
@@ -43,7 +43,7 @@ module RuboCop
43
43
  def on_irange(node)
44
44
  return if node.parent&.begin_type?
45
45
  return unless node.begin && node.end
46
- return if same_line?(node.begin, node.end)
46
+ return if same_line?(node.loc.operator, node.end)
47
47
 
48
48
  message = format(MSG, range: "#{node.begin.source}#{node.loc.operator.source}")
49
49
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Check for arguments to `rescue` that will result in a `TypeError`
6
+ # Checks for arguments to `rescue` that will result in a `TypeError`
7
7
  # if an exception is raised.
8
8
  #
9
9
  # @example
@@ -45,7 +45,7 @@ module RuboCop
45
45
  return if allow_rbs_inline_annotation? && rbs_inline_annotation?(node.receiver)
46
46
 
47
47
  if node.method?(:[]=)
48
- handle_key_assignment(node) if node.arguments.size == 2
48
+ handle_key_assignment(node)
49
49
  elsif node.assignment_method?
50
50
  handle_attribute_assignment(node) if node.arguments.size == 1
51
51
  end
@@ -105,12 +105,13 @@ module RuboCop
105
105
  end
106
106
 
107
107
  def handle_key_assignment(node)
108
- value_node = node.arguments[1]
108
+ value_node = node.last_argument
109
+ node_arguments = node.arguments[0...-1]
109
110
 
110
111
  if value_node.send_type? && value_node.method?(:[]) &&
111
112
  node.receiver == value_node.receiver &&
112
- !node.first_argument.call_type? &&
113
- node.first_argument == value_node.first_argument
113
+ node_arguments.none?(&:call_type?) &&
114
+ node_arguments == value_node.arguments
114
115
  add_offense(node)
115
116
  end
116
117
  end
@@ -17,6 +17,7 @@ module RuboCop
17
17
  #
18
18
  # # good
19
19
  # CGI.escape('http://example.com')
20
+ # URI.encode_uri_component(uri) # Since Ruby 3.1
20
21
  # URI.encode_www_form([['example', 'param'], ['lang', 'en']])
21
22
  # URI.encode_www_form(page: 10, locale: 'en')
22
23
  # URI.encode_www_form_component('http://example.com')
@@ -27,6 +28,7 @@ module RuboCop
27
28
  #
28
29
  # # good
29
30
  # CGI.unescape(enc_uri)
31
+ # URI.decode_uri_component(uri) # Since Ruby 3.1
30
32
  # URI.decode_www_form(enc_uri)
31
33
  # URI.decode_www_form_component(enc_uri)
32
34
  class UriEscapeUnescape < Base
@@ -31,6 +31,7 @@ module RuboCop
31
31
  #
32
32
  class UselessNumericOperation < Base
33
33
  extend AutoCorrector
34
+
34
35
  MSG = 'Do not apply inconsequential numeric operations to variables.'
35
36
  RESTRICT_ON_SEND = %i[+ - * / **].freeze
36
37
 
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ module Utils
7
+ # Utility class that checks if the receiver can't be nil.
8
+ class NilReceiverChecker
9
+ NIL_METHODS = (nil.methods + %i[!]).to_set.freeze
10
+
11
+ def initialize(receiver, additional_nil_methods)
12
+ @receiver = receiver
13
+ @additional_nil_methods = additional_nil_methods
14
+ @checked_nodes = {}.compare_by_identity
15
+ end
16
+
17
+ def cant_be_nil?
18
+ sole_condition_of_parent_if?(@receiver) || _cant_be_nil?(@receiver.parent, @receiver)
19
+ end
20
+
21
+ private
22
+
23
+ # rubocop:disable Metrics
24
+ def _cant_be_nil?(node, receiver)
25
+ return false unless node
26
+
27
+ # For some nodes, we check their parent and then some children for these parents.
28
+ # This is added to avoid infinite loops.
29
+ return false if @checked_nodes.key?(node)
30
+
31
+ @checked_nodes[node] = true
32
+
33
+ case node.type
34
+ when :def, :class, :module, :sclass
35
+ return false
36
+ when :send
37
+ return non_nil_method?(node.method_name) if node.receiver == receiver
38
+
39
+ node.arguments.each do |argument|
40
+ return true if _cant_be_nil?(argument, receiver)
41
+ end
42
+
43
+ return true if _cant_be_nil?(node.receiver, receiver)
44
+ when :begin
45
+ return true if _cant_be_nil?(node.children.first, receiver)
46
+ when :if, :case
47
+ return true if _cant_be_nil?(node.condition, receiver)
48
+ when :and, :or
49
+ return true if _cant_be_nil?(node.lhs, receiver)
50
+ when :pair
51
+ if _cant_be_nil?(node.key, receiver) ||
52
+ _cant_be_nil?(node.value, receiver)
53
+ return true
54
+ end
55
+ when :when
56
+ node.each_condition do |condition|
57
+ return true if _cant_be_nil?(condition, receiver)
58
+ end
59
+ when :lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn
60
+ return true if _cant_be_nil?(node.expression, receiver)
61
+ end
62
+
63
+ # Due to how `if/else` are implemented (`elsif` is a child of `if` or another `elsif`),
64
+ # using left_siblings will not work correctly for them.
65
+ if !else_branch?(node) || (node.if_type? && !node.elsif?)
66
+ node.left_siblings.reverse_each do |sibling|
67
+ next unless sibling.is_a?(AST::Node)
68
+
69
+ return true if _cant_be_nil?(sibling, receiver)
70
+ end
71
+ end
72
+
73
+ if node.parent
74
+ _cant_be_nil?(node.parent, receiver)
75
+ else
76
+ false
77
+ end
78
+ end
79
+ # rubocop:enable Metrics
80
+
81
+ def non_nil_method?(method_name)
82
+ !NIL_METHODS.include?(method_name) && !@additional_nil_methods.include?(method_name)
83
+ end
84
+
85
+ # rubocop:disable Metrics/PerceivedComplexity
86
+ def sole_condition_of_parent_if?(node)
87
+ parent = node.parent
88
+
89
+ while parent
90
+ if parent.if_type?
91
+ if parent.condition == node
92
+ return true
93
+ elsif parent.elsif?
94
+ parent = find_top_if(parent)
95
+ end
96
+ elsif else_branch?(parent)
97
+ # Find the top `if` for `else`.
98
+ parent = parent.parent
99
+ end
100
+
101
+ parent = parent&.parent
102
+ end
103
+
104
+ false
105
+ end
106
+ # rubocop:enable Metrics/PerceivedComplexity
107
+
108
+ def else_branch?(node)
109
+ node.parent&.if_type? && node.parent.else_branch == node
110
+ end
111
+
112
+ def find_top_if(node)
113
+ node = node.parent while node.elsif?
114
+
115
+ node
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -19,8 +19,7 @@ module RuboCop
19
19
  def check_end_kw_alignment(node, align_ranges)
20
20
  return if ignored_node?(node)
21
21
 
22
- end_loc = node.loc.end
23
- return if accept_end_kw_alignment?(end_loc)
22
+ return unless (end_loc = node.loc.end)
24
23
 
25
24
  matching = matching_ranges(end_loc, align_ranges)
26
25
 
@@ -57,11 +56,6 @@ module RuboCop
57
56
  add_offense(end_loc, message: msg) { |corrector| autocorrect(corrector, node) }
58
57
  end
59
58
 
60
- def accept_end_kw_alignment?(end_loc)
61
- end_loc.nil? || # Discard modifier forms of if/while/until.
62
- !/\A[ \t]*end/.match?(processed_source.lines[end_loc.line - 1])
63
- end
64
-
65
59
  def style_parameter_name
66
60
  'EnforcedStyleAlignWith'
67
61
  end