rubocop 1.75.8 → 1.81.7

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -16
  3. data/config/default.yml +121 -28
  4. data/config/obsoletion.yml +6 -3
  5. data/exe/rubocop +1 -8
  6. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  7. data/lib/rubocop/cli.rb +18 -3
  8. data/lib/rubocop/config_loader.rb +4 -39
  9. data/lib/rubocop/config_loader_resolver.rb +5 -4
  10. data/lib/rubocop/config_store.rb +5 -0
  11. data/lib/rubocop/cop/autocorrect_logic.rb +4 -4
  12. data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  14. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  15. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +5 -2
  16. data/lib/rubocop/cop/gemspec/attribute_assignment.rb +91 -0
  17. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +0 -22
  18. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
  19. data/lib/rubocop/cop/gemspec/require_mfa.rb +15 -1
  20. data/lib/rubocop/cop/internal_affairs/example_description.rb +1 -1
  21. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +4 -4
  22. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +4 -1
  23. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +3 -2
  24. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  25. data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +1 -1
  26. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  27. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  28. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  29. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  30. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +101 -0
  31. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -1
  32. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +8 -29
  33. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +1 -1
  34. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -5
  35. data/lib/rubocop/cop/layout/line_length.rb +35 -6
  36. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  37. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  38. data/lib/rubocop/cop/layout/space_around_keyword.rb +6 -1
  39. data/lib/rubocop/cop/layout/space_around_operators.rb +8 -0
  40. data/lib/rubocop/cop/layout/space_before_brackets.rb +2 -9
  41. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +7 -2
  42. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  43. data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
  44. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  45. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +13 -7
  46. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  47. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  48. data/lib/rubocop/cop/lint/duplicate_methods.rb +25 -4
  49. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  50. data/lib/rubocop/cop/lint/empty_interpolation.rb +14 -1
  51. data/lib/rubocop/cop/lint/float_comparison.rb +4 -4
  52. data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
  53. data/lib/rubocop/cop/lint/literal_as_condition.rb +34 -28
  54. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +17 -8
  55. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +1 -0
  56. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  57. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +101 -2
  58. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +4 -4
  59. data/lib/rubocop/cop/lint/require_range_parentheses.rb +1 -1
  60. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  61. data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
  62. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
  63. data/lib/rubocop/cop/lint/self_assignment.rb +31 -5
  64. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  65. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
  66. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  67. data/lib/rubocop/cop/lint/useless_access_modifier.rb +29 -4
  68. data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
  69. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +1 -0
  70. data/lib/rubocop/cop/lint/useless_or.rb +98 -0
  71. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +3 -3
  72. data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +121 -0
  73. data/lib/rubocop/cop/lint/void.rb +7 -0
  74. data/lib/rubocop/cop/message_annotator.rb +1 -1
  75. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  76. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  77. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  78. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  79. data/lib/rubocop/cop/mixin/gemspec_help.rb +22 -0
  80. data/lib/rubocop/cop/mixin/line_length_help.rb +24 -8
  81. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  82. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  83. data/lib/rubocop/cop/naming/file_name.rb +2 -2
  84. data/lib/rubocop/cop/naming/method_name.rb +129 -13
  85. data/lib/rubocop/cop/naming/predicate_method.rb +319 -0
  86. data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +4 -4
  87. data/lib/rubocop/cop/security/eval.rb +2 -1
  88. data/lib/rubocop/cop/security/json_load.rb +33 -11
  89. data/lib/rubocop/cop/security/open.rb +1 -0
  90. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -1
  91. data/lib/rubocop/cop/style/accessor_grouping.rb +13 -1
  92. data/lib/rubocop/cop/style/arguments_forwarding.rb +11 -17
  93. data/lib/rubocop/cop/style/array_intersect.rb +99 -35
  94. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  95. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  96. data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
  97. data/lib/rubocop/cop/style/case_like_if.rb +1 -1
  98. data/lib/rubocop/cop/style/collection_querying.rb +167 -0
  99. data/lib/rubocop/cop/style/conditional_assignment.rb +11 -5
  100. data/lib/rubocop/cop/style/constant_visibility.rb +14 -9
  101. data/lib/rubocop/cop/style/dig_chain.rb +1 -1
  102. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  103. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
  104. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  105. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  106. data/lib/rubocop/cop/style/exponential_notation.rb +3 -2
  107. data/lib/rubocop/cop/style/fetch_env_var.rb +32 -6
  108. data/lib/rubocop/cop/style/float_division.rb +15 -1
  109. data/lib/rubocop/cop/style/hash_conversion.rb +16 -8
  110. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  111. data/lib/rubocop/cop/style/if_unless_modifier.rb +13 -6
  112. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  113. data/lib/rubocop/cop/style/inverse_methods.rb +1 -1
  114. data/lib/rubocop/cop/style/it_assignment.rb +69 -12
  115. data/lib/rubocop/cop/style/it_block_parameter.rb +36 -15
  116. data/lib/rubocop/cop/style/map_to_hash.rb +1 -3
  117. data/lib/rubocop/cop/style/map_to_set.rb +1 -3
  118. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +4 -6
  119. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +16 -0
  120. data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
  121. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  122. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  123. data/lib/rubocop/cop/style/parallel_assignment.rb +32 -20
  124. data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
  125. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  126. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  127. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  128. data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -9
  129. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  130. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  131. data/lib/rubocop/cop/style/redundant_interpolation.rb +12 -3
  132. data/lib/rubocop/cop/style/redundant_line_continuation.rb +1 -1
  133. data/lib/rubocop/cop/style/redundant_parentheses.rb +55 -16
  134. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  135. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  136. data/lib/rubocop/cop/style/redundant_self.rb +8 -5
  137. data/lib/rubocop/cop/style/safe_navigation.rb +44 -12
  138. data/lib/rubocop/cop/style/semicolon.rb +23 -7
  139. data/lib/rubocop/cop/style/single_line_methods.rb +7 -4
  140. data/lib/rubocop/cop/style/sole_nested_conditional.rb +40 -3
  141. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +1 -1
  142. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  143. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  144. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  145. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  146. data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
  147. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  148. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  149. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  150. data/lib/rubocop/cop/variable_force.rb +25 -8
  151. data/lib/rubocop/cops_documentation_generator.rb +5 -4
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  153. data/lib/rubocop/formatter/fuubar_style_formatter.rb +1 -1
  154. data/lib/rubocop/formatter/markdown_formatter.rb +1 -0
  155. data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
  156. data/lib/rubocop/formatter/pacman_formatter.rb +1 -0
  157. data/lib/rubocop/lsp/diagnostic.rb +25 -24
  158. data/lib/rubocop/lsp/routes.rb +65 -9
  159. data/lib/rubocop/lsp/runtime.rb +2 -2
  160. data/lib/rubocop/lsp/server.rb +2 -2
  161. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  162. data/lib/rubocop/pending_cops_reporter.rb +56 -0
  163. data/lib/rubocop/result_cache.rb +14 -12
  164. data/lib/rubocop/rspec/expect_offense.rb +9 -3
  165. data/lib/rubocop/runner.rb +6 -4
  166. data/lib/rubocop/server/cache.rb +4 -2
  167. data/lib/rubocop/server/client_command/base.rb +10 -0
  168. data/lib/rubocop/server/client_command/exec.rb +2 -1
  169. data/lib/rubocop/server/client_command/start.rb +11 -1
  170. data/lib/rubocop/target_finder.rb +9 -9
  171. data/lib/rubocop/target_ruby.rb +10 -1
  172. data/lib/rubocop/version.rb +1 -1
  173. data/lib/rubocop.rb +12 -1
  174. data/lib/ruby_lsp/rubocop/addon.rb +25 -10
  175. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  176. metadata +18 -7
