rubocop 1.53.0 → 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -3
  4. data/config/default.yml +80 -11
  5. data/config/obsoletion.yml +5 -0
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +10 -5
  7. data/lib/rubocop/cli.rb +1 -1
  8. data/lib/rubocop/config.rb +0 -2
  9. data/lib/rubocop/config_finder.rb +2 -2
  10. data/lib/rubocop/config_loader.rb +0 -1
  11. data/lib/rubocop/config_obsoletion/parameter_rule.rb +9 -1
  12. data/lib/rubocop/config_obsoletion.rb +11 -8
  13. data/lib/rubocop/config_validator.rb +0 -2
  14. data/lib/rubocop/cop/autocorrect_logic.rb +3 -1
  15. data/lib/rubocop/cop/base.rb +7 -1
  16. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -0
  17. data/lib/rubocop/cop/bundler/duplicated_group.rb +127 -0
  18. data/lib/rubocop/cop/bundler/gem_comment.rb +2 -2
  19. data/lib/rubocop/cop/bundler/ordered_gems.rb +9 -1
  20. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -4
  21. data/lib/rubocop/cop/exclude_limit.rb +1 -1
  22. data/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +2 -2
  23. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +9 -1
  24. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  25. data/lib/rubocop/cop/internal_affairs/example_description.rb +42 -21
  26. data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +3 -1
  27. data/lib/rubocop/cop/internal_affairs/method_name_equal.rb +19 -20
  28. data/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb +53 -0
  29. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -2
  30. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +11 -2
  31. data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +2 -0
  32. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  33. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  34. data/lib/rubocop/cop/layout/dot_position.rb +1 -5
  35. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +42 -9
  36. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +26 -3
  37. data/lib/rubocop/cop/layout/end_alignment.rb +12 -2
  38. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -10
  39. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +22 -7
  40. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +1 -1
  41. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  42. data/lib/rubocop/cop/layout/heredoc_indentation.rb +4 -1
  43. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  44. data/lib/rubocop/cop/layout/leading_comment_space.rb +1 -1
  45. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +17 -9
  46. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  47. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +2 -0
  48. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
  49. data/lib/rubocop/cop/layout/redundant_line_break.rb +15 -4
  50. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +4 -4
  51. data/lib/rubocop/cop/layout/single_line_block_chain.rb +5 -0
  52. data/lib/rubocop/cop/layout/space_after_comma.rb +9 -1
  53. data/lib/rubocop/cop/layout/space_after_not.rb +1 -1
  54. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +2 -2
  55. data/lib/rubocop/cop/layout/space_around_operators.rb +53 -21
  56. data/lib/rubocop/cop/layout/space_inside_parens.rb +1 -1
  57. data/lib/rubocop/cop/layout/trailing_empty_lines.rb +5 -0
  58. data/lib/rubocop/cop/lint/assignment_in_condition.rb +4 -4
  59. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +2 -2
  60. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +1 -1
  61. data/lib/rubocop/cop/lint/debugger.rb +18 -4
  62. data/lib/rubocop/cop/lint/duplicate_methods.rb +1 -1
  63. data/lib/rubocop/cop/lint/empty_block.rb +1 -1
  64. data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -3
  65. data/lib/rubocop/cop/lint/float_comparison.rb +10 -0
  66. data/lib/rubocop/cop/lint/hash_compare_by_identity.rb +2 -1
  67. data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +56 -0
  68. data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +85 -0
  69. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
  70. data/lib/rubocop/cop/lint/mixed_case_range.rb +3 -1
  71. data/lib/rubocop/cop/lint/next_without_accumulator.rb +6 -21
  72. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +10 -7
  73. data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +3 -5
  74. data/lib/rubocop/cop/lint/number_conversion.rb +9 -4
  75. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +10 -0
  76. data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -0
  77. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +63 -4
  78. data/lib/rubocop/cop/lint/redundant_with_index.rb +2 -2
  79. data/lib/rubocop/cop/lint/redundant_with_object.rb +2 -2
  80. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +14 -8
  81. data/lib/rubocop/cop/lint/self_assignment.rb +38 -0
  82. data/lib/rubocop/cop/lint/shadowed_argument.rb +1 -0
  83. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +7 -1
  84. data/lib/rubocop/cop/lint/struct_new_override.rb +12 -12
  85. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  86. data/lib/rubocop/cop/lint/symbol_conversion.rb +8 -3
  87. data/lib/rubocop/cop/lint/syntax.rb +6 -3
  88. data/lib/rubocop/cop/lint/to_enum_arguments.rb +5 -3
  89. data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -1
  90. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +2 -2
  91. data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
  92. data/lib/rubocop/cop/lint/useless_assignment.rb +38 -12
  93. data/lib/rubocop/cop/lint/useless_times.rb +1 -1
  94. data/lib/rubocop/cop/lint/void.rb +46 -21
  95. data/lib/rubocop/cop/metrics/abc_size.rb +3 -3
  96. data/lib/rubocop/cop/metrics/block_length.rb +1 -1
  97. data/lib/rubocop/cop/metrics/class_length.rb +8 -3
  98. data/lib/rubocop/cop/metrics/method_length.rb +1 -1
  99. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +32 -4
  100. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  101. data/lib/rubocop/cop/mixin/comments_help.rb +16 -12
  102. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -0
  103. data/lib/rubocop/cop/mixin/def_node.rb +1 -1
  104. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +14 -11
  105. data/lib/rubocop/cop/mixin/heredoc.rb +6 -2
  106. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -2
  107. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +6 -8
  108. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  109. data/lib/rubocop/cop/mixin/string_help.rb +4 -2
  110. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  111. data/lib/rubocop/cop/naming/block_forwarding.rb +12 -4
  112. data/lib/rubocop/cop/naming/constant_name.rb +1 -2
  113. data/lib/rubocop/cop/naming/file_name.rb +1 -1
  114. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +3 -1
  115. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
  116. data/lib/rubocop/cop/registry.rb +4 -4
  117. data/lib/rubocop/cop/security/open.rb +2 -2
  118. data/lib/rubocop/cop/style/access_modifier_declarations.rb +2 -2
  119. data/lib/rubocop/cop/style/accessor_grouping.rb +1 -1
  120. data/lib/rubocop/cop/style/alias.rb +9 -8
  121. data/lib/rubocop/cop/style/arguments_forwarding.rb +355 -63
  122. data/lib/rubocop/cop/style/array_first_last.rb +64 -0
  123. data/lib/rubocop/cop/style/array_intersect.rb +13 -5
  124. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +21 -14
  125. data/lib/rubocop/cop/style/bisected_attr_accessor.rb +2 -2
  126. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  127. data/lib/rubocop/cop/style/case_like_if.rb +4 -4
  128. data/lib/rubocop/cop/style/class_check.rb +1 -0
  129. data/lib/rubocop/cop/style/class_equality_comparison.rb +7 -0
  130. data/lib/rubocop/cop/style/collection_compact.rb +7 -6
  131. data/lib/rubocop/cop/style/collection_methods.rb +2 -0
  132. data/lib/rubocop/cop/style/combinable_loops.rb +17 -9
  133. data/lib/rubocop/cop/style/concat_array_literals.rb +2 -1
  134. data/lib/rubocop/cop/style/conditional_assignment.rb +2 -2
  135. data/lib/rubocop/cop/style/date_time.rb +5 -4
  136. data/lib/rubocop/cop/style/each_for_simple_loop.rb +7 -7
  137. data/lib/rubocop/cop/style/each_with_object.rb +2 -2
  138. data/lib/rubocop/cop/style/empty_case_condition.rb +6 -1
  139. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  140. data/lib/rubocop/cop/style/eval_with_location.rb +3 -14
  141. data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -1
  142. data/lib/rubocop/cop/style/explicit_block_argument.rb +2 -2
  143. data/lib/rubocop/cop/style/for.rb +1 -1
  144. data/lib/rubocop/cop/style/format_string.rb +24 -3
  145. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +3 -1
  146. data/lib/rubocop/cop/style/guard_clause.rb +26 -0
  147. data/lib/rubocop/cop/style/hash_conversion.rb +10 -0
  148. data/lib/rubocop/cop/style/hash_each_methods.rb +83 -10
  149. data/lib/rubocop/cop/style/hash_except.rb +2 -1
  150. data/lib/rubocop/cop/style/identical_conditional_branches.rb +28 -3
  151. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  152. data/lib/rubocop/cop/style/inverse_methods.rb +6 -5
  153. data/lib/rubocop/cop/style/invertible_unless_condition.rb +39 -2
  154. data/lib/rubocop/cop/style/lambda.rb +3 -3
  155. data/lib/rubocop/cop/style/lambda_call.rb +5 -0
  156. data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +3 -2
  157. data/lib/rubocop/cop/style/map_to_hash.rb +17 -7
  158. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -6
  159. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -4
  160. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +20 -0
  161. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  162. data/lib/rubocop/cop/style/missing_respond_to_missing.rb +2 -2
  163. data/lib/rubocop/cop/style/mixin_grouping.rb +1 -1
  164. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  165. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +2 -4
  166. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -11
  167. data/lib/rubocop/cop/style/next.rb +1 -1
  168. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +1 -1
  169. data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
  170. data/lib/rubocop/cop/style/operator_method_call.rb +8 -2
  171. data/lib/rubocop/cop/style/parallel_assignment.rb +2 -2
  172. data/lib/rubocop/cop/style/parentheses_around_condition.rb +8 -0
  173. data/lib/rubocop/cop/style/redundant_argument.rb +9 -3
  174. data/lib/rubocop/cop/style/redundant_begin.rb +9 -1
  175. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -9
  176. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +1 -1
  177. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +93 -5
  178. data/lib/rubocop/cop/style/redundant_each.rb +7 -4
  179. data/lib/rubocop/cop/style/redundant_exception.rb +32 -12
  180. data/lib/rubocop/cop/style/redundant_fetch_block.rb +3 -3
  181. data/lib/rubocop/cop/style/redundant_filter_chain.rb +22 -5
  182. data/lib/rubocop/cop/style/redundant_line_continuation.rb +10 -1
  183. data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -21
  184. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +18 -4
  185. data/lib/rubocop/cop/style/redundant_return.rb +8 -3
  186. data/lib/rubocop/cop/style/redundant_self.rb +17 -2
  187. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +5 -0
  188. data/lib/rubocop/cop/style/redundant_sort.rb +9 -8
  189. data/lib/rubocop/cop/style/redundant_sort_by.rb +2 -2
  190. data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -1
  191. data/lib/rubocop/cop/style/return_nil.rb +6 -2
  192. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +23 -9
  193. data/lib/rubocop/cop/style/sample.rb +2 -1
  194. data/lib/rubocop/cop/style/select_by_regexp.rb +7 -6
  195. data/lib/rubocop/cop/style/self_assignment.rb +1 -1
  196. data/lib/rubocop/cop/style/semicolon.rb +8 -3
  197. data/lib/rubocop/cop/style/single_argument_dig.rb +7 -3
  198. data/lib/rubocop/cop/style/single_line_do_end_block.rb +67 -0
  199. data/lib/rubocop/cop/style/slicing_with_range.rb +76 -10
  200. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  201. data/lib/rubocop/cop/style/string_chars.rb +1 -0
  202. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +30 -5
  203. data/lib/rubocop/cop/style/strip.rb +7 -4
  204. data/lib/rubocop/cop/style/super_with_args_parentheses.rb +35 -0
  205. data/lib/rubocop/cop/style/symbol_array.rb +35 -15
  206. data/lib/rubocop/cop/style/symbol_proc.rb +36 -0
  207. data/lib/rubocop/cop/style/unpack_first.rb +11 -14
  208. data/lib/rubocop/cop/style/yoda_condition.rb +4 -2
  209. data/lib/rubocop/cop/style/yoda_expression.rb +8 -7
  210. data/lib/rubocop/cop/utils/regexp_ranges.rb +26 -13
  211. data/lib/rubocop/cop/variable_force/assignment.rb +14 -5
  212. data/lib/rubocop/cops_documentation_generator.rb +11 -1
  213. data/lib/rubocop/ext/regexp_node.rb +9 -4
  214. data/lib/rubocop/file_finder.rb +4 -7
  215. data/lib/rubocop/formatter/disabled_config_formatter.rb +17 -6
  216. data/lib/rubocop/formatter/html_formatter.rb +5 -4
  217. data/lib/rubocop/formatter/json_formatter.rb +0 -1
  218. data/lib/rubocop/formatter/junit_formatter.rb +1 -1
  219. data/lib/rubocop/formatter.rb +1 -1
  220. data/lib/rubocop/lsp/routes.rb +42 -19
  221. data/lib/rubocop/lsp/runtime.rb +22 -2
  222. data/lib/rubocop/lsp/server.rb +10 -4
  223. data/lib/rubocop/magic_comment.rb +12 -10
  224. data/lib/rubocop/options.rb +0 -8
  225. data/lib/rubocop/result_cache.rb +4 -1
  226. data/lib/rubocop/rspec/shared_contexts.rb +8 -3
  227. data/lib/rubocop/rspec/support.rb +1 -0
  228. data/lib/rubocop/runner.rb +6 -4
  229. data/lib/rubocop/server/client_command/exec.rb +1 -1
  230. data/lib/rubocop/string_interpreter.rb +3 -3
  231. data/lib/rubocop/target_finder.rb +7 -3
  232. data/lib/rubocop/target_ruby.rb +9 -5
  233. data/lib/rubocop/version.rb +1 -1
  234. data/lib/rubocop.rb +6 -0
  235. metadata +20 -13
  236. /data/lib/rubocop/formatter/{git_hub_actions_formatter.rb → github_actions_formatter.rb} +0 -0
