rubocop 1.57.2 → 1.62.1

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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +4 -4
  4. data/assets/output.css.erb +159 -0
  5. data/assets/output.html.erb +1 -160
  6. data/config/default.yml +87 -15
  7. data/lib/rubocop/cli/command/auto_generate_config.rb +12 -3
  8. data/lib/rubocop/cli/command/lsp.rb +2 -2
  9. data/lib/rubocop/cli.rb +6 -1
  10. data/lib/rubocop/config.rb +4 -2
  11. data/lib/rubocop/config_finder.rb +12 -2
  12. data/lib/rubocop/config_loader.rb +0 -1
  13. data/lib/rubocop/config_obsoletion.rb +11 -8
  14. data/lib/rubocop/config_validator.rb +14 -7
  15. data/lib/rubocop/cop/autocorrect_logic.rb +6 -1
  16. data/lib/rubocop/cop/base.rb +17 -2
  17. data/lib/rubocop/cop/bundler/gem_comment.rb +2 -2
  18. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +4 -8
  19. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +5 -13
  20. data/lib/rubocop/cop/exclude_limit.rb +1 -1
  21. data/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +2 -2
  22. data/lib/rubocop/cop/gemspec/required_ruby_version.rb +5 -1
  23. data/lib/rubocop/cop/internal_affairs/example_description.rb +4 -4
  24. data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +8 -6
  25. data/lib/rubocop/cop/internal_affairs/method_name_equal.rb +19 -20
  26. data/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb +53 -0
  27. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +123 -29
  28. data/lib/rubocop/cop/internal_affairs/redundant_expect_offense_arguments.rb +34 -0
  29. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  30. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  31. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +14 -7
  32. data/lib/rubocop/cop/layout/end_alignment.rb +8 -2
  33. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -10
  34. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +22 -7
  35. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +1 -1
  36. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  37. data/lib/rubocop/cop/layout/heredoc_indentation.rb +1 -1
  38. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +1 -1
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +16 -3
  40. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +4 -4
  41. data/lib/rubocop/cop/layout/single_line_block_chain.rb +5 -0
  42. data/lib/rubocop/cop/layout/space_around_operators.rb +50 -20
  43. data/lib/rubocop/cop/layout/space_before_block_braces.rb +19 -10
  44. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
  45. data/lib/rubocop/cop/lint/assignment_in_condition.rb +4 -4
  46. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +2 -2
  47. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  48. data/lib/rubocop/cop/lint/debugger.rb +2 -1
  49. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  50. data/lib/rubocop/cop/lint/empty_conditional_body.rb +1 -1
  51. data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -3
  52. data/lib/rubocop/cop/lint/float_comparison.rb +10 -0
  53. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +2 -1
  54. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +56 -0
  55. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +85 -0
  56. data/lib/rubocop/cop/lint/next_without_accumulator.rb +6 -21
  57. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -5
  58. data/lib/rubocop/cop/lint/number_conversion.rb +9 -4
  59. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +54 -6
  60. data/lib/rubocop/cop/lint/redundant_with_index.rb +3 -2
  61. data/lib/rubocop/cop/lint/redundant_with_object.rb +2 -2
  62. data/lib/rubocop/cop/lint/rescue_type.rb +1 -3
  63. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +3 -4
  64. data/lib/rubocop/cop/lint/script_permission.rb +3 -3
  65. data/lib/rubocop/cop/lint/self_assignment.rb +38 -0
  66. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -0
  67. data/lib/rubocop/cop/lint/symbol_conversion.rb +7 -2
  68. data/lib/rubocop/cop/lint/syntax.rb +6 -3
  69. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -2
  70. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -1
  71. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +2 -2
  72. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  73. data/lib/rubocop/cop/lint/useless_times.rb +2 -2
  74. data/lib/rubocop/cop/lint/void.rb +20 -2
  75. data/lib/rubocop/cop/metrics/abc_size.rb +3 -3
  76. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  77. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -0
  78. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
  79. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +1 -1
  80. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  81. data/lib/rubocop/cop/naming/block_forwarding.rb +12 -4
  82. data/lib/rubocop/cop/naming/constant_name.rb +1 -2
  83. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  84. data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
  85. data/lib/rubocop/cop/registry.rb +1 -1
  86. data/lib/rubocop/cop/security/open.rb +2 -2
  87. data/lib/rubocop/cop/style/access_modifier_declarations.rb +2 -2
  88. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -1
  89. data/lib/rubocop/cop/style/arguments_forwarding.rb +152 -21
  90. data/lib/rubocop/cop/style/array_first_last.rb +64 -0
  91. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +21 -14
  92. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +2 -2
  93. data/lib/rubocop/cop/style/case_like_if.rb +5 -5
  94. data/lib/rubocop/cop/style/class_check.rb +1 -0
  95. data/lib/rubocop/cop/style/class_vars.rb +3 -3
  96. data/lib/rubocop/cop/style/collection_compact.rb +18 -8
  97. data/lib/rubocop/cop/style/combinable_loops.rb +13 -7
  98. data/lib/rubocop/cop/style/commented_keyword.rb +5 -2
  99. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -0
  100. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -7
  101. data/lib/rubocop/cop/style/date_time.rb +5 -4
  102. data/lib/rubocop/cop/style/each_for_simple_loop.rb +7 -7
  103. data/lib/rubocop/cop/style/each_with_object.rb +2 -2
  104. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  105. data/lib/rubocop/cop/style/eval_with_location.rb +3 -14
  106. data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
  107. data/lib/rubocop/cop/style/explicit_block_argument.rb +2 -2
  108. data/lib/rubocop/cop/style/for.rb +2 -0
  109. data/lib/rubocop/cop/style/hash_each_methods.rb +105 -11
  110. data/lib/rubocop/cop/style/hash_except.rb +2 -1
  111. data/lib/rubocop/cop/style/hash_syntax.rb +6 -2
  112. data/lib/rubocop/cop/style/identical_conditional_branches.rb +4 -1
  113. data/lib/rubocop/cop/style/inverse_methods.rb +14 -13
  114. data/lib/rubocop/cop/style/invertible_unless_condition.rb +44 -2
  115. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +8 -10
  116. data/lib/rubocop/cop/style/map_to_hash.rb +17 -7
  117. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +14 -5
  118. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -4
  119. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +20 -0
  120. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  121. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +2 -2
  122. data/lib/rubocop/cop/style/multiline_method_signature.rb +10 -1
  123. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +5 -3
  124. data/lib/rubocop/cop/style/next.rb +1 -1
  125. data/lib/rubocop/cop/style/nil_comparison.rb +2 -0
  126. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +1 -1
  127. data/lib/rubocop/cop/style/object_then.rb +5 -3
  128. data/lib/rubocop/cop/style/operator_method_call.rb +2 -2
  129. data/lib/rubocop/cop/style/parallel_assignment.rb +3 -5
  130. data/lib/rubocop/cop/style/parentheses_around_condition.rb +8 -0
  131. data/lib/rubocop/cop/style/raise_args.rb +4 -1
  132. data/lib/rubocop/cop/style/redundant_argument.rb +4 -3
  133. data/lib/rubocop/cop/style/redundant_assignment.rb +10 -2
  134. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +4 -3
  135. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +3 -3
  136. data/lib/rubocop/cop/style/redundant_each.rb +7 -4
  137. data/lib/rubocop/cop/style/redundant_fetch_block.rb +3 -3
  138. data/lib/rubocop/cop/style/redundant_line_continuation.rb +27 -7
  139. data/lib/rubocop/cop/style/redundant_parentheses.rb +33 -10
  140. data/lib/rubocop/cop/style/redundant_return.rb +7 -1
  141. data/lib/rubocop/cop/style/redundant_self.rb +17 -2
  142. data/lib/rubocop/cop/style/redundant_sort.rb +9 -8
  143. data/lib/rubocop/cop/style/redundant_sort_by.rb +2 -2
  144. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -1
  145. data/lib/rubocop/cop/style/sample.rb +3 -4
  146. data/lib/rubocop/cop/style/select_by_regexp.rb +7 -6
  147. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  148. data/lib/rubocop/cop/style/semicolon.rb +8 -0
  149. data/lib/rubocop/cop/style/single_argument_dig.rb +5 -2
  150. data/lib/rubocop/cop/style/slicing_with_range.rb +76 -10
  151. data/lib/rubocop/cop/style/string_chars.rb +1 -0
  152. data/lib/rubocop/cop/style/strip.rb +7 -4
  153. data/lib/rubocop/cop/style/super_with_args_parentheses.rb +35 -0
  154. data/lib/rubocop/cop/style/symbol_proc.rb +36 -0
  155. data/lib/rubocop/cop/style/unpack_first.rb +11 -14
  156. data/lib/rubocop/cop/utils/regexp_ranges.rb +1 -1
  157. data/lib/rubocop/cops_documentation_generator.rb +15 -3
  158. data/lib/rubocop/directive_comment.rb +10 -8
  159. data/lib/rubocop/ext/regexp_node.rb +9 -4
  160. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  161. data/lib/rubocop/formatter/html_formatter.rb +31 -12
  162. data/lib/rubocop/formatter/json_formatter.rb +0 -1
  163. data/lib/rubocop/formatter/offense_count_formatter.rb +12 -2
  164. data/lib/rubocop/formatter.rb +1 -1
  165. data/lib/rubocop/lsp/logger.rb +1 -1
  166. data/lib/rubocop/lsp/routes.rb +2 -2
  167. data/lib/rubocop/lsp/runtime.rb +1 -1
  168. data/lib/rubocop/lsp/server.rb +5 -2
  169. data/lib/rubocop/lsp/severity.rb +1 -1
  170. data/lib/rubocop/lsp.rb +29 -0
  171. data/lib/rubocop/magic_comment.rb +1 -1
  172. data/lib/rubocop/options.rb +11 -8
  173. data/lib/rubocop/path_util.rb +6 -2
  174. data/lib/rubocop/result_cache.rb +0 -1
  175. data/lib/rubocop/rspec/cop_helper.rb +8 -2
  176. data/lib/rubocop/rspec/expect_offense.rb +8 -8
  177. data/lib/rubocop/rspec/shared_contexts.rb +40 -15
  178. data/lib/rubocop/rspec/support.rb +2 -0
  179. data/lib/rubocop/runner.rb +10 -3
  180. data/lib/rubocop/server/cache.rb +1 -2
  181. data/lib/rubocop/server/client_command/exec.rb +0 -1
  182. data/lib/rubocop/server/server_command/exec.rb +0 -1
  183. data/lib/rubocop/target_finder.rb +84 -78
  184. data/lib/rubocop/target_ruby.rb +82 -80
  185. data/lib/rubocop/version.rb +18 -3
  186. data/lib/rubocop.rb +4 -0
  187. metadata +18 -10
  188. /data/lib/rubocop/formatter/{git_hub_actions_formatter.rb → github_actions_formatter.rb} +0 -0
