rubocop 1.67.0 → 1.69.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +81 -6
  4. data/lib/rubocop/cached_data.rb +12 -4
  5. data/lib/rubocop/cli/command/execute_runner.rb +1 -1
  6. data/lib/rubocop/cli/command/version.rb +2 -2
  7. data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
  8. data/lib/rubocop/cop/base.rb +1 -1
  9. data/lib/rubocop/cop/bundler/gem_filename.rb +0 -1
  10. data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +0 -1
  11. data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
  12. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +1 -1
  13. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
  14. data/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +1 -2
  15. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +0 -2
  16. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +2 -4
  17. data/lib/rubocop/cop/internal_affairs/numblock_handler.rb +1 -1
  18. data/lib/rubocop/cop/internal_affairs/operator_keyword.rb +46 -0
  19. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +0 -2
  20. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  21. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -2
  22. data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
  23. data/lib/rubocop/cop/layout/begin_end_alignment.rb +0 -1
  24. data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
  25. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  26. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +2 -3
  27. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +3 -4
  28. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +3 -1
  29. data/lib/rubocop/cop/layout/indentation_width.rb +7 -7
  30. data/lib/rubocop/cop/layout/leading_comment_space.rb +44 -1
  31. data/lib/rubocop/cop/layout/line_length.rb +118 -4
  32. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
  33. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +1 -1
  34. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -3
  35. data/lib/rubocop/cop/layout/parameter_alignment.rb +3 -4
  36. data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -35
  37. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -2
  38. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
  39. data/lib/rubocop/cop/layout/space_around_operators.rb +16 -17
  40. data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
  41. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +6 -0
  42. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
  43. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +4 -0
  44. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +0 -1
  45. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +11 -12
  46. data/lib/rubocop/cop/lint/circular_argument_reference.rb +2 -0
  47. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +1 -1
  48. data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
  49. data/lib/rubocop/cop/lint/empty_ensure.rb +1 -1
  50. data/lib/rubocop/cop/lint/empty_file.rb +0 -2
  51. data/lib/rubocop/cop/lint/ensure_return.rb +1 -1
  52. data/lib/rubocop/cop/lint/float_comparison.rb +14 -6
  53. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -3
  54. data/lib/rubocop/cop/lint/hash_new_with_keyword_arguments_as_default.rb +55 -0
  55. data/lib/rubocop/cop/lint/interpolation_check.rb +9 -0
  56. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +3 -0
  57. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +1 -1
  58. data/lib/rubocop/cop/lint/mixed_case_range.rb +2 -5
  59. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
  60. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +2 -2
  61. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +8 -1
  62. data/lib/rubocop/cop/lint/number_conversion.rb +0 -1
  63. data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -2
  64. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +106 -0
  65. data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -2
  66. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +1 -1
  67. data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +1 -1
  68. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +12 -7
  69. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -7
  70. data/lib/rubocop/cop/lint/regexp_as_condition.rb +0 -1
  71. data/lib/rubocop/cop/lint/rescue_type.rb +3 -7
  72. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
  73. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +5 -1
  74. data/lib/rubocop/cop/lint/self_assignment.rb +8 -10
  75. data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
  76. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
  77. data/lib/rubocop/cop/lint/unused_method_argument.rb +18 -2
  78. data/lib/rubocop/cop/lint/useless_defined.rb +55 -0
  79. data/lib/rubocop/cop/lint/useless_rescue.rb +1 -1
  80. data/lib/rubocop/cop/lint/useless_setter_call.rb +14 -25
  81. data/lib/rubocop/cop/lint/void.rb +3 -2
  82. data/lib/rubocop/cop/metrics/class_length.rb +7 -7
  83. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
  84. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -1
  85. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -2
  86. data/lib/rubocop/cop/mixin/check_assignment.rb +4 -12
  87. data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
  88. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +49 -0
  89. data/lib/rubocop/cop/mixin/dig_help.rb +27 -0
  90. data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
  91. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
  92. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +5 -9
  93. data/lib/rubocop/cop/mixin/range_help.rb +0 -1
  94. data/lib/rubocop/cop/mixin/target_ruby_version.rb +17 -1
  95. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  96. data/lib/rubocop/cop/naming/constant_name.rb +6 -7
  97. data/lib/rubocop/cop/naming/file_name.rb +0 -2
  98. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +11 -12
  99. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +3 -11
  100. data/lib/rubocop/cop/naming/variable_name.rb +3 -4
  101. data/lib/rubocop/cop/naming/variable_number.rb +2 -3
  102. data/lib/rubocop/cop/offense.rb +2 -3
  103. data/lib/rubocop/cop/style/access_modifier_declarations.rb +53 -24
  104. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
  105. data/lib/rubocop/cop/style/array_intersect.rb +5 -4
  106. data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
  107. data/lib/rubocop/cop/style/block_delimiters.rb +18 -3
  108. data/lib/rubocop/cop/style/case_like_if.rb +8 -11
  109. data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
  110. data/lib/rubocop/cop/style/commented_keyword.rb +11 -1
  111. data/lib/rubocop/cop/style/conditional_assignment.rb +19 -21
  112. data/lib/rubocop/cop/style/constant_visibility.rb +3 -12
  113. data/lib/rubocop/cop/style/dig_chain.rb +90 -0
  114. data/lib/rubocop/cop/style/endless_method.rb +1 -14
  115. data/lib/rubocop/cop/style/file_null.rb +73 -0
  116. data/lib/rubocop/cop/style/file_touch.rb +75 -0
  117. data/lib/rubocop/cop/style/for.rb +0 -1
  118. data/lib/rubocop/cop/style/global_vars.rb +1 -3
  119. data/lib/rubocop/cop/style/guard_clause.rb +15 -2
  120. data/lib/rubocop/cop/style/hash_conversion.rb +1 -2
  121. data/lib/rubocop/cop/style/if_inside_else.rb +0 -1
  122. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -2
  123. data/lib/rubocop/cop/style/if_with_semicolon.rb +14 -5
  124. data/lib/rubocop/cop/style/inverse_methods.rb +0 -1
  125. data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
  126. data/lib/rubocop/cop/style/lambda_call.rb +0 -1
  127. data/lib/rubocop/cop/style/map_into_array.rb +6 -1
  128. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
  129. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +7 -11
  130. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +33 -3
  131. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  132. data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
  133. data/lib/rubocop/cop/style/mutable_constant.rb +4 -5
  134. data/lib/rubocop/cop/style/negated_if_else_condition.rb +6 -4
  135. data/lib/rubocop/cop/style/nested_ternary_operator.rb +5 -4
  136. data/lib/rubocop/cop/style/not.rb +1 -1
  137. data/lib/rubocop/cop/style/one_line_conditional.rb +25 -4
  138. data/lib/rubocop/cop/style/operator_method_call.rb +5 -6
  139. data/lib/rubocop/cop/style/or_assignment.rb +3 -6
  140. data/lib/rubocop/cop/style/parallel_assignment.rb +8 -13
  141. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  142. data/lib/rubocop/cop/style/redundant_assignment.rb +1 -1
  143. data/lib/rubocop/cop/style/redundant_condition.rb +36 -21
  144. data/lib/rubocop/cop/style/redundant_line_continuation.rb +21 -2
  145. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -11
  146. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -0
  147. data/lib/rubocop/cop/style/redundant_return.rb +2 -2
  148. data/lib/rubocop/cop/style/redundant_self.rb +7 -14
  149. data/lib/rubocop/cop/style/redundant_self_assignment.rb +7 -5
  150. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +4 -4
  151. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  152. data/lib/rubocop/cop/style/rescue_modifier.rb +2 -3
  153. data/lib/rubocop/cop/style/safe_navigation.rb +13 -1
  154. data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
  155. data/lib/rubocop/cop/style/select_by_regexp.rb +1 -1
  156. data/lib/rubocop/cop/style/self_assignment.rb +11 -17
  157. data/lib/rubocop/cop/style/signal_exception.rb +2 -3
  158. data/lib/rubocop/cop/style/single_argument_dig.rb +9 -5
  159. data/lib/rubocop/cop/style/single_line_do_end_block.rb +13 -3
  160. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -3
  161. data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
  162. data/lib/rubocop/cop/style/string_concatenation.rb +0 -1
  163. data/lib/rubocop/cop/style/swap_values.rb +4 -15
  164. data/lib/rubocop/cop/style/ternary_parentheses.rb +25 -4
  165. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +4 -4
  166. data/lib/rubocop/cop/style/variable_interpolation.rb +1 -2
  167. data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
  168. data/lib/rubocop/cop/variable_force/branch.rb +1 -1
  169. data/lib/rubocop/cop/variable_force/variable.rb +5 -1
  170. data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
  171. data/lib/rubocop/cop/variable_force.rb +4 -10
  172. data/lib/rubocop/cops_documentation_generator.rb +20 -10
  173. data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
  174. data/lib/rubocop/runner.rb +16 -8
  175. data/lib/rubocop/target_ruby.rb +1 -1
  176. data/lib/rubocop/version.rb +27 -8
  177. data/lib/rubocop.rb +16 -0
  178. metadata +28 -12
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer bitwise predicate methods over direct comparison operations.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe, as it can produce false positives if the receiver
10
+ # is not an `Integer` object.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad - checks any set bits
15
+ # (variable & flags).positive?
16
+ #
17
+ # # good
18
+ # variable.anybits?(flags)
19
+ #
20
+ # # bad - checks all set bits
21
+ # (variable & flags) == flags
22
+ #
23
+ # # good
24
+ # variable.allbits?(flags)
25
+ #
26
+ # # bad - checks no set bits
27
+ # (variable & flags).zero?
28
+ #
29
+ # # good
30
+ # variable.nobits?(flags)
31
+ #
32
+ class BitwisePredicate < Base
33
+ extend AutoCorrector
34
+ extend TargetRubyVersion
35
+
36
+ MSG = 'Replace with `%<preferred>s` for comparison with bit flags.'
37
+ RESTRICT_ON_SEND = %i[!= == > >= positive? zero?].freeze
38
+
39
+ minimum_target_ruby_version 2.5
40
+
41
+ # @!method anybits?(node)
42
+ def_node_matcher :anybits?, <<~PATTERN
43
+ {
44
+ (send #bit_operation? :positive?)
45
+ (send #bit_operation? :> (int 0))
46
+ (send #bit_operation? :>= (int 1))
47
+ (send #bit_operation? :!= (int 0))
48
+ }
49
+ PATTERN
50
+
51
+ # @!method allbits?(node)
52
+ def_node_matcher :allbits?, <<~PATTERN
53
+ {
54
+ (send (begin (send _ :& _flags)) :== _flags)
55
+ (send (begin (send _flags :& _)) :== _flags)
56
+ }
57
+ PATTERN
58
+
59
+ # @!method nobits?(node)
60
+ def_node_matcher :nobits?, <<~PATTERN
61
+ {
62
+ (send #bit_operation? :zero?)
63
+ (send #bit_operation? :== (int 0))
64
+ }
65
+ PATTERN
66
+
67
+ # @!method bit_operation?(node)
68
+ def_node_matcher :bit_operation?, <<~PATTERN
69
+ (begin
70
+ (send _ :& _))
71
+ PATTERN
72
+
73
+ def on_send(node)
74
+ return unless node.receiver&.begin_type?
75
+ return unless (preferred_method = preferred_method(node))
76
+
77
+ bit_operation = node.receiver.children.first
78
+ lhs, _operator, rhs = *bit_operation
79
+ preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
80
+
81
+ add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
82
+ corrector.replace(node, preferred)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def preferred_method(node)
89
+ if anybits?(node)
90
+ 'anybits?'
91
+ elsif allbits?(node)
92
+ 'allbits?'
93
+ elsif nobits?(node)
94
+ 'nobits?'
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -303,13 +303,28 @@ module RuboCop
303
303
 
304
304
  def move_comment_before_block(corrector, comment, block_node, closing_brace)
305
305
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
306
+
307
+ # It is possible that there is code between the block and the comment
308
+ # which needs to be preserved and trimmed.
309
+ pre_comment_range = source_range_before_comment(range, comment)
310
+
306
311
  corrector.remove(range_with_surrounding_space(comment.source_range, side: :right))
307
- remove_trailing_whitespace(corrector, range, comment)
308
- corrector.insert_after(range, "\n")
312
+ remove_trailing_whitespace(corrector, pre_comment_range, comment)
313
+ corrector.insert_after(pre_comment_range, "\n")
309
314
 
310
315
  corrector.insert_before(block_node, "#{comment.text}\n")
311
316
  end
312
317
 
318
+ def source_range_before_comment(range, comment)
319
+ range = range.end.join(comment.source_range.begin)
320
+
321
+ # End the range before any whitespace that precedes the comment
322
+ trailing_whitespace_count = range.source[/\s+\z/]&.length
323
+ range = range.adjust(end_pos: -trailing_whitespace_count) if trailing_whitespace_count
324
+
325
+ range
326
+ end
327
+
313
328
  def end_of_chain(node)
314
329
  return end_of_chain(node.block_node) if with_block?(node)
315
330
  return node unless node.chained?
@@ -466,7 +481,7 @@ module RuboCop
466
481
  end
467
482
 
468
483
  def conditional?(node)
469
- node.if_type? || node.or_type? || node.and_type?
484
+ node.if_type? || node.operator_keyword?
470
485
  end
471
486
 
472
487
  def array_or_range?(node)
@@ -106,7 +106,7 @@ module RuboCop
106
106
  when :or
107
107
  find_target(node.lhs)
108
108
  when :match_with_lvasgn
109
- lhs, rhs = *node
109
+ lhs, rhs = *node # rubocop:disable InternalAffairs/NodeDestructuring
110
110
  if lhs.regexp_type?
111
111
  rhs
112
112
  elsif rhs.regexp_type?
@@ -172,7 +172,7 @@ module RuboCop
172
172
  return collect_conditions(node.lhs, target, conditions) &&
173
173
  collect_conditions(node.rhs, target, conditions)
174
174
  when :match_with_lvasgn
175
- lhs, rhs = *node
175
+ lhs, rhs = *node # rubocop:disable InternalAffairs/NodeDestructuring
176
176
  condition_from_binary_op(lhs, rhs, target)
177
177
  when :send
178
178
  condition_from_send_node(node, target)
@@ -191,8 +191,7 @@ module RuboCop
191
191
  when :=~, :match, :match?
192
192
  condition_from_match_node(node, target)
193
193
  when :===
194
- lhs, _method, rhs = *node
195
- lhs if rhs == target
194
+ node.receiver if node.first_argument == target
196
195
  when :include?, :cover?
197
196
  condition_from_include_or_cover_node(node, target)
198
197
  end
@@ -200,14 +199,12 @@ module RuboCop
200
199
  # rubocop:enable Metrics/CyclomaticComplexity
201
200
 
202
201
  def condition_from_equality_node(node, target)
203
- lhs, _method, rhs = *node
204
- condition = condition_from_binary_op(lhs, rhs, target)
202
+ condition = condition_from_binary_op(node.receiver, node.first_argument, target)
205
203
  condition if condition && !class_reference?(condition)
206
204
  end
207
205
 
208
206
  def condition_from_match_node(node, target)
209
- lhs, _method, rhs = *node
210
- condition_from_binary_op(lhs, rhs, target)
207
+ condition_from_binary_op(node.receiver, node.first_argument, target)
211
208
  end
212
209
 
213
210
  def condition_from_include_or_cover_node(node, target)
@@ -263,11 +260,11 @@ module RuboCop
263
260
  def regexp_with_working_captures?(node)
264
261
  case node.type
265
262
  when :match_with_lvasgn
266
- lhs, _rhs = *node
263
+ lhs, _rhs = *node # rubocop:disable InternalAffairs/NodeDestructuring
267
264
  node.loc.selector.source == '=~' && regexp_with_named_captures?(lhs)
268
265
  when :send
269
- lhs, method, rhs = *node
270
- method == :match && [lhs, rhs].any? { |n| regexp_with_named_captures?(n) }
266
+ node.method?(:match) &&
267
+ [node.receiver, node.first_argument].any? { |n| regexp_with_named_captures?(n) }
271
268
  end
272
269
  end
273
270
 
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for multiple `defined?` calls joined by `&&` that can be combined
7
+ # into a single `defined?`.
8
+ #
9
+ # When checking that a nested constant or chained method is defined, it is
10
+ # not necessary to check each ancestor or component of the chain.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # defined?(Foo) && defined?(Foo::Bar) && defined?(Foo::Bar::Baz)
15
+ #
16
+ # # good
17
+ # defined?(Foo::Bar::Baz)
18
+ #
19
+ # # bad
20
+ # defined?(foo) && defined?(foo.bar) && defined?(foo.bar.baz)
21
+ #
22
+ # # good
23
+ # defined?(foo.bar.baz)
24
+ class CombinableDefined < Base
25
+ extend AutoCorrector
26
+ include RangeHelp
27
+
28
+ MSG = 'Combine nested `defined?` calls.'
29
+ OPERATORS = %w[&& and].freeze
30
+
31
+ def on_and(node)
32
+ # Only register an offense if all `&&` terms are `defined?` calls
33
+ return unless (terms = terms(node)).all?(&:defined_type?)
34
+
35
+ calls = defined_calls(terms)
36
+ namespaces = namespaces(calls)
37
+
38
+ calls.each do |call|
39
+ next unless namespaces.any?(call)
40
+
41
+ add_offense(node) do |corrector|
42
+ remove_term(corrector, call)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def terms(node)
50
+ node.each_descendant.select do |descendant|
51
+ descendant.parent.and_type? && !descendant.and_type?
52
+ end
53
+ end
54
+
55
+ def defined_calls(nodes)
56
+ nodes.filter_map do |defined_node|
57
+ subject = defined_node.first_argument
58
+ subject if subject.const_type? || subject.call_type?
59
+ end
60
+ end
61
+
62
+ def namespaces(nodes)
63
+ nodes.filter_map do |node|
64
+ if node.respond_to?(:namespace)
65
+ node.namespace
66
+ elsif node.respond_to?(:receiver)
67
+ node.receiver
68
+ end
69
+ end
70
+ end
71
+
72
+ def remove_term(corrector, term)
73
+ term = term.parent until term.parent.and_type?
74
+ range = if term == term.parent.children.last
75
+ rhs_range_to_remove(term)
76
+ else
77
+ lhs_range_to_remove(term)
78
+ end
79
+
80
+ corrector.remove(range)
81
+ end
82
+
83
+ # If the redundant `defined?` node is the LHS of an `and` node,
84
+ # the term as well as the subsequent `&&`/`and` operator will be removed.
85
+ def lhs_range_to_remove(term)
86
+ source = @processed_source.buffer.source
87
+
88
+ pos = term.source_range.end_pos
89
+ pos += 1 until source[..pos].end_with?(*OPERATORS)
90
+
91
+ range_with_surrounding_space(
92
+ range: term.source_range.with(end_pos: pos + 1),
93
+ side: :right,
94
+ newlines: false
95
+ )
96
+ end
97
+
98
+ # If the redundant `defined?` node is the RHS of an `and` node,
99
+ # the term as well as the preceding `&&`/`and` operator will be removed.
100
+ def rhs_range_to_remove(term)
101
+ source = @processed_source.buffer.source
102
+
103
+ pos = term.source_range.begin_pos
104
+ pos -= 1 until source[pos, 3].start_with?(*OPERATORS)
105
+
106
+ range_with_surrounding_space(
107
+ range: term.source_range.with(begin_pos: pos - 1),
108
+ side: :right,
109
+ newlines: false
110
+ )
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -57,6 +57,9 @@ module RuboCop
57
57
 
58
58
  REGEXP = /(?<keyword>\S+).*#/.freeze
59
59
 
60
+ SUBCLASS_DEFINITION = /\A\s*class\s+\w+\s*<\s*\w+/.freeze
61
+ METHOD_DEFINITION = /\A\s*def\s/.freeze
62
+
60
63
  def on_new_investigation
61
64
  processed_source.comments.each do |comment|
62
65
  next unless offensive?(comment) && (match = source_line(comment).match(REGEXP))
@@ -93,7 +96,14 @@ module RuboCop
93
96
  end
94
97
 
95
98
  def rbs_inline_annotation?(line, comment)
96
- comment.text.start_with?('#:') && line.start_with?(/\A\s*def\s/)
99
+ case line
100
+ when SUBCLASS_DEFINITION
101
+ comment.text.start_with?(/#\[.+\]/)
102
+ when METHOD_DEFINITION
103
+ comment.text.start_with?('#:')
104
+ else
105
+ false
106
+ end
97
107
  end
98
108
  end
99
109
  end
@@ -33,24 +33,20 @@ module RuboCop
33
33
  branch.begin_type? ? Array(branch).last : branch
34
34
  end
35
35
 
36
- # rubocop:disable Metrics/AbcSize
37
36
  def lhs(node)
38
37
  case node.type
39
38
  when :send
40
39
  lhs_for_send(node)
41
- when :op_asgn
42
- "#{node.children[0].source} #{node.children[1]}= "
43
- when :and_asgn, :or_asgn
44
- "#{node.children[0].source} #{node.loc.operator.source} "
40
+ when :op_asgn, :and_asgn, :or_asgn
41
+ "#{node.assignment_node.source} #{node.operator}= "
45
42
  when :casgn
46
43
  lhs_for_casgn(node)
47
44
  when *ConditionalAssignment::VARIABLE_ASSIGNMENT_TYPES
48
- "#{node.children[0]} = "
45
+ "#{node.name} = "
49
46
  else
50
47
  node.source
51
48
  end
52
49
  end
53
- # rubocop:enable Metrics/AbcSize
54
50
 
55
51
  def indent(cop, source)
56
52
  conf = cop.config.for_cop(END_ALIGNMENT)
@@ -94,11 +90,12 @@ module RuboCop
94
90
  end
95
91
 
96
92
  def lhs_for_casgn(node)
97
- namespace = node.children[0]
98
- if namespace.nil? || namespace.cbase_type?
99
- "#{namespace&.source}#{node.children[1]} = "
93
+ if node.namespace.nil?
94
+ "#{node.name} = "
95
+ elsif node.namespace.cbase_type?
96
+ "::#{node.name} = "
100
97
  else
101
- "#{namespace.source}::#{node.children[1]} = "
98
+ "#{node.namespace.const_name}::#{node.name} = "
102
99
  end
103
100
  end
104
101
 
@@ -210,7 +207,6 @@ module RuboCop
210
207
  class ConditionalAssignment < Base
211
208
  include ConditionalAssignmentHelper
212
209
  include ConfigurableEnforcedStyle
213
- include IgnoredNode
214
210
  extend AutoCorrector
215
211
 
216
212
  MSG = 'Use the return of the conditional for variable assignment and comparison.'
@@ -317,12 +313,14 @@ module RuboCop
317
313
  end
318
314
 
319
315
  def assignment_node(node)
320
- *_variable, assignment = *node
316
+ assignment = node.send_type? ? node.last_argument : node.expression
321
317
 
322
318
  # ignore pseudo-assignments without rhs in for nodes
323
319
  return if node.parent&.for_type?
324
320
 
325
- assignment, = *assignment if assignment.begin_type? && assignment.children.one?
321
+ if assignment.begin_type? && assignment.children.one?
322
+ assignment = assignment.children.first
323
+ end
326
324
 
327
325
  assignment
328
326
  end
@@ -338,7 +336,7 @@ module RuboCop
338
336
  end
339
337
 
340
338
  def move_assignment_inside_condition(corrector, node)
341
- *_assignment, condition = *node
339
+ condition = node.send_type? ? node.last_argument : node.expression
342
340
 
343
341
  if ternary_condition?(condition)
344
342
  TernaryCorrector.move_assignment_inside_condition(corrector, node)
@@ -459,7 +457,7 @@ module RuboCop
459
457
  end
460
458
 
461
459
  def assignment(node)
462
- *_, condition = *node
460
+ condition = node.send_type? ? node.last_argument : node.expression
463
461
 
464
462
  node.source_range.begin.join(condition.source_range.begin)
465
463
  end
@@ -506,7 +504,7 @@ module RuboCop
506
504
  end
507
505
 
508
506
  def move_assignment_inside_condition(corrector, node)
509
- *_var, rhs = *node
507
+ rhs = node.send_type? ? node.last_argument : node.expression
510
508
  if_branch, else_branch = extract_branches(node)
511
509
  assignment = assignment(node)
512
510
 
@@ -537,8 +535,8 @@ module RuboCop
537
535
  end
538
536
 
539
537
  def extract_branches(node)
540
- *_var, rhs = *node
541
- condition, = *rhs if rhs.begin_type? && rhs.children.one?
538
+ rhs = node.send_type? ? node.last_argument : node.expression
539
+ condition = rhs.children.first if rhs.begin_type? && rhs.children.one?
542
540
  _condition, if_branch, else_branch = *(condition || rhs)
543
541
 
544
542
  [if_branch, else_branch]
@@ -567,7 +565,7 @@ module RuboCop
567
565
 
568
566
  def move_assignment_inside_condition(corrector, node)
569
567
  column = node.source_range.column
570
- *_var, condition = *node
568
+ condition = node.send_type? ? node.last_argument : node.expression
571
569
  assignment = assignment(node)
572
570
 
573
571
  corrector.remove(assignment)
@@ -618,7 +616,7 @@ module RuboCop
618
616
 
619
617
  def move_assignment_inside_condition(corrector, node)
620
618
  column = node.source_range.column
621
- *_var, condition = *node
619
+ condition = node.send_type? ? node.last_argument : node.expression
622
620
  assignment = assignment(node)
623
621
 
624
622
  corrector.remove(assignment)
@@ -53,8 +53,7 @@ module RuboCop
53
53
  return if visibility_declaration?(node)
54
54
  return if ignore_modules? && module?(node)
55
55
 
56
- message = message(node)
57
- add_offense(node, message: message)
56
+ add_offense(node, message: format(MSG, constant_name: node.name))
58
57
  end
59
58
 
60
59
  private
@@ -64,13 +63,7 @@ module RuboCop
64
63
  end
65
64
 
66
65
  def module?(node)
67
- node.children.last.class_constructor?
68
- end
69
-
70
- def message(node)
71
- _namespace, constant_name, _value = *node
72
-
73
- format(MSG, constant_name: constant_name)
66
+ node.expression.class_constructor?
74
67
  end
75
68
 
76
69
  def class_or_module_scope?(node)
@@ -85,10 +78,8 @@ module RuboCop
85
78
  end
86
79
 
87
80
  def visibility_declaration?(node)
88
- _namespace, constant_name, _value = *node
89
-
90
81
  node.parent.each_child_node(:send).any? do |child|
91
- visibility_declaration_for?(child, constant_name)
82
+ visibility_declaration_for?(child, node.name)
92
83
  end
93
84
  end
94
85
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Check for chained `dig` calls that can be collapsed into a single `dig`.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe because it cannot be guaranteed that the receiver
10
+ # is an `Enumerable` or does not have a nonstandard implementation
11
+ # of `dig`.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # x.dig(:foo).dig(:bar).dig(:baz)
16
+ # x.dig(:foo, :bar).dig(:baz)
17
+ # x.dig(:foo, :bar)&.dig(:baz)
18
+ #
19
+ # # good
20
+ # x.dig(:foo, :bar, :baz)
21
+ #
22
+ # # good - `dig`s cannot be combined
23
+ # x.dig(:foo).bar.dig(:baz)
24
+ #
25
+ class DigChain < Base
26
+ extend AutoCorrector
27
+ include RangeHelp
28
+ include CommentsHelp
29
+ include DigHelp
30
+
31
+ MSG = 'Use `%<replacement>s` instead of chaining.'
32
+ RESTRICT_ON_SEND = %i[dig].freeze
33
+
34
+ def on_send(node)
35
+ return if ignored_node?(node)
36
+ return unless node.loc.dot
37
+ return unless dig?(node)
38
+
39
+ range, arguments = inspect_chain(node)
40
+ return if invalid_arguments?(arguments)
41
+ return unless range
42
+
43
+ register_offense(node, range, arguments)
44
+ end
45
+ alias on_csend on_send
46
+
47
+ private
48
+
49
+ # Walk up the method chain while the receiver is `dig` with arguments.
50
+ def inspect_chain(node)
51
+ arguments = node.arguments.dup
52
+ end_pos = node.source_range.end_pos
53
+
54
+ while dig?((node = node.receiver))
55
+ begin_pos = node.loc.dot ? node.loc.dot.begin_pos + 1 : 0
56
+ arguments.unshift(*node.arguments)
57
+ ignore_node(node)
58
+ end
59
+
60
+ return unless begin_pos
61
+
62
+ [range_between(begin_pos, end_pos), arguments]
63
+ end
64
+
65
+ def invalid_arguments?(arguments)
66
+ # If any of the arguments are arguments forwarding (`...`), it can only be the
67
+ # first argument, or else the resulting code will have a syntax error.
68
+
69
+ return false unless arguments&.any?
70
+
71
+ forwarded_args_index = arguments.index(&:forwarded_args_type?)
72
+ forwarded_args_index && forwarded_args_index < (arguments.size - 1)
73
+ end
74
+
75
+ def register_offense(node, range, arguments)
76
+ arguments = arguments.map(&:source).join(', ')
77
+ replacement = "dig(#{arguments})"
78
+
79
+ add_offense(range, message: format(MSG, replacement: replacement)) do |corrector|
80
+ corrector.replace(range, replacement)
81
+
82
+ comments_in_range(node).reverse_each do |comment|
83
+ corrector.insert_before(node, "#{comment.source}\n")
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -48,6 +48,7 @@ module RuboCop
48
48
  #
49
49
  class EndlessMethod < Base
50
50
  include ConfigurableEnforcedStyle
51
+ include EndlessMethodRewriter
51
52
  extend TargetRubyVersion
52
53
  extend AutoCorrector
53
54
 
@@ -81,20 +82,6 @@ module RuboCop
81
82
 
82
83
  add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
83
84
  end
84
-
85
- def correct_to_multiline(corrector, node)
86
- replacement = <<~RUBY.strip
87
- def #{node.method_name}#{arguments(node)}
88
- #{node.body.source}
89
- end
90
- RUBY
91
-
92
- corrector.replace(node, replacement)
93
- end
94
-
95
- def arguments(node, missing = '')
96
- node.arguments.any? ? node.arguments.source : missing
97
- end
98
85
  end
99
86
  end
100
87
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Use `File::NULL` instead of hardcoding the null device (`/dev/null` on Unix-like
7
+ # OSes, `NUL` or `NUL:` on Windows), so that code is platform independent.
8
+ # Only looks for full string matches, substrings within a longer string are not
9
+ # considered.
10
+ #
11
+ # NOTE: Uses inside arrays and hashes are ignored.
12
+ #
13
+ # @safety
14
+ # It is possible for a string value to be changed if code is being run
15
+ # on multiple platforms and was previously hardcoded to a specific null device.
16
+ #
17
+ # For example, the following string will change on Windows when changed to
18
+ # `File::NULL`:
19
+ #
20
+ # [source,ruby]
21
+ # ----
22
+ # path = "/dev/null"
23
+ # ----
24
+ #
25
+ # @example
26
+ # # bad
27
+ # '/dev/null'
28
+ # 'NUL'
29
+ # 'NUL:'
30
+ #
31
+ # # good
32
+ # File::NULL
33
+ #
34
+ # # ok - inside an array
35
+ # null_devices = %w[/dev/null nul]
36
+ #
37
+ # # ok - inside a hash
38
+ # { unix: "/dev/null", windows: "nul" }
39
+ class FileNull < Base
40
+ extend AutoCorrector
41
+
42
+ REGEXP = %r{\A(/dev/null|NUL:?)\z}i.freeze
43
+ MSG = 'Use `File::NULL` instead of `%<source>s`.'
44
+
45
+ def on_str(node)
46
+ value = node.value
47
+
48
+ return if invalid_string?(value)
49
+ return if acceptable?(node)
50
+ return unless REGEXP.match?(value)
51
+
52
+ add_offense(node, message: format(MSG, source: value)) do |corrector|
53
+ corrector.replace(node, 'File::NULL')
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def invalid_string?(value)
60
+ value.empty? || !value.valid_encoding?
61
+ end
62
+
63
+ def acceptable?(node)
64
+ # Using a hardcoded null device is acceptable when inside an array or
65
+ # inside a hash to ensure behavior doesn't change.
66
+ return false unless node.parent
67
+
68
+ node.parent.type?(:array, :pair)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end