@@ -8,6 +8,21 @@ module RuboCop
8
8
  # This cop identifies places where `do_something(*args, &block)`
9
9
  # can be replaced by `do_something(...)`.
10
10
  #
11
+ # In Ruby 3.2, anonymous args/kwargs forwarding has been added.
12
+ #
13
+ # This cop also identifies places where `use_args(*args)`/`use_kwargs(**kwargs)` can be
14
+ # replaced by `use_args(*)`/`use_kwargs(**)`; if desired, this functionality can be disabled
15
+ # by setting `UseAnonymousForwarding: false`.
16
+ #
17
+ # And this cop has `RedundantRestArgumentNames`, `RedundantKeywordRestArgumentNames`,
18
+ # and `RedundantBlockArgumentNames` options. This configuration is a list of redundant names
19
+ # that are sufficient for anonymizing meaningless naming.
20
+ #
21
+ # Meaningless names that are commonly used can be anonymized by default:
22
+ # e.g., `*args`, `**options`, `&block`, and so on.
23
+ #
24
+ # Names not on this list are likely to be meaningful and are allowed by default.
25
+ #
11
26
  # @example
12
27
  # # bad
13
28
  # def foo(*args, &block)
@@ -24,7 +39,27 @@ module RuboCop
24
39
  # bar(...)