@@ -6,22 +6,40 @@ module RuboCop
6
6
  # Checks for the use of JSON class methods which have potential
7
7
  # security issues.
8
8
  #
9
+ # `JSON.load` and similar methods allow deserialization of arbitrary ruby objects:
10
+ #
11
+ # [source,ruby]
12
+ # ----
13
+ # require 'json/add/string'
14
+ # result = JSON.load('{ "json_class": "String", "raw": [72, 101, 108, 108, 111] }')
15
+ # pp result # => "Hello"
16
+ # ----
17
+ #
18
+ # Never use `JSON.load` for untrusted user input. Prefer `JSON.parse` unless you have
19
+ # a concrete use-case for `JSON.load`.
20
+ #
21
+ # NOTE: Starting with `json` gem version 2.8.0, triggering this behavior without explicitly
22
+ # passing the `create_additions` keyword argument emits a deprecation warning, with the
23
+ # goal of being secure by default in the next major version 3.0.0.
24
+ #
9
25
  # @safety
10
26
  # This cop's autocorrection is unsafe because it's potentially dangerous.
11
- # If using a stream, like `JSON.load(open('file'))`, it will need to call
27
+ # If using a stream, like `JSON.load(open('file'))`, you will need to call
12
28
  # `#read` manually, like `JSON.parse(open('file').read)`.