@@ -49,12 +49,12 @@ module RuboCop
49
49
  class DateTime < Base
50
50
  extend AutoCorrector
51
51
 
52
- CLASS_MSG = 'Prefer Time over DateTime.'
53
- COERCION_MSG = 'Do not use #to_datetime.'
52
+ CLASS_MSG = 'Prefer `Time` over `DateTime`.'
53
+ COERCION_MSG = 'Do not use `#to_datetime`.'
54
54
 
55
55
  # @!method date_time?(node)
56
56
  def_node_matcher :date_time?, <<~PATTERN
57
- (send (const {nil? (cbase)} :DateTime) ...)
57
+ (call (const {nil? (cbase)} :DateTime) ...)
58
58
  PATTERN
59
59
 
60
60
  # @!method historic_date?(node)
@@ -64,7 +64,7 @@ module RuboCop
64
64
 
65
65
  # @!method to_datetime?(node)
66
66
  def_node_matcher :to_datetime?, <<~PATTERN
67
- (send _ :to_datetime)
67
+ (call _ :to_datetime)
68
68
  PATTERN
69
69
 
70
70
  def on_send(node)
@@ -75,6 +75,7 @@ module RuboCop
75
75
 
76
76
  add_offense(node, message: message) { |corrector| autocorrect(corrector, node) }