25
40
  # end
26
41
  #
27
- # @example AllowOnlyRestArgument: true (default)
42
+ # @example UseAnonymousForwarding: true (default, only relevant for Ruby >= 3.2)
43
+ # # bad
44
+ # def foo(*args, **kwargs)
45
+ # args_only(*args)
46
+ # kwargs_only(**kwargs)
47
+ # end
48
+ #
49
+ # # good
50
+ # def foo(*, **)
51
+ # args_only(*)
52
+ # kwargs_only(**)
53
+ # end
54
+ #
55
+ # @example UseAnonymousForwarding: false (only relevant for Ruby >= 3.2)
56
+ # # good
57
+ # def foo(*args, **kwargs)
58
+ # args_only(*args)
59
+ # kwargs_only(**kwargs)
60
+ # end
61
+ #
62
+ # @example AllowOnlyRestArgument: true (default, only relevant for Ruby < 3.2)
28
63
  # # good
29
64
  # def foo(*args)
30
65
  # bar(*args)
@@ -34,7 +69,7 @@ module RuboCop
34
69
  # bar(**kwargs)
35
70
  # end
36
71
  #
37
- # @example AllowOnlyRestArgument: false
72
+ # @example AllowOnlyRestArgument: false (only relevant for Ruby < 3.2)
38
73
  # # bad