13
- # If reading single values (rather than proper JSON objects), like
14
- # `JSON.load('false')`, it will need to pass the `quirks_mode: true`
15
- # option, like `JSON.parse('false', quirks_mode: true)`.
16
29
  # Other similar issues may apply.
17
30
  #
18
31
  # @example
19
32
  # # bad
20
- # JSON.load("{}")
21
- # JSON.restore("{}")
33
+ # JSON.load('{}')
34
+ # JSON.restore('{}')
22
35
  #
23
36
  # # good
24
- # JSON.parse("{}")
37
+ # JSON.parse('{}')
38
+ # JSON.unsafe_load('{}')
39
+ #
40
+ # # good - explicit use of `create_additions` option
41
+ # JSON.load('{}', create_additions: true)
42
+ # JSON.load('{}', create_additions: false)
25
43
  #
26
44
  class JSONLoad < Base
27
45
  extend AutoCorrector
@@ -29,13 +47,17 @@ module RuboCop
29
47
  MSG = 'Prefer `JSON.parse` over `JSON.%<method>s`.'
30
48
  RESTRICT_ON_SEND = %i[load restore].freeze
31
49
 
32
- # @!method json_load(node)
33
- def_node_matcher :json_load, <<~PATTERN
34
- (send (const {nil? cbase} :JSON) ${:load :restore} ...)
50
+ # @!method insecure_json_load(node)
51
+ def_node_matcher :insecure_json_load, <<~PATTERN
52
+ (
53
+ send (const {nil? cbase} :JSON) ${:load :restore}
54
+ ...
55
+ !(hash `(sym $:create_additions))
56
+ )
35
57
  PATTERN
36
58
 
37
59
  def on_send(node)
38
- json_load(node) do |method|
60
+ insecure_json_load(node) do |method|
39
61
  add_offense(node.loc.selector, message: format(MSG, method: method)) do |corrector|
40
62
  corrector.replace(node.loc.selector, 'parse')
41
63
  end
@@ -34,6 +34,7 @@ module RuboCop
34
34
  # # good (literal strings)
35
35
  # open("foo.text")
36
36
  # URI.open("http://example.com")
37
+ # URI.parse(url).open
37
38
  class Open < Base
38
39
  MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
39
40
  RESTRICT_ON_SEND = %i[open].freeze
@@ -348,7 +348,7 @@ module RuboCop
348
348
  end
349
349
 
350
350
  def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
351
- def_node = begin_node.children[1]
351
+ def_node = begin_node.children[begin_node.children.index(modifier_node) + 1]
352
352
  range = modifier_node.source_range.begin.join(def_node.source_range.begin)
353
353
  corrector.remove(range)
354
354
  end
@@ -84,7 +84,10 @@ module RuboCop
84
84
 
85
85
  def autocorrect(corrector, node)
86
86
  if (preferred_accessors = preferred_accessors(node))
87
- corrector.replace(node, preferred_accessors)
87
+ corrector.replace(
88
+ grouped_style? ? node : range_with_trailing_argument_comment(node),
89
+ preferred_accessors
90
+ )
88
91
  else
89
92
  range = range_with_surrounding_space(node.source_range, side: :left)
90
93
  corrector.remove(range)
@@ -196,6 +199,15 @@ module RuboCop
196
199
  end
197
200
  end.join("\n")
198
201
  end
202
+
203
+ def range_with_trailing_argument_comment(node)
204
+ comment = processed_source.ast_with_comments[node.last_argument].last
205
+ if comment
206
+ add_range(node.source_range, comment.source_range)
207
+ else
208
+ node
209
+ end
210
+ end
199
211
  end
200
212
  end
201
213
  end
@@ -146,7 +146,6 @@ module RuboCop
146
146
  minimum_target_ruby_version 2.7
147
147
 
148
148
  FORWARDING_LVAR_TYPES = %i[splat kwsplat block_pass].freeze
149
- ADDITIONAL_ARG_TYPES = %i[lvar arg optarg].freeze
150
149
 
151
150
  FORWARDING_MSG = 'Use shorthand syntax `...` for arguments forwarding.'
152
151
  ARGS_MSG = 'Use anonymous positional arguments forwarding (`*`).'
@@ -198,9 +197,9 @@ module RuboCop
198
197
  send_classifications.all? { |_, c, _, _| all_classifications.include?(c) }
199
198
  end
200
199
 
201
- # rubocop:disable Metrics/MethodLength
200
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
202
201
  def add_forward_all_offenses(node, send_classifications, forwardable_args)
203
- _rest_arg, _kwrest_arg, block_arg = *forwardable_args
202
+ rest_arg, kwrest_arg, block_arg = *forwardable_args
204
203
  registered_block_arg_offense = false
205
204
 
206
205
  send_classifications.each do |send_node, c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
@@ -212,16 +211,20 @@ module RuboCop
212
211
  registered_block_arg_offense = true
213
212
  break
214
213
  else
215
- register_forward_all_offense(send_node, send_node, forward_rest)
214
+ first_arg = forward_rest || forward_kwrest || forward_all_first_argument(send_node)
215
+ register_forward_all_offense(send_node, send_node, first_arg)
216
216
  end
217
217
  end
218
218
 
219
219
  return if registered_block_arg_offense
220
220
 
221
- rest_arg, _kwrest_arg, _block_arg = *forwardable_args
222
- register_forward_all_offense(node, node.arguments, rest_arg)
221
+ register_forward_all_offense(node, node.arguments, rest_arg || kwrest_arg)
222
+ end
223
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
224
+
225
+ def forward_all_first_argument(node)
226
+ node.arguments.reverse_each.find(&:forwarded_restarg_type?)
223
227
  end
224
- # rubocop:enable Metrics/MethodLength
225
228
 
226
229
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
227
230
  def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
@@ -361,18 +364,9 @@ module RuboCop
361
364
  end
362
365
  end
363
366
 
364
- # rubocop:disable Metrics/AbcSize
365
367
  def arguments_range(node, first_node)
366
- arguments = node.arguments.reject do |arg|
367
- next true if ADDITIONAL_ARG_TYPES.include?(arg.type) || arg.variable? || arg.call_type?
368
-
369
- arg.literal? && arg.each_descendant(:kwsplat).none?
370
- end
371
-
372
- start_node = first_node || arguments.first
373
- start_node.source_range.begin.join(arguments.last.source_range.end)
368
+ first_node.source_range.begin.join(node.last_argument.source_range.end)
374
369
  end
375
- # rubocop:enable Metrics/AbcSize
376
370
 
377
371
  def allow_only_rest_arguments?
378
372
  cop_config.fetch('AllowOnlyRestArgument', true)
@@ -5,12 +5,17 @@ module RuboCop
5
5
  module Style
6
6
  # In Ruby 3.1, `Array#intersect?` has been added.
7
7
  #
8
- # This cop identifies places where `(array1 & array2).any?`
9
- # or `(array1.intersection(array2)).any?` can be replaced by
10
- # `array1.intersect?(array2)`.
8
+ # This cop identifies places where:
11
9
  #
12
- # The `array1.intersect?(array2)` method is faster than
13
- # `(array1 & array2).any?` and is more readable.
10
+ # * `(array1 & array2).any?`
11
+ # * `(array1.intersection(array2)).any?`
12
+ # * `array1.any? { |elem| array2.member?(elem) }`
13
+ # * `(array1 & array2).count > 0`
14
+ # * `(array1 & array2).size > 0`
15
+ #
16
+ # can be replaced with `array1.intersect?(array2)`.
17
+ #
18
+ # `array1.intersect?(array2)` is faster and more readable.
14
19
  #
15
20
  # In cases like the following, compatibility is not ensured,
16
21
  # so it will not be detected when using block argument.
@@ -40,10 +45,27 @@ module RuboCop
40
45
  # array1.intersection(array2).empty?
41
46
  # array1.intersection(array2).none?
42
47
  #
48
+ # # bad
49
+ # array1.any? { |elem| array2.member?(elem) }
50
+ # array1.none? { |elem| array2.member?(elem) }
51
+ #
43
52
  # # good
44
53
  # array1.intersect?(array2)
45
54
  # !array1.intersect?(array2)
46
55
  #
56
+ # # bad
57
+ # (array1 & array2).count > 0
58
+ # (array1 & array2).count.positive?
59
+ # (array1 & array2).count != 0
60
+ #
61
+ # (array1 & array2).count == 0
62
+ # (array1 & array2).count.zero?
63
+ #
64
+ # # good
65
+ # array1.intersect?(array2)
66
+ #
67
+ # !array1.intersect?(array2)
68
+ #
47
69
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
48
70
  # # good
49
71
  # (array1 & array2).present?
@@ -66,64 +88,106 @@ module RuboCop
66
88
  PREDICATES = %i[any? empty? none?].to_set.freeze
67
89
  ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze
68
90
 
91
+ ARRAY_SIZE_METHODS = %i[count length size].to_set.freeze
92
+
69
93
  # @!method bad_intersection_check?(node, predicates)
70
94
  def_node_matcher :bad_intersection_check?, <<~PATTERN
71
- (call
95
+ $(call
72
96
  {
73
97
  (begin (send $_ :& $_))
74
- (call $_ :intersection $_)
98
+ (call $!nil? :intersection $_)
75
99
  }
76
100
  $%1
77
101
  )
78
102
  PATTERN
79
103
 
80
- MSG = 'Use `%<negated>s%<receiver>s%<dot>sintersect?(%<argument>s)` ' \
81
- 'instead of `%<existing>s`.'
82
- STRAIGHT_METHODS = %i[present? any?].freeze
83
- NEGATED_METHODS = %i[blank? empty? none?].freeze
104
+ # @!method intersection_size_check?(node, predicates)
105
+ def_node_matcher :intersection_size_check?, <<~PATTERN
106
+ (call
107
+ $(call
108
+ {
109
+ (begin (send $_ :& $_))
110
+ (call $!nil? :intersection $_)
111
+ }
112
+ %ARRAY_SIZE_METHODS
113
+ )
114
+ {$:> (int 0) | $:positive? | $:!= (int 0) | $:== (int 0) | $:zero?}
115
+ )
116
+ PATTERN
117
+
118
+ # @!method any_none_block_intersection(node)
119
+ def_node_matcher :any_none_block_intersection, <<~PATTERN
120
+ {
121
+ (block
122
+ (call $_receiver ${:any? :none?})
123
+ (args (arg _key))
124
+ (send $_argument :member? (lvar _key))
125
+ )
126
+ (numblock
127
+ (call $_receiver ${:any? :none?}) 1
128
+ (send $_argument :member? (lvar :_1))
129
+ )
130
+ (itblock
131
+ (call $_receiver ${:any? :none?}) :it
132
+ (send $_argument :member? (lvar :it))
133
+ )
134
+ }
135
+ PATTERN
136
+
137
+ MSG = 'Use `%<replacement>s` instead of `%<existing>s`.'
138
+ STRAIGHT_METHODS = %i[present? any? > positive? !=].freeze
139
+ NEGATED_METHODS = %i[blank? empty? none? == zero?].freeze
84
140
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
85
141
 
86
142
  def on_send(node)
87
143
  return if node.block_literal?
88
- return unless (receiver, argument, method_name = bad_intersection?(node))
89
-
90
- dot = node.loc.dot.source
91
- message = message(receiver.source, argument.source, method_name, dot, node.source)
144
+ return unless (dot_node, receiver, argument, method_name = bad_intersection?(node))
92
145
 
93
- add_offense(node, message: message) do |corrector|
94
- bang = straight?(method_name) ? '' : '!'
146
+ dot = dot_node.loc.dot.source
147
+ bang = straight?(method_name) ? '' : '!'
148
+ replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
95
149
 
96
- corrector.replace(node, "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})")
97
- end
150
+ register_offense(node, replacement)
98
151
  end
99
152
  alias on_csend on_send
100
153
 
154
+ def on_block(node)
155
+ return unless (receiver, method_name, argument = any_none_block_intersection(node))
156
+
157
+ dot = node.send_node.loc.dot.source
158
+ bang = method_name == :any? ? '' : '!'
159
+ replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
160
+
161
+ register_offense(node, replacement)
162
+ end
163
+ alias on_numblock on_block
164
+ alias on_itblock on_block
165
+
101
166
  private
102
167
 
103
168
  def bad_intersection?(node)
104
- predicates = if active_support_extensions_enabled?
105
- ACTIVE_SUPPORT_PREDICATES
106
- else
107
- PREDICATES
108
- end
169
+ bad_intersection_check?(node, bad_intersection_predicates) ||
170
+ intersection_size_check?(node)
171
+ end
109
172
 
110
- bad_intersection_check?(node, predicates)
173
+ def bad_intersection_predicates
174
+ if active_support_extensions_enabled?
175
+ ACTIVE_SUPPORT_PREDICATES
176
+ else
177
+ PREDICATES
178
+ end
111
179
  end
112
180
 
113
181
  def straight?(method_name)
114
182
  STRAIGHT_METHODS.include?(method_name.to_sym)
115
183
  end
116
184
 
117
- def message(receiver, argument, method_name, dot, existing)
118
- negated = straight?(method_name) ? '' : '!'
119
- format(
120
- MSG,
121
- negated: negated,
122
- receiver: receiver,
123
- argument: argument,
124
- dot: dot,
125
- existing: existing
126
- )
185
+ def register_offense(node, replacement)
186
+ message = format(MSG, replacement: replacement, existing: node.source)
187
+
188
+ add_offense(node, message: message) do |corrector|
189
+ corrector.replace(node, replacement)
190
+ end
127
191
  end
128
192
  end
129
193
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Use `include?(element)` instead of `intersect?([element])`.
7
+ #
8
+ # @safety
9
+ # The receiver might not be an array.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # array.intersect?([element])
14
+ #
15
+ # # good
16
+ # array.include?(element)
17
+ class ArrayIntersectWithSingleElement < Base
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `include?(element)` instead of `intersect?([element])`.'
21
+
22
+ RESTRICT_ON_SEND = %i[intersect?].freeze
23
+
24
+ # @!method single_element(node)
25
+ def_node_matcher :single_element, <<~PATTERN
26
+ (send _ _ $(array $_))
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ array, element = single_element(node)
31
+ return unless array
32
+
33
+ add_offense(
34
+ node.source_range.with(begin_pos: node.loc.selector.begin_pos)
35
+ ) do |corrector|
36
+ corrector.replace(node.loc.selector, 'include?')
37
+ corrector.replace(
38
+ array,
39
+ array.percent_literal? ? element.value.inspect : element.source
40
+ )
41
+ end
42
+ end
43
+ alias on_csend on_send
44
+ end
45
+ end
46
+ end
47
+ end
@@ -70,18 +70,25 @@ module RuboCop
70
70
  (send _ :& _))
71
71
  PATTERN
72
72
 
73
+ # rubocop:disable Metrics/AbcSize
73
74
  def on_send(node)
74
75
  return unless node.receiver&.begin_type?
75
76
  return unless (preferred_method = preferred_method(node))
76
77
 
77
78
  bit_operation = node.receiver.children.first
78
79
  lhs, _operator, rhs = *bit_operation
79
- preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
80
+
81
+ preferred = if preferred_method == 'allbits?' && lhs.source == node.first_argument.source
82
+ "#{rhs.source}.allbits?(#{lhs.source})"
83
+ else
84
+ "#{lhs.source}.#{preferred_method}(#{rhs.source})"
85
+ end
80
86
 
81
87
  add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
82
88
  corrector.replace(node, preferred)
83
89
  end
84
90
  end
91
+ # rubocop:enable Metrics/AbcSize
85
92
 
86
93
  private
87
94
 
@@ -4,7 +4,7 @@
4
4
  module RuboCop
5
5
  module Cop
6
6
  module Style
7
- # Check for uses of braces or do/end around single line or
7
+ # Checks for uses of braces or do/end around single line or
8
8
  # multi-line blocks.
9
9
  #
10
10
  # Methods that can be either procedural or functional and cannot be
@@ -269,7 +269,7 @@ module RuboCop
269
269
  end
270
270
 
271
271
  def regexp_with_named_captures?(node)
272
- node.regexp_type? && node.each_capture(named: true).count.positive?
272
+ node.regexp_type? && node.each_capture(named: true).any?
273
273
  end
274
274
  end
275
275
  end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Prefer `Enumerable` predicate methods over expressions with `count`.
7
+ #
8
+ # The cop checks calls to `count` without arguments, or with a
9
+ # block. It doesn't register offenses for `count` with a positional
10
+ # argument because its behavior differs from predicate methods (`count`
11
+ # matches the argument using `==`, while `any?`, `none?` and `one?` use
12
+ # `===`).
13
+ #
14
+ # NOTE: This cop doesn't check `length` and `size` methods because they
15
+ # would yield false positives. For example, `String` implements `length`
16
+ # and `size`, but it doesn't include `Enumerable`.
17
+ #
18
+ # @safety
19
+ # The cop is unsafe because receiver might not include `Enumerable`, or
20
+ # it has nonstandard implementation of `count` or any replacement
21
+ # methods.
22
+ #
23
+ # It's also unsafe because for collections with falsey values, expressions
24
+ # with `count` without a block return a different result than methods `any?`,
25
+ # `none?` and `one?`:
26
+ #
27
+ # [source,ruby]
28
+ # ----
29
+ # [nil, false].count.positive?
30
+ # [nil].count == 1
31
+ # # => true
32
+ #
33
+ # [nil, false].any?
34
+ # [nil].one?
35
+ # # => false
36
+ #
37
+ # [nil].count == 0
38
+ # # => false
39
+ #
40
+ # [nil].none?
41
+ # # => true
42
+ # ----
43
+ #
44
+ # Autocorrection is unsafe when replacement methods don't iterate over
45
+ # every element in collection and the given block runs side effects:
46
+ #
47
+ # [source,ruby]
48
+ # ----
49
+ # x.count(&:method_with_side_effects).positive?
50
+ # # calls `method_with_side_effects` on every element
51
+ #
52
+ # x.any?(&:method_with_side_effects)
53
+ # # calls `method_with_side_effects` until first element returns a truthy value
54
+ # ----
55
+ #
56
+ # @example
57
+ #
58
+ # # bad
59
+ # x.count.positive?
60
+ # x.count > 0
61
+ # x.count != 0
62
+ #
63
+ # x.count(&:foo?).positive?
64
+ # x.count { |item| item.foo? }.positive?
65
+ #
66
+ # # good
67
+ # x.any?
68
+ #
69
+ # x.any?(&:foo?)
70
+ # x.any? { |item| item.foo? }
71
+ #
72
+ # # bad
73
+ # x.count.zero?
74
+ # x.count == 0
75
+ #
76
+ # # good
77
+ # x.none?
78
+ #
79
+ # # bad
80
+ # x.count == 1
81
+ # x.one?
82
+ #
83
+ # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
84
+ #
85
+ # # good
86
+ # x.count > 1
87
+ #
88
+ # @example AllCops:ActiveSupportExtensionsEnabled: true
89
+ #
90
+ # # bad
91
+ # x.count > 1
92
+ #
93
+ # # good
94
+ # x.many?
95
+ #
96
+ class CollectionQuerying < Base
97
+ include RangeHelp
98
+ extend AutoCorrector
99
+
100
+ MSG = 'Use `%<prefer>s` instead.'
101
+
102
+ RESTRICT_ON_SEND = %i[positive? > != zero? ==].freeze
103
+
104
+ REPLACEMENTS = {
105
+ [:positive?, nil] => :any?,
106
+ [:>, 0] => :any?,
107
+ [:!=, 0] => :any?,
108
+ [:zero?, nil] => :none?,
109
+ [:==, 0] => :none?,
110
+ [:==, 1] => :one?,
111
+ [:>, 1] => :many?
112
+ }.freeze
113
+
114
+ # @!method count_predicate(node)
115
+ def_node_matcher :count_predicate, <<~PATTERN
116
+ (send
117
+ {
118
+ (any_block $(call !nil? :count) _ _)
119
+ $(call !nil? :count (block-pass _)?)
120
+ }
121
+ {
122
+ :positive? |
123
+ :> (int 0) |
124
+ :!= (int 0) |
125
+ :zero? |
126
+ :== (int 0) |
127
+ :== (int 1) |
128
+ :> (int 1)
129
+ })
130
+ PATTERN
131
+
132
+ def on_send(node)
133
+ return unless (count_node = count_predicate(node))
134
+
135
+ replacement_method = replacement_method(node)
136
+
137
+ return unless replacement_supported?(replacement_method)
138
+
139
+ offense_range = count_node.loc.selector.join(node.source_range.end)
140
+ add_offense(offense_range,
141
+ message: format(MSG, prefer: replacement_method)) do |corrector|
142
+ corrector.replace(count_node.loc.selector, replacement_method)
143
+ corrector.remove(removal_range(node))
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def replacement_method(node)
150
+ REPLACEMENTS.fetch([node.method_name, node.first_argument&.value])
151
+ end
152
+
153
+ def replacement_supported?(method_name)
154
+ return true if active_support_extensions_enabled?
155
+
156
+ method_name != :many?
157
+ end
158
+
159
+ def removal_range(node)
160
+ range = (node.loc.dot || node.loc.selector).join(node.source_range.end)
161
+
162
+ range_with_surrounding_space(range, side: :left)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -111,7 +111,7 @@ module RuboCop
111
111
  end
112
112
  end
113
113
 
114
- # Check for `if` and `case` statements where each branch is used for
114
+ # Checks for `if` and `case` statements where each branch is used for
115
115
  # both the assignment and comparison of the same variable
116
116
  # when using the return of the condition can be used instead.
117
117
  #
@@ -444,14 +444,16 @@ module RuboCop
444
444
  next if child.parent.dstr_type?
445
445
 
446
446
  white_space = white_space_range(child, column)
447
- corrector.remove(white_space) if white_space.source.strip.empty?
447
+ corrector.remove(white_space) if white_space
448
448
  end
449
449
 
450
450
  if condition.loc.else && !same_line?(condition.else_branch, condition)
451
451
  corrector.remove_preceding(condition.loc.else, condition.loc.else.column - column)
452
452
  end
453
453
 
454
- return unless condition.loc.end && !same_line?(condition.loc.end, condition)
454
+ return unless condition.loc.end && !same_line?(
455
+ condition.branches.last.parent.else_branch, condition.loc.end
456
+ )
455
457
 
456
458
  corrector.remove_preceding(condition.loc.end, condition.loc.end.column - column)
457
459
  end
@@ -463,9 +465,13 @@ module RuboCop
463
465
 
464
466
  def white_space_range(node, column)
465
467
  expression = node.source_range
466
- begin_pos = expression.begin_pos - (expression.column - column - 2)
468
+ end_pos = expression.begin_pos
469
+ begin_pos = end_pos - (expression.column - column - 2)
467
470
 
468
- Parser::Source::Range.new(expression.source_buffer, begin_pos, expression.begin_pos)
471
+ return nil if begin_pos > end_pos
472
+
473
+ white_space = Parser::Source::Range.new(expression.source_buffer, begin_pos, end_pos)
474
+ white_space if white_space.source.strip.empty?
469
475
  end
470
476
 
471
477
  def assignment(node)