77
77
  end
78
+ alias on_csend on_send
78
79
 
79
80
  private
80
81
 
@@ -32,27 +32,27 @@ module RuboCop
32
32
 
33
33
  send_node = node.send_node
34
34
 
35
- range = send_node.receiver.source_range.join(send_node.loc.selector)
36
-
37
- add_offense(range) do |corrector|
35
+ add_offense(send_node) do |corrector|
38
36
  range_type, min, max = each_range(node)
39
37
 
40
38
  max += 1 if range_type == :irange
41
39
 
42
- corrector.replace(node.send_node, "#{max - min}.times")
40
+ corrector.replace(send_node, "#{max - min}.times")
43
41
  end
44
42
  end
45
43
 
46
44
  private
47
45
 
48
46
  def offending?(node)
47
+ return false unless node.arguments.empty?
48
+
49
49
  each_range_with_zero_origin?(node) || each_range_without_block_argument?(node)
50
50
  end
51
51
 
52
52
  # @!method each_range(node)
53
53
  def_node_matcher :each_range, <<~PATTERN
54
54
  (block
55
- (send
55
+ (call
56
56
  (begin
57
57
  (${irange erange}
58
58
  (int $_) (int $_)))
@@ -64,7 +64,7 @@ module RuboCop
64
64
  # @!method each_range_with_zero_origin?(node)
65
65
  def_node_matcher :each_range_with_zero_origin?, <<~PATTERN
66
66
  (block
67
- (send
67
+ (call
68
68
  (begin
69
69
  ({irange erange}
70
70
  (int 0) (int _)))
@@ -76,7 +76,7 @@ module RuboCop
76
76
  # @!method each_range_without_block_argument?(node)
77
77
  def_node_matcher :each_range_without_block_argument?, <<~PATTERN
78
78
  (block
79
- (send
79
+ (call
80
80
  (begin
81
81
  ({irange erange}
82
82
  (int _) (int _)))
@@ -58,12 +58,12 @@ module RuboCop
58
58
 
59
59
  # @!method each_with_object_block_candidate?(node)
60
60
  def_node_matcher :each_with_object_block_candidate?, <<~PATTERN
61
- (block $(send _ {:inject :reduce} _) $_ $_)
61
+ (block $(call _ {:inject :reduce} _) $_ $_)
62
62
  PATTERN
63
63
 
64
64
  # @!method each_with_object_numblock_candidate?(node)
65
65
  def_node_matcher :each_with_object_numblock_candidate?, <<~PATTERN
66
- (numblock $(send _ {:inject :reduce} _) 2 $_)
66
+ (numblock $(call _ {:inject :reduce} _) 2 $_)
67
67
  PATTERN
68
68
 
69
69
  def autocorrect_block(corrector, node, return_value)
@@ -83,7 +83,7 @@ module RuboCop
83
83
  parent = node.parent
84
84
  return false unless parent && %i[send super zsuper].include?(parent.type)
85
85
 
86
- node.equal?(parent.arguments.first) && !parentheses?(node.parent)
86
+ node.equal?(parent.first_argument) && !parentheses?(node.parent)
87
87
  end
88
88
 
89
89
  def replacement_range(node)
@@ -84,7 +84,7 @@ module RuboCop
84
84
  # are considered.
85
85
  return if node.method?(:eval) && !valid_eval_receiver?(node.receiver)
86
86
 
87
- code = node.arguments.first
87
+ code = node.first_argument
88
88
  return unless code && (code.str_type? || code.dstr_type?)
89
89
 
90
90
  check_location(node, code)
@@ -128,17 +128,6 @@ module RuboCop
128
128
  node.method?(:eval) ? node.arguments.size >= 2 : true
129
129
  end
130
130
 
131
- # FIXME: It's a Style/ConditionalAssignment's false positive.
132
- # rubocop:disable Style/ConditionalAssignment
133
- def with_lineno?(node)
134
- if node.method?(:eval)
135
- node.arguments.size == 4
136
- else
137
- node.arguments.size == 3
138
- end
139
- end
140
- # rubocop:enable Style/ConditionalAssignment
141
-
142
131
  def add_offense_for_incorrect_line(method_name, line_node, sign, line_diff)
143
132
  expected = expected_line(sign, line_diff)
144
133
  message = format(MSG_INCORRECT_LINE,
@@ -165,7 +154,7 @@ module RuboCop
165
154
  end
166
155
 
167
156
  def check_line(node, code)
168
- line_node = node.arguments.last
157
+ line_node = node.last_argument
169
158
  line_diff = line_difference(line_node, code)
170
159
  if line_diff.zero?
171
160
  add_offense_for_same_line(node, line_node)
@@ -227,7 +216,7 @@ module RuboCop
227
216
  end
228
217
 
229
218
  def missing_line(node, code)
230
- line_diff = line_difference(node.arguments.last, code)
219
+ line_diff = line_difference(node.last_argument, code)
231
220
  sign = line_diff.positive? ? :+ : :-
232
221
  expected_line(sign, line_diff)
233
222
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  # @!method exact_regexp_match(node)
32
32
  def_node_matcher :exact_regexp_match, <<~PATTERN
33
- (send
33
+ (call
34
34
  _ {:=~ :=== :!~ :match :match?}
35
35
  (regexp
36
36
  (str $_)
@@ -49,6 +49,7 @@ module RuboCop
49
49
  corrector.replace(node, prefer)
50
50
  end
51
51
  end
52
+ alias on_csend on_send
52
53
 
53
54
  private
54
55
 
@@ -86,7 +86,7 @@ module RuboCop
86
86
 
87
87
  def extract_block_name(def_node)
88
88
  if def_node.block_argument?
89
- def_node.arguments.last.name
89
+ def_node.last_argument.name
90
90
  else
91
91
  'block'
92
92
  end
@@ -127,7 +127,7 @@ module RuboCop
127
127
  end
128
128
 
129
129
  def insert_argument(node, corrector, block_name)
130
- last_arg = node.arguments.last
130
+ last_arg = node.last_argument
131
131
  arg_range = range_with_surrounding_comma(last_arg.source_range, :right)
132
132
  replacement = " &#{block_name}"
133
133
  replacement = ",#{replacement}" unless arg_range.source.end_with?(',')
@@ -66,6 +66,8 @@ module RuboCop
66
66
  return unless suspect_enumerable?(node)
67
67
 
68
68
  if style == :for
69
+ return unless node.receiver
70
+
69
71
  add_offense(node, message: PREFER_FOR) do |corrector|
70
72
  EachToForCorrector.new(node).call(corrector)
71
73
  opposite_style_detected
@@ -17,10 +17,16 @@ module RuboCop
17
17
  # @example
18
18
  # # bad
19
19
  # hash.keys.each { |k| p k }
20
- # hash.values.each { |v| p v }
20
+ # hash.each { |k, unused_value| p k }
21
21
  #
22
22
  # # good
23
23
  # hash.each_key { |k| p k }
24
+ #
25
+ # # bad
26
+ # hash.values.each { |v| p v }
27
+ # hash.each { |unused_key, v| p v }
28
+ #
29
+ # # good
24
30
  # hash.each_value { |v| p v }
25
31
  #
26
32
  # @example AllowedReceivers: ['execute']
@@ -33,25 +39,59 @@ module RuboCop
33
39
  extend AutoCorrector
34
40
 
35
41
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
42
+ UNUSED_BLOCK_ARG_MSG = "#{MSG.chop} and remove the unused `%<unused_code>s` block argument."
43
+ ARRAY_CONVERTER_METHODS = %i[assoc chunk flatten rassoc sort sort_by to_a].freeze
36
44
 
37
45
  # @!method kv_each(node)
38
46
  def_node_matcher :kv_each, <<~PATTERN
39
- ({block numblock} $(send (send _ ${:keys :values}) :each) ...)
47
+ ({block numblock} $(call (call _ ${:keys :values}) :each) ...)
48
+ PATTERN
49
+
50
+ # @!method each_arguments(node)
51
+ def_node_matcher :each_arguments, <<~PATTERN
52
+ (block (call _ :each)(args $_key $_value) ...)
40
53
  PATTERN
41
54
 
42
55
  # @!method kv_each_with_block_pass(node)
43
56
  def_node_matcher :kv_each_with_block_pass, <<~PATTERN
44
- (send $(send _ ${:keys :values}) :each (block_pass (sym _)))
57
+ (call $(call _ ${:keys :values}) :each (block_pass (sym _)))
45
58
  PATTERN
46
59
 
47
60
  def on_block(node)
61
+ return unless handleable?(node)
62
+
48
63
  kv_each(node) do |target, method|
49
- register_kv_offense(target, method)
64
+ register_kv_offense(target, method) and return
50
65
  end
51
- end
52
66
 
67
+ return unless (key, value = each_arguments(node))
68
+
69
+ check_unused_block_args(node, key, value)
70
+ end
53
71
  alias on_numblock on_block
54
72
 
73
+ # rubocop:disable Metrics/AbcSize
74
+ def check_unused_block_args(node, key, value)
75
+ return if node.body.nil?
76
+
77
+ value_unused = unused_block_arg_exist?(node, value)
78
+ key_unused = unused_block_arg_exist?(node, key)
79
+ return if value_unused && key_unused
80
+
81
+ if value_unused
82
+ message = message('each_key', node.method_name, value.source)
83
+ unused_range = key.source_range.end.join(value.source_range.end)
84
+
85
+ register_each_args_offense(node, message, 'each_key', unused_range)
86
+ elsif key_unused
87
+ message = message('each_value', node.method_name, key.source)
88
+ unused_range = key.source_range.begin.join(value.source_range.begin)
89
+
90
+ register_each_args_offense(node, message, 'each_value', unused_range)
91
+ end
92
+ end
93
+ # rubocop:enable Metrics/AbcSize
94
+
55
95
  def on_block_pass(node)
56
96
  kv_each_with_block_pass(node.parent) do |target, method|
57
97
  register_kv_with_block_pass_offense(node, target, method)
@@ -60,27 +100,81 @@ module RuboCop
60
100
 
61
101
  private
62
102
 
103
+ def handleable?(node)
104
+ return false if use_array_converter_method_as_preceding?(node)
105
+ return false unless (root_receiver = root_receiver(node))
106
+
107
+ !root_receiver.literal? || root_receiver.hash_type?
108
+ end
109
+
63
110
  def register_kv_offense(target, method)
64
111
  return unless (parent_receiver = target.receiver.receiver)
65
112
  return if allowed_receiver?(parent_receiver)
66
113
 
67
- add_offense(kv_range(target), message: format_message(method)) do |corrector|
114
+ current = target.receiver.loc.selector.join(target.source_range.end).source
115
+
116
+ add_offense(kv_range(target), message: format_message(method, current)) do |corrector|
68
117
  correct_key_value_each(target, corrector)
69
118
  end
70
119
  end
71
120
 
121
+ def unused_block_arg_exist?(node, block_arg)
122
+ lvar_sources = node.body.each_descendant(:lvar).map(&:source)
123
+
124
+ if block_arg.mlhs_type?
125
+ block_arg.each_descendant(:arg, :restarg).all? do |block_arg|
126
+ lvar_sources.none?(block_arg.source.delete_prefix('*'))
127
+ end
128
+ else
129
+ lvar_sources.none?(block_arg.source.delete_prefix('*'))
130
+ end
131
+ end
132
+
133
+ def message(prefer, method_name, unused_code)
134
+ format(
135
+ UNUSED_BLOCK_ARG_MSG, prefer: prefer, current: method_name, unused_code: unused_code
136
+ )
137
+ end
138
+
139
+ def register_each_args_offense(node, message, prefer, unused_range)
140
+ add_offense(node, message: message) do |corrector|
141
+ corrector.replace(node.send_node.loc.selector, prefer)
142
+ corrector.remove(unused_range)
143
+ end
144
+ end
145
+
72
146
  def register_kv_with_block_pass_offense(node, target, method)
73
147
  return unless (parent_receiver = node.parent.receiver.receiver)
74
148
  return if allowed_receiver?(parent_receiver)
75
149
 
76
- range = target.loc.selector.with(end_pos: node.parent.loc.selector.end_pos)
77
- add_offense(range, message: format_message(method)) do |corrector|
150
+ range = target.loc.selector.join(node.parent.loc.selector.end)
151
+
152
+ add_offense(range, message: format_message(method, range.source)) do |corrector|
78
153
  corrector.replace(range, "each_#{method[0..-2]}")
79
154
  end
80
155
  end
81
156
 
82
- def format_message(method_name)
83
- format(MSG, prefer: "each_#{method_name[0..-2]}", current: "#{method_name}.each")
157
+ def use_array_converter_method_as_preceding?(node)
158
+ return false unless (preceding_method = node.children.first.children.first)
159
+ unless preceding_method.call_type? ||
160
+ preceding_method.block_type? || preceding_method.numblock_type?
161
+ return false
162
+ end
163
+
164
+ ARRAY_CONVERTER_METHODS.include?(preceding_method.method_name)
165
+ end
166
+
167
+ def root_receiver(node)
168
+ receiver = node.receiver
169
+ if receiver&.receiver
170
+ root_receiver(receiver)
171
+ else
172
+ receiver
173
+ end
174
+ end
175
+
176
+ def format_message(method_name, current)
177
+ format(MSG, prefer: "each_#{method_name[0..-2]}", current: current)
84
178
  end
85
179
 
86
180
  def check_argument(variable)
@@ -103,7 +197,7 @@ module RuboCop
103
197
  name = "each_#{node.receiver.method_name.to_s.chop}"
104
198
  return correct_implicit(node, corrector, name) unless receiver
105
199
 
106
- new_source = receiver.source + ".#{name}"
200
+ new_source = receiver.source + "#{node.loc.dot.source}#{name}"
107
201
  corrector.replace(node, new_source)
108
202
  end
109
203
 
@@ -43,7 +43,7 @@ module RuboCop
43
43
  # @!method bad_method_with_poro?(node)
44
44
  def_node_matcher :bad_method_with_poro?, <<~PATTERN
45
45
  (block
46
- (send _ _)
46
+ (call _ _)
47
47
  (args
48
48
  $(arg _)
49
49
  (arg _))
@@ -86,6 +86,7 @@ module RuboCop
86
86
  corrector.replace(range, preferred_method)
87
87
  end
88
88
  end
89
+ alias on_csend on_send
89
90
 
90
91
  private
91
92
 
@@ -195,6 +195,7 @@ module RuboCop
195
195
  acceptable_19_syntax_symbol?(pair.key.source)
196
196
  end
197
197
 
198
+ # rubocop:disable Metrics/CyclomaticComplexity
198
199
  def acceptable_19_syntax_symbol?(sym_name)
199
200
  sym_name.delete_prefix!(':')
200
201
 
@@ -209,9 +210,12 @@ module RuboCop
209
210
  # Most hash keys can be matched against a simple regex.
210
211
  return true if /\A[_a-z]\w*[?!]?\z/i.match?(sym_name)
211
212
 
212
- # For more complicated hash keys, let the parser validate the syntax.
213
- parse("{ #{sym_name}: :foo }").valid_syntax?
213
+ return false if target_ruby_version <= 2.1
214
+
215
+ (sym_name.start_with?("'") && sym_name.end_with?("'")) ||
216
+ (sym_name.start_with?('"') && sym_name.end_with?('"'))
214
217
  end
218
+ # rubocop:enable Metrics/CyclomaticComplexity
215
219
 
216
220
  def check(pairs, delim, msg)
217
221
  pairs.each do |pair|
@@ -158,7 +158,10 @@ module RuboCop
158
158
  if head.assignment?
159
159
  # The `send` node is used instead of the `indexasgn` node, so `name` cannot be used.
160
160
  # https://github.com/rubocop/rubocop-ast/blob/v1.29.0/lib/rubocop/ast/node/indexasgn_node.rb
161
- assigned_value = head.send_type? ? head.receiver.source : head.name.to_s
161
+ #
162
+ # FIXME: It would be better to update `RuboCop::AST::OpAsgnNode` or its subclasses to
163
+ # handle `self.foo ||= value` as a solution, instead of using `head.node_parts[0].to_s`.
164
+ assigned_value = head.send_type? ? head.receiver.source : head.node_parts[0].to_s
162
165
 
163
166
  return if condition_variable == assigned_value
164
167
  end
@@ -60,25 +60,25 @@ module RuboCop
60
60
  # @!method inverse_candidate?(node)
61
61
  def_node_matcher :inverse_candidate?, <<~PATTERN
62
62
  {
63
- (send $(send $(...) $_ $...) :!)
64
- (send ({block numblock} $(send $(...) $_) $...) :!)
65
- (send (begin $(send $(...) $_ $...)) :!)
63
+ (send $(call $(...) $_ $...) :!)
64
+ (send ({block numblock} $(call $(...) $_) $...) :!)
65
+ (send (begin $(call $(...) $_ $...)) :!)
66
66
  }
67
67
  PATTERN
68
68
 
69
69
  # @!method inverse_block?(node)
70
70
  def_node_matcher :inverse_block?, <<~PATTERN
71
- ({block numblock} $(send (...) $_) ... { $(send ... :!)
71
+ ({block numblock} $(call (...) $_) ... { $(call ... :!)
72
72
  $(send (...) {:!= :!~} ...)
73
- (begin ... $(send ... :!))
73
+ (begin ... $(call ... :!))
74
74
  (begin ... $(send (...) {:!= :!~} ...))
75
75
  })
76
76
  PATTERN
77
77
 
78
78
  def on_send(node)
79
- inverse_candidate?(node) do |_method_call, lhs, method, rhs|
79
+ inverse_candidate?(node) do |method_call, lhs, method, rhs|
80
80
  return unless inverse_methods.key?(method)
81
- return if negated?(node)
81
+ return if negated?(node) || relational_comparison_with_safe_navigation?(method_call)
82
82
  return if part_of_ignored_node?(node)
83
83
  return if possible_class_hierarchy_check?(lhs, rhs, method)
84
84
 
@@ -87,6 +87,7 @@ module RuboCop
87
87
  end
88
88
  end
89
89
  end
90
+ alias on_csend on_send
90
91
 
91
92
  def on_block(node)
92
93
  inverse_block?(node) do |_method_call, method, block|
@@ -154,16 +155,16 @@ module RuboCop
154
155
  node.parent.respond_to?(:method?) && node.parent.method?(:!)
155
156
  end
156
157
 
158
+ def relational_comparison_with_safe_navigation?(node)
159
+ node.csend_type? && CLASS_COMPARISON_METHODS.include?(node.method_name)
160
+ end
161
+
157
162
  def not_to_receiver(node, method_call)
158
- Parser::Source::Range.new(node.source_range.source_buffer,
159
- node.loc.selector.begin_pos,
160
- method_call.source_range.begin_pos)
163
+ node.loc.selector.begin.join(method_call.source_range.begin)
161
164
  end
162
165
 
163
166
  def end_parentheses(node, method_call)
164
- Parser::Source::Range.new(node.source_range.source_buffer,
165
- method_call.source_range.end_pos,
166
- node.source_range.end_pos)
167
+ method_call.source_range.end.join(node.source_range.end)
167
168
  end
168
169
 
169
170
  # When comparing classes, `!(Integer < Numeric)` is not the same as
@@ -32,12 +32,14 @@ module RuboCop
32
32
  # foo unless x != y
33
33
  # foo unless x >= 10
34
34
  # foo unless x.even?
35
+ # foo unless odd?
35
36
  #
36
37
  # # good
37
38
  # foo if bar
38
39
  # foo if x == y
39
40
  # foo if x < 10
40
41
  # foo if x.odd?
42
+ # foo if even?
41
43
  #
42
44
  # # bad (complex condition)
43
45
  # foo unless x != y || x.even?
@@ -51,7 +53,7 @@ module RuboCop
51
53
  class InvertibleUnlessCondition < Base
52
54
  extend AutoCorrector
53
55
 
54
- MSG = 'Favor `if` with inverted condition over `unless`.'
56
+ MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
55
57
 
56
58
  def on_if(node)
57
59
  return unless node.unless?
@@ -59,7 +61,10 @@ module RuboCop
59
61
  condition = node.condition
60
62
  return unless invertible?(condition)
61
63
 
62
- add_offense(node) do |corrector|
64
+ message = format(MSG, prefer: "#{node.inverse_keyword} #{preferred_condition(condition)}",
65
+ current: "#{node.keyword} #{condition.source}")
66
+
67
+ add_offense(node, message: message) do |corrector|
63
68
  corrector.replace(node.loc.keyword, node.inverse_keyword)
64
69
  autocorrect(corrector, condition)
65
70
  end
@@ -88,6 +93,43 @@ module RuboCop
88
93
  (argument.const_type? && argument.short_name.to_s.upcase != argument.short_name.to_s)
89
94
  end
90
95
 
96
+ def preferred_condition(node)
97
+ case node.type
98
+ when :begin then "(#{preferred_condition(node.children.first)})"
99
+ when :send then preferred_send_condition(node)
100
+ when :or, :and then preferred_logical_condition(node)
101
+ end
102
+ end
103
+
104
+ def preferred_send_condition(node) # rubocop:disable Metrics/CyclomaticComplexity
105
+ receiver_source = node.receiver&.source
106
+ return receiver_source if node.method?(:!)
107
+
108
+ # receiver may be implicit (self)
109
+ dotted_receiver_source = receiver_source ? "#{receiver_source}." : ''
110
+
111
+ inverse_method_name = inverse_methods[node.method_name]
112
+ return "#{dotted_receiver_source}#{inverse_method_name}" unless node.arguments?
113
+
114
+ argument_list = node.arguments.map(&:source).join(', ')
115
+ if node.operator_method?
116
+ return "#{receiver_source} #{inverse_method_name} #{argument_list}"
117
+ end
118
+
119
+ if node.parenthesized?
120
+ return "#{dotted_receiver_source}#{inverse_method_name}(#{argument_list})"
121
+ end
122
+
123
+ "#{dotted_receiver_source}#{inverse_method_name} #{argument_list}"
124
+ end
125
+
126
+ def preferred_logical_condition(node)
127
+ preferred_lhs = preferred_condition(node.lhs)
128
+ preferred_rhs = preferred_condition(node.rhs)
129
+
130
+ "#{preferred_lhs} #{node.inverse_operator} #{preferred_rhs}"
131
+ end
132
+
91
133
  def autocorrect(corrector, node)
92
134
  case node.type
93
135
  when :begin
@@ -44,9 +44,9 @@ module RuboCop
44
44
 
45
45
  # @!method map_and_compact?(node)
46
46
  def_node_matcher :map_and_compact?, <<~RUBY
47
- (send
47
+ (call
48
48
  (block
49
- (send _ :map)
49
+ (call _ :map)
50
50
  (args
51
51
  $(arg _))
52
52
  {
@@ -85,6 +85,7 @@ module RuboCop
85
85
  end
86
86
  end
87
87
  end
88
+ alias on_csend on_send
88
89
 
89
90
  private
90
91
 
@@ -115,20 +116,17 @@ module RuboCop
115
116
  def truthy_branch_for_guard?(node)
116
117
  if_node = node.left_sibling
117
118
 
118
- if if_node.if? || if_node.ternary?
119
- if_node.else_branch.nil?
120
- elsif if_node.unless?
121
- if_node.if_branch.nil?
119
+ if if_node.if?
120
+ if_node.if_branch.arguments.any?
121
+ else
122
+ if_node.if_branch.arguments.none?
122
123
  end
123
124
  end
124
125
 
125
126
  def range(node)
126
- buffer = node.source_range.source_buffer
127
127
  map_node = node.receiver.send_node
128
- begin_pos = map_node.loc.selector.begin_pos
129
- end_pos = node.source_range.end_pos
130
128
 
131
- Parser::Source::Range.new(buffer, begin_pos, end_pos)
129
+ map_node.loc.selector.join(node.source_range.end)
132
130
  end
133
131
  end
134
132
  end
@@ -34,21 +34,25 @@ module RuboCop
34
34
 
35
35
  minimum_target_ruby_version 2.6
36
36
 
37
- MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
37
+ MSG = 'Pass a block to `to_h` instead of calling `%<method>s%<dot>sto_h`.'
38
38
  RESTRICT_ON_SEND = %i[to_h].freeze
39
39
 
40
- # @!method map_to_h?(node)
41
- def_node_matcher :map_to_h?, <<~PATTERN
40
+ # @!method map_to_h(node)
41
+ def_node_matcher :map_to_h, <<~PATTERN
42
42
  {
43
- $(send ({block numblock} $(send _ {:map :collect}) ...) :to_h)
44
- $(send $(send _ {:map :collect} (block_pass sym)) :to_h)
43
+ $(call ({block numblock} $(call _ {:map :collect}) ...) :to_h)
44
+ $(call $(call _ {:map :collect} (block_pass sym)) :to_h)
45
45
  }
46
46
  PATTERN
47
47
 
48
+ def self.autocorrect_incompatible_with
49
+ [Layout::SingleLineBlockChain]
50
+ end
51
+
48
52
  def on_send(node)
49
- return unless (to_h_node, map_node = map_to_h?(node))
53
+ return unless (to_h_node, map_node = map_to_h(node))
50
54
 
51
- message = format(MSG, method: map_node.loc.selector.source)
55
+ message = format(MSG, method: map_node.loc.selector.source, dot: to_h_node.loc.dot.source)
52
56
  add_offense(map_node.loc.selector, message: message) do |corrector|
53
57
  # If the `to_h` call already has a block, do not autocorrect.
54
58
  next if to_h_node.block_node
@@ -56,15 +60,21 @@ module RuboCop
56
60
  autocorrect(corrector, to_h_node, map_node)
57
61
  end
58
62
  end
63
+ alias on_csend on_send
59
64
 
60
65
  private
61
66
 
67
+ # rubocop:disable Metrics/AbcSize
62
68
  def autocorrect(corrector, to_h, map)
63
69
  removal_range = range_between(to_h.loc.dot.begin_pos, to_h.loc.selector.end_pos)
64
70
 
65
71
  corrector.remove(range_with_surrounding_space(removal_range, side: :left))
72
+ if (map_dot = map.loc.dot)
73
+ corrector.replace(map_dot, to_h.loc.dot.source)
74
+ end
66
75
  corrector.replace(map.loc.selector, 'to_h')
67
76
  end
77
+ # rubocop:enable Metrics/AbcSize
68
78
  end
69
79
  end
70
80
  end