39
74
  # # The following code can replace the arguments with `...`,
40
75
  # # but it will change the behavior. Because `...` forwards block also.
@@ -46,6 +81,38 @@ module RuboCop
46
81
  # bar(**kwargs)
47
82
  # end
48
83
  #
84
+ # @example RedundantRestArgumentNames: ['args', 'arguments'] (default)
85
+ # # bad
86
+ # def foo(*args)
87
+ # bar(*args)
88
+ # end
89
+ #
90
+ # # good
91
+ # def foo(*)
92
+ # bar(*)
93
+ # end
94
+ #
95
+ # @example RedundantKeywordRestArgumentNames: ['kwargs', 'options', 'opts'] (default)
96
+ # # bad
97
+ # def foo(**kwargs)
98
+ # bar(**kwargs)
99
+ # end
100
+ #
101
+ # # good
102
+ # def foo(**)
103
+ # bar(**)
104
+ # end
105
+ #
106
+ # @example RedundantBlockArgumentNames: ['blk', 'block', 'proc'] (default)
107
+ # # bad
108
+ # def foo(&block)
109
+ # bar(&block)
110
+ # end
111
+ #
112
+ # # good
113
+ # def foo(&)
114
+ # bar(&)
115
+ # end
49
116
  class ArgumentsForwarding < Base
50
117
  include RangeHelp
51
118
  extend AutoCorrector
@@ -53,102 +120,327 @@ module RuboCop
53
120
 
54
121
  minimum_target_ruby_version 2.7
55
122
 
56
- MSG = 'Use arguments forwarding.'
57
-
58
- # @!method use_rest_arguments?(node)
59
- def_node_matcher :use_rest_arguments?, <<~PATTERN
60
- (args ({restarg kwrestarg} $_) $...)
61
- PATTERN
62
-
63
- # @!method only_rest_arguments?(node, name)
64
- def_node_matcher :only_rest_arguments?, <<~PATTERN
65
- {
66
- (send _ _ (splat (lvar %1)))
67
- (send _ _ (hash (kwsplat (lvar %1))))
68
- }
69
- PATTERN
70
-
71
- # @!method forwarding_method_arguments?(node, rest_name, block_name, kwargs_name)
72
- def_node_matcher :forwarding_method_arguments?, <<~PATTERN
73
- {
74
- (send _ _
75
- (splat (lvar %1))
76
- (block-pass {(lvar %2) nil?}))
77
- (send _ _
78
- (splat (lvar %1))
79
- (hash (kwsplat (lvar %3)))
80
- (block-pass {(lvar %2) nil?}))
81
- }
82
- PATTERN
123
+ FORWARDING_LVAR_TYPES = %i[splat kwsplat block_pass].freeze
124
+ ADDITIONAL_ARG_TYPES = %i[lvar arg].freeze
125
+
126
+ FORWARDING_MSG = 'Use shorthand syntax `...` for arguments forwarding.'
127
+ ARGS_MSG = 'Use anonymous positional arguments forwarding (`*`).'
128
+ KWARGS_MSG = 'Use anonymous keyword arguments forwarding (`**`).'
129
+
130
+ def self.autocorrect_incompatible_with
131
+ [Naming::BlockForwarding]
132
+ end
83
133
 
84
134
  def on_def(node)
85
135
  return unless node.body
86
- return unless (rest_args_name, args = use_rest_arguments?(node.arguments))
87
- return if args.any?(&:default?)
88
136
 
89
- node.each_descendant(:send) do |send_node|
90
- kwargs_name, block_name = extract_argument_names_from(args)
137
+ restarg, kwrestarg, blockarg = extract_forwardable_args(node.arguments)
138
+ forwardable_args = redundant_forwardable_named_args(restarg, kwrestarg, blockarg)
139
+ send_nodes = node.each_descendant(:send).to_a
140
+
141
+ send_classifications = classify_send_nodes(
142
+ node, send_nodes, non_splat_or_block_pass_lvar_references(node.body), forwardable_args
143
+ )
91
144
 
92
- next unless forwarding_method?(send_node, rest_args_name, kwargs_name, block_name) &&
93
- all_lvars_as_forwarding_method_arguments?(node, send_node)
145
+ return if send_classifications.empty?
94
146
 
95
- register_offense_to_forwarding_method_arguments(send_node)
96
- register_offense_to_method_definition_arguments(node)
147
+ if only_forwards_all?(send_classifications)
148
+ add_forward_all_offenses(node, send_classifications, forwardable_args)
149
+ elsif target_ruby_version >= 3.2
150
+ add_post_ruby_32_offenses(node, send_classifications, forwardable_args)
97
151
  end
98
152
  end
153
+
99
154
  alias on_defs on_def
100
155
 
101
156
  private
102
157
 
103
- def extract_argument_names_from(args)
104
- kwargs_name = args.first.source.delete('**') if args.first&.kwrestarg_type?
105
- block_arg_name = args.last.source.delete('&') if args.last&.blockarg_type?
106
-
107
- [kwargs_name, block_arg_name].map { |name| name&.to_sym }
158
+ def extract_forwardable_args(args)
159
+ [args.find(&:restarg_type?), args.find(&:kwrestarg_type?), args.find(&:blockarg_type?)]
108
160
  end
109
161
 
110
- def forwarding_method?(node, rest_arg, kwargs, block_arg)
111
- return only_rest_arguments?(node, rest_arg) unless allow_only_rest_arguments?
162
+ def redundant_forwardable_named_args(restarg, kwrestarg, blockarg)
163
+ restarg_node = redundant_named_arg(restarg, 'RedundantRestArgumentNames', '*')
164
+ kwrestarg_node = redundant_named_arg(kwrestarg, 'RedundantKeywordRestArgumentNames', '**')
165
+ blockarg_node = redundant_named_arg(blockarg, 'RedundantBlockArgumentNames', '&')
112
166
 
113
- forwarding_method_arguments?(node, rest_arg, block_arg, kwargs)
167
+ [restarg_node, kwrestarg_node, blockarg_node]
114
168
  end
115
169
 
116
- def all_lvars_as_forwarding_method_arguments?(def_node, forwarding_method)
117
- lvars = def_node.body.each_descendant(:lvar, :lvasgn)
170
+ def only_forwards_all?(send_classifications)
171
+ send_classifications.all? { |_, c, _, _| c == :all }
172
+ end
118
173
 
119
- begin_pos = forwarding_method.source_range.begin_pos
120
- end_pos = forwarding_method.source_range.end_pos
174
+ def add_forward_all_offenses(node, send_classifications, forwardable_args)
175
+ send_classifications.each do |send_node, _c, forward_rest, _forward_kwrest|
176
+ register_forward_all_offense(send_node, send_node, forward_rest)
177
+ end
121
178
 
122
- lvars.all? { |lvar| lvar.source_range.begin_pos.between?(begin_pos, end_pos) }
179
+ rest_arg, _kwrest_arg, _block_arg = *forwardable_args
180
+ register_forward_all_offense(node, node.arguments, rest_arg)
123
181
  end
124
182
 
125
- def register_offense_to_forwarding_method_arguments(forwarding_method)
126
- add_offense(arguments_range(forwarding_method)) do |corrector|
127
- begin_pos = forwarding_method.loc.selector&.end_pos || forwarding_method.loc.dot.end_pos
128
- range = range_between(begin_pos, forwarding_method.source_range.end_pos)
183
+ def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
184
+ return unless use_anonymous_forwarding?
185
+
186
+ rest_arg, kwrest_arg, _block_arg = *forwardable_args
187
+
188
+ send_classifications.each do |send_node, _c, forward_rest, forward_kwrest|
189
+ if outside_block?(forward_rest)
190
+ register_forward_args_offense(def_node.arguments, rest_arg)
191
+ register_forward_args_offense(send_node, forward_rest)
192
+ end
129
193
 
130
- corrector.replace(range, '(...)')
194
+ if outside_block?(forward_kwrest)
195
+ register_forward_kwargs_offense(!forward_rest, def_node.arguments, kwrest_arg)
196
+ register_forward_kwargs_offense(!forward_rest, send_node, forward_kwrest)
197
+ end
131
198
  end
132
199
  end
133
200
 
134
- def register_offense_to_method_definition_arguments(method_definition)
135
- add_offense(arguments_range(method_definition)) do |corrector|
136
- arguments_range = range_with_surrounding_space(
137
- method_definition.arguments.source_range, side: :left
201
+ def non_splat_or_block_pass_lvar_references(body)
202
+ body.each_descendant(:lvar, :lvasgn).filter_map do |lvar|
203
+ parent = lvar.parent
204
+
205
+ next if lvar.lvar_type? && FORWARDING_LVAR_TYPES.include?(parent.type)
206
+
207
+ lvar.children.first
208
+ end.uniq
209
+ end
210
+
211
+ def classify_send_nodes(def_node, send_nodes, referenced_lvars, forwardable_args)
212
+ send_nodes.filter_map do |send_node|
213
+ classification_and_forwards = classification_and_forwards(
214
+ def_node,
215
+ send_node,
216
+ referenced_lvars,
217
+ forwardable_args
138
218
  )
139
- corrector.replace(arguments_range, '(...)')
219
+
220
+ next unless classification_and_forwards
221
+
222
+ [send_node, *classification_and_forwards]
140
223
  end
141
224
  end
142
225
 
143
- def arguments_range(node)
144
- arguments = node.arguments
226
+ def classification_and_forwards(def_node, send_node, referenced_lvars, forwardable_args)
227
+ classifier = SendNodeClassifier.new(
228
+ def_node,
229
+ send_node,
230
+ referenced_lvars,
231
+ forwardable_args,
232
+ target_ruby_version: target_ruby_version,
233
+ allow_only_rest_arguments: allow_only_rest_arguments?
234
+ )
235
+
236
+ classification = classifier.classification
145
237
 
146
- range_between(arguments.first.source_range.begin_pos, arguments.last.source_range.end_pos)
238
+ return unless classification
239
+
240
+ [classification, classifier.forwarded_rest_arg, classifier.forwarded_kwrest_arg]
241
+ end
242
+
243
+ def redundant_named_arg(arg, config_name, keyword)
244
+ return nil unless arg
245
+
246
+ redundant_arg_names = cop_config.fetch(config_name, []).map do |redundant_arg_name|
247
+ "#{keyword}#{redundant_arg_name}"
248
+ end << keyword
249
+
250
+ redundant_arg_names.include?(arg.source) ? arg : nil
251
+ end
252
+
253
+ def outside_block?(node)
254
+ return false unless node
255
+
256
+ node.each_ancestor(:block, :numblock).none?
257
+ end
258
+
259
+ def register_forward_args_offense(def_arguments_or_send, rest_arg_or_splat)
260
+ add_offense(rest_arg_or_splat, message: ARGS_MSG) do |corrector|
261
+ add_parens_if_missing(def_arguments_or_send, corrector)
262
+
263
+ corrector.replace(rest_arg_or_splat, '*')
264
+ end
265
+ end
266
+
267
+ def register_forward_kwargs_offense(add_parens, def_arguments_or_send, kwrest_arg_or_splat)
268
+ add_offense(kwrest_arg_or_splat, message: KWARGS_MSG) do |corrector|
269
+ add_parens_if_missing(def_arguments_or_send, corrector) if add_parens
270
+
271
+ corrector.replace(kwrest_arg_or_splat, '**')
272
+ end
273
+ end
274
+
275
+ def register_forward_all_offense(def_or_send, send_or_arguments, rest_or_splat)
276
+ arg_range = arguments_range(def_or_send, rest_or_splat)
277
+
278
+ add_offense(arg_range, message: FORWARDING_MSG) do |corrector|
279
+ add_parens_if_missing(send_or_arguments, corrector)
280
+
281
+ corrector.replace(arg_range, '...')
282
+ end
283
+ end
284
+
285
+ def arguments_range(node, first_node)
286
+ arguments = node.arguments.reject { |arg| ADDITIONAL_ARG_TYPES.include?(arg.type) }
287
+
288
+ start_node = first_node || arguments.first
289
+
290
+ range_between(start_node.source_range.begin_pos, arguments.last.source_range.end_pos)
147
291
  end
148
292
 
149
293
  def allow_only_rest_arguments?
150
294
  cop_config.fetch('AllowOnlyRestArgument', true)
151
295
  end
296
+
297
+ def use_anonymous_forwarding?
298
+ cop_config.fetch('UseAnonymousForwarding', false)
299
+ end
300
+
301
+ def add_parens_if_missing(node, corrector)
302
+ return if parentheses?(node)
303
+
304
+ add_parentheses(node, corrector)
305
+ end
306
+
307
+ # Classifies send nodes for possible rest/kwrest/all (including block) forwarding.
308
+ class SendNodeClassifier
309
+ extend NodePattern::Macros
310
+
311
+ # @!method forwarded_rest_arg?(node, rest_name)
312
+ def_node_matcher :forwarded_rest_arg?, '(splat (lvar %1))'
313
+
314
+ # @!method extract_forwarded_kwrest_arg(node, kwrest_name)
315
+ def_node_matcher :extract_forwarded_kwrest_arg, '(hash <$(kwsplat (lvar %1)) ...>)'
316
+
317
+ # @!method forwarded_block_arg?(node, block_name)
318
+ def_node_matcher :forwarded_block_arg?, '(block_pass {(lvar %1) nil?})'
319
+
320
+ def initialize(def_node, send_node, referenced_lvars, forwardable_args, **config)
321
+ @def_node = def_node
322
+ @send_node = send_node
323
+ @referenced_lvars = referenced_lvars
324
+ @rest_arg, @kwrest_arg, @block_arg = *forwardable_args
325
+ @rest_arg_name, @kwrest_arg_name, @block_arg_name =
326
+ *forwardable_args.map { |a| a&.name }
327
+ @config = config
328
+ end
329
+
330
+ def forwarded_rest_arg
331
+ return nil if referenced_rest_arg?
332
+
333
+ arguments.find { |arg| forwarded_rest_arg?(arg, @rest_arg_name) }
334
+ end
335
+
336
+ def forwarded_kwrest_arg
337
+ return nil if referenced_kwrest_arg?
338
+
339
+ arguments.filter_map { |arg| extract_forwarded_kwrest_arg(arg, @kwrest_arg_name) }.first
340
+ end
341
+
342
+ def forwarded_block_arg
343
+ return nil if referenced_block_arg?
344
+
345
+ arguments.find { |arg| forwarded_block_arg?(arg, @block_arg_name) }
346
+ end
347
+
348
+ def classification
349
+ return nil unless forwarded_rest_arg || forwarded_kwrest_arg || forwarded_block_arg
350
+
351
+ if can_forward_all?
352
+ :all
353
+ else
354
+ :rest_or_kwrest
355
+ end
356
+ end
357
+
358
+ private
359
+
360
+ def can_forward_all?
361
+ return false if any_arg_referenced?
362
+ return false if ruby_32_missing_rest_or_kwest?
363
+ return false unless offensive_block_forwarding?
364
+ return false if additional_kwargs_or_forwarded_kwargs?
365
+
366
+ no_additional_args? || (target_ruby_version >= 3.0 && no_post_splat_args?)
367
+ end
368
+
369
+ def ruby_32_missing_rest_or_kwest?
370
+ target_ruby_version >= 3.2 && !forwarded_rest_and_kwrest_args
371
+ end
372
+
373
+ def offensive_block_forwarding?
374
+ @block_arg ? forwarded_block_arg : allow_offense_for_no_block?
375
+ end
376
+
377
+ def forwarded_rest_and_kwrest_args
378
+ forwarded_rest_arg && forwarded_kwrest_arg
379
+ end
380
+
381
+ def arguments
382
+ @send_node.arguments
383
+ end
384
+
385
+ def referenced_rest_arg?
386
+ @referenced_lvars.include?(@rest_arg_name)
387
+ end
388
+
389
+ def referenced_kwrest_arg?
390
+ @referenced_lvars.include?(@kwrest_arg_name)
391
+ end
392
+
393
+ def referenced_block_arg?
394
+ @referenced_lvars.include?(@block_arg_name)
395
+ end
396
+
397
+ def any_arg_referenced?
398
+ referenced_rest_arg? || referenced_kwrest_arg? || referenced_block_arg?
399
+ end
400
+
401
+ def target_ruby_version
402
+ @config.fetch(:target_ruby_version)
403
+ end
404
+
405
+ def no_post_splat_args?
406
+ return true unless (splat_index = arguments.index(forwarded_rest_arg))
407
+
408
+ arg_after_splat = arguments[splat_index + 1]
409
+ [nil, :hash, :block_pass].include?(arg_after_splat&.type)
410
+ end
411
+
412
+ def additional_kwargs_or_forwarded_kwargs?
413
+ additional_kwargs? || forward_additional_kwargs?
414
+ end
415
+
416
+ def additional_kwargs?
417
+ @def_node.arguments.any? { |a| a.kwarg_type? || a.kwoptarg_type? }
418
+ end
419
+
420
+ def forward_additional_kwargs?
421
+ return false unless forwarded_kwrest_arg
422
+
423
+ !forwarded_kwrest_arg.parent.children.one?
424
+ end
425
+
426
+ def allow_offense_for_no_block?
427
+ !@config.fetch(:allow_only_rest_arguments)
428
+ end
429
+
430
+ def no_additional_args?
431
+ forwardable_count = [@rest_arg, @kwrest_arg, @block_arg].compact.size
432
+
433
+ return false if missing_rest_arg_or_kwrest_arg?
434
+
435
+ @def_node.arguments.size == forwardable_count &&
436
+ @send_node.arguments.size == forwardable_count
437
+ end
438
+
439
+ def missing_rest_arg_or_kwrest_arg?
440
+ (@rest_arg_name && !forwarded_rest_arg) ||
441
+ (@kwrest_arg_name && !forwarded_kwrest_arg)
442
+ end
443
+ end
152
444
  end
153
445
  end
154
446
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Identifies usages of `arr[0]` and `arr[-1]` and suggests to change
7
+ # them to use `arr.first` and `arr.last` instead.
8
+ #
9
+ # The cop is disabled by default due to safety concerns.
10
+ #
11
+ # @safety
12
+ # This cop is unsafe because `[0]` or `[-1]` can be called on a Hash,
13
+ # which returns a value for `0` or `-1` key, but changing these to use
14
+ # `.first` or `.last` will return first/last tuple instead. Also, String
15
+ # does not implement `first`/`last` methods.
16
+ #
17
+ # @example
18
+ # # bad
19
+ # arr[0]
20
+ # arr[-1]
21
+ #
22
+ # # good
23
+ # arr.first
24
+ # arr.last
25
+ # arr[0] = 2
26
+ # arr[0][-2]
27
+ #
28
+ class ArrayFirstLast < Base
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Use `%<preferred>s`.'
32
+ RESTRICT_ON_SEND = %i[[]].freeze
33
+
34
+ # rubocop:disable Metrics/AbcSize
35
+ def on_send(node)
36
+ return unless node.arguments.size == 1 && node.first_argument.int_type?
37
+
38
+ value = node.first_argument.value
39
+ return unless [0, -1].include?(value)
40
+
41
+ node = innermost_braces_node(node)
42
+ return if node.parent && brace_method?(node.parent)
43
+
44
+ preferred = (value.zero? ? 'first' : 'last')
45
+ add_offense(node.loc.selector, message: format(MSG, preferred: preferred)) do |corrector|
46
+ corrector.replace(node.loc.selector, ".#{preferred}")
47
+ end
48
+ end
49
+ # rubocop:enable Metrics/AbcSize
50
+
51
+ private
52
+
53
+ def innermost_braces_node(node)
54
+ node = node.receiver while node.receiver.send_type? && node.receiver.method?(:[])
55
+ node
56
+ end
57
+
58
+ def brace_method?(node)
59
+ node.send_type? && (node.method?(:[]) || node.method?(:[]=))
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -11,6 +11,15 @@ module RuboCop
11
11
  # The `array1.intersect?(array2)` method is faster than
12
12
  # `(array1 & array2).any?` and is more readable.
13
13
  #
14
+ # In cases like the following, compatibility is not ensured,
15
+ # so it will not be detected when using block argument.
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # ([1] & [1,2]).any? { |x| false } # => false
20
+ # [1].intersect?([1,2]) { |x| false } # => true
21
+ # ----
22
+ #
14
23
  # @safety
15
24
  # This cop cannot guarantee that `array1` and `array2` are
16
25
  # actually arrays while method `intersect?` is for arrays only.
@@ -68,16 +77,15 @@ module RuboCop
68
77
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
69
78
 
70
79
  def on_send(node)
80
+ return if (parent = node.parent) && (parent.block_type? || parent.numblock_type?)
71
81
  return unless (receiver, argument, method_name = bad_intersection_check?(node))
72
82
 
73
83
  message = message(receiver.source, argument.source, method_name)
74
84
 
75
85
  add_offense(node, message: message) do |corrector|
76
- if straight?(method_name)
77
- corrector.replace(node, "#{receiver.source}.intersect?(#{argument.source})")
78
- else
79
- corrector.replace(node, "!#{receiver.source}.intersect?(#{argument.source})")
80
- end
86
+ bang = straight?(method_name) ? '' : '!'
87
+
88
+ corrector.replace(node, "#{bang}#{receiver.source}.intersect?(#{argument.source})")
81
89
  end
82
90
  end
83
91
 
@@ -16,31 +16,38 @@ module RuboCop
16
16
  # File.open('file') do |f|
17
17
  # # ...
18
18
  # end
19
+ #
20
+ # # bad
21
+ # f = Tempfile.open('temp')
22
+ #
23
+ # # good
24
+ # Tempfile.open('temp') do |f|
25
+ # # ...
26
+ # end
19
27
  class AutoResourceCleanup < Base
20
- MSG = 'Use the block version of `%<class>s.%<method>s`.'
21
-
22
- TARGET_METHODS = { File: :open }.freeze
28
+ MSG = 'Use the block version of `%<current>s`.'
29
+ RESTRICT_ON_SEND = %i[open].freeze
23
30
 
24
- RESTRICT_ON_SEND = TARGET_METHODS.values.freeze
31
+ # @!method file_open_method?(node)
32
+ def_node_matcher :file_open_method?, <<~PATTERN
33
+ (send (const {nil? cbase} {:File :Tempfile}) :open ...)
34
+ PATTERN
25
35
 
26
36
  def on_send(node)
27
- TARGET_METHODS.each do |target_class, target_method|
28
- next if node.method_name != target_method
37
+ return if !file_open_method?(node) || cleanup?(node)
29
38
 
30
- target_receiver = s(:const, nil, target_class)
31
- next if node.receiver != target_receiver
39
+ current = node.receiver.source_range.begin.join(node.selector.end).source
32
40
 
33
- next if cleanup?(node)
34
-
35
- add_offense(node, message: format(MSG, class: target_class, method: target_method))
36
- end
41
+ add_offense(node, message: format(MSG, current: current))
37
42
  end
38
43
 
39
44
  private
40
45
 
41
46
  def cleanup?(node)
42
- parent = node.parent
43
- node.block_argument? || (parent && (parent.block_type? || !parent.lvasgn_type?))
47
+ return true if node.block_argument?
48
+ return false unless (parent = node.parent)
49
+
50
+ parent.block_type? || !parent.lvasgn_type?
44
51
  end
45
52
  end
46
53
  end
@@ -33,7 +33,7 @@ module RuboCop
33
33
  def on_class(class_node)
34
34
  @macros_to_rewrite[class_node] = Set.new
35
35
 
36
- find_macros(class_node.body).each do |_visibility, macros|
36
+ find_macros(class_node.body).each_value do |macros|
37
37
  bisected = find_bisection(macros)
38
38
  next unless bisected.any?
39
39
 
@@ -74,7 +74,7 @@ module RuboCop
74
74
  def find_macros(class_def)
75
75
  # Find all the macros (`attr_reader`, `attr_writer`, etc.) in the class body
76
76
  # and turn them into `Macro` objects so that they can be processed.
77
- return [] if !class_def || class_def.def_type?
77
+ return {} if !class_def || class_def.def_type?
78
78
 
79
79
  send_nodes =
80
80
  if class_def.send_type?
@@ -370,7 +370,8 @@ module RuboCop
370
370
  def special_method_proper_block_style?(node)
371
371
  method_name = node.method_name
372
372
  return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
373
- return node.braces? if braces_required_method?(method_name)
373
+
374
+ node.braces? if braces_required_method?(method_name)
374
375
  end
375
376
 
376
377
  def braces_required_method?(method_name)