rubocop 1.79.2 → 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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +14 -2
  4. data/exe/rubocop +1 -8
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  6. data/lib/rubocop/cli.rb +6 -2
  7. data/lib/rubocop/config_loader.rb +3 -1
  8. data/lib/rubocop/config_loader_resolver.rb +5 -4
  9. data/lib/rubocop/config_store.rb +5 -0
  10. data/lib/rubocop/cop/autocorrect_logic.rb +4 -4
  11. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  12. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  14. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  15. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  16. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  17. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  18. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  19. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -5
  20. data/lib/rubocop/cop/layout/line_length.rb +9 -1
  21. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  22. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  23. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  24. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  25. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +13 -7
  26. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  27. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  28. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  29. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  30. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +17 -8
  31. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  32. data/lib/rubocop/cop/lint/self_assignment.rb +6 -5
  33. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  34. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  35. data/lib/rubocop/cop/lint/void.rb +7 -0
  36. data/lib/rubocop/cop/message_annotator.rb +1 -1
  37. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  38. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  39. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  40. data/lib/rubocop/cop/naming/method_name.rb +4 -2
  41. data/lib/rubocop/cop/naming/predicate_method.rb +19 -6
  42. data/lib/rubocop/cop/security/json_load.rb +33 -11
  43. data/lib/rubocop/cop/style/array_intersect.rb +46 -12
  44. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  45. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  46. data/lib/rubocop/cop/style/conditional_assignment.rb +7 -3
  47. data/lib/rubocop/cop/style/constant_visibility.rb +14 -9
  48. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  49. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  50. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  51. data/lib/rubocop/cop/style/float_division.rb +15 -1
  52. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  53. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  54. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  55. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  56. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  57. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  58. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  59. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  60. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  61. data/lib/rubocop/cop/style/redundant_parentheses.rb +14 -11
  62. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  63. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  64. data/lib/rubocop/cop/style/safe_navigation.rb +18 -1
  65. data/lib/rubocop/cop/style/semicolon.rb +23 -7
  66. data/lib/rubocop/cop/style/sole_nested_conditional.rb +8 -1
  67. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  68. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  69. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  70. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  71. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  72. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  73. data/lib/rubocop/cop/variable_force.rb +9 -7
  74. data/lib/rubocop/cops_documentation_generator.rb +4 -4
  75. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  76. data/lib/rubocop/lsp/diagnostic.rb +21 -20
  77. data/lib/rubocop/lsp/routes.rb +62 -6
  78. data/lib/rubocop/lsp/runtime.rb +2 -2
  79. data/lib/rubocop/lsp/server.rb +2 -2
  80. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  81. data/lib/rubocop/result_cache.rb +1 -1
  82. data/lib/rubocop/runner.rb +6 -4
  83. data/lib/rubocop/target_finder.rb +9 -9
  84. data/lib/rubocop/target_ruby.rb +10 -1
  85. data/lib/rubocop/version.rb +1 -1
  86. data/lib/rubocop.rb +1 -0
  87. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  88. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  89. metadata +7 -6
@@ -24,8 +24,6 @@ module RuboCop
24
24
 
25
25
  MSG_REPEATED_ELEMENT = 'Duplicate element inside regexp character class'
26
26
 
27
- OCTAL_DIGITS_AFTER_ESCAPE = 2
28
-
29
27
  def on_regexp(node)
30
28
  each_repeated_character_class_element_loc(node) do |loc|
31
29
  add_offense(loc, message: MSG_REPEATED_ELEMENT) do |corrector|
@@ -40,9 +38,9 @@ module RuboCop
40
38
 
41
39
  seen = Set.new
42
40
  group_expressions(node, expr.expressions) do |group|
43
- group_source = group.map(&:to_s).join
41
+ group_source = group.to_s
44
42
 
45
- yield source_range(group) if seen.include?(group_source)
43
+ yield group.expression if seen.include?(group_source)
46
44
 
47
45
  seen << group_source
48
46
  end
@@ -52,40 +50,13 @@ module RuboCop
52
50
  private
53
51
 
54
52
  def group_expressions(node, expressions)
55
- # Create a mutable list to simplify state tracking while we iterate.
56
- expressions = expressions.to_a
57
-
58
- until expressions.empty?
59
- # With we may need to compose a group of multiple expressions.
60
- group = [expressions.shift]
61
- next if within_interpolation?(node, group.first)
62
-
63
- # With regexp_parser < 2.7 escaped octal sequences may be up to 3
64
- # separate expressions ("\\0", "0", "1").
65
- pop_octal_digits(group, expressions) if escaped_octal?(group.first.to_s)
66
-
67
- yield(group)
68
- end
69
- end
70
-
71
- def pop_octal_digits(current_child, expressions)
72
- OCTAL_DIGITS_AFTER_ESCAPE.times do
73
- next_child = expressions.first
74
- break unless octal?(next_child.to_s)
53
+ expressions.each do |expression|
54
+ next if within_interpolation?(node, expression)
75
55
 
76
- current_child << expressions.shift
56
+ yield(expression)
77
57
  end
78
58
  end
79
59
 
80
- def source_range(children)
81
- return children.first.expression if children.size == 1
82
-
83
- range_between(
84
- children.first.expression.begin_pos,
85
- children.last.expression.begin_pos + children.last.to_s.length
86
- )
87
- end
88
-
89
60
  def skip_expression?(expr)
90
61
  expr.type != :set || expr.token == :intersection
91
62
  end
@@ -99,14 +70,6 @@ module RuboCop
99
70
  interpolation_locs(node).any? { |il| il.overlaps?(parse_tree_child_loc) }
100
71
  end
101
72
 
102
- def escaped_octal?(string)
103
- string.length == 2 && string[0] == '\\' && octal?(string[1])
104
- end
105
-
106
- def octal?(char)
107
- ('0'..'7').cover?(char)
108
- end
109
-
110
73
  def interpolation_locs(node)
111
74
  @interpolation_locs ||= {}
112
75
 
@@ -19,12 +19,23 @@ module RuboCop
19
19
  MSG = 'Empty interpolation detected.'
20
20
 
21
21
  def on_interpolation(begin_node)
22
+ return if in_percent_literal_array?(begin_node)
23
+
22
24
  node_children = begin_node.children.dup
23
25
  node_children.delete_if { |e| e.nil_type? || (e.basic_literal? && e.str_content&.empty?) }
24
26
  return unless node_children.empty?
25
27
 
26
28
  add_offense(begin_node) { |corrector| corrector.remove(begin_node) }
27
29
  end
30
+
31
+ private
32
+
33
+ def in_percent_literal_array?(begin_node)
34
+ array_node = begin_node.each_ancestor(:array).first
35
+ return false unless array_node
36
+
37
+ array_node.percent_literal?
38
+ end
28
39
  end
29
40
  end
30
41
  end
@@ -9,9 +9,21 @@ module RuboCop
9
9
  # cop disables on wide ranges of code, that latter contributors to
10
10
  # a file wouldn't be aware of.
11
11
  #
12
- # @example
13
- # # Lint/MissingCopEnableDirective:
14
- # # MaximumRangeSize: .inf
12
+ # You can set `MaximumRangeSize` to define the maximum number of
13
+ # consecutive lines a cop can be disabled for.
14
+ #
15
+ # - `.inf` any size (default)
16
+ # - `0` allows only single-line disables
17
+ # - `1` means the maximum allowed is as follows:
18
+ #
19
+ # [source,ruby]
20
+ # ----
21
+ # # rubocop:disable SomeCop
22
+ # a = 1
23
+ # # rubocop:enable SomeCop
24
+ # ----
25
+ #
26
+ # @example MaximumRangeSize: .inf (default)
15
27
  #
16
28
  # # good
17
29
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -25,9 +37,7 @@ module RuboCop
25
37
  # x= 0
26
38
  # # EOF
27
39
  #
28
- # @example
29
- # # Lint/MissingCopEnableDirective:
30
- # # MaximumRangeSize: 2
40
+ # @example MaximumRangeSize: 2
31
41
  #
32
42
  # # good
33
43
  # # rubocop:disable Layout/SpaceAroundOperators
@@ -52,10 +62,9 @@ module RuboCop
52
62
  each_missing_enable do |cop, line_range|
53
63
  next if acceptable_range?(cop, line_range)
54
64
 
55
- range = source_range(processed_source.buffer, line_range.min, 0..0)
56
65
  comment = processed_source.comment_at_line(line_range.begin)
57
66
 
58
- add_offense(range, message: message(cop, comment))
67
+ add_offense(comment, message: message(cop, comment))
59
68
  end
60
69
  end
61
70
 
@@ -24,10 +24,7 @@ module RuboCop
24
24
  MSG = 'Avoid rescuing the `Exception` class. Perhaps you meant to rescue `StandardError`?'
25
25
 
26
26
  def on_resbody(node)
27
- return unless node.children.first
28
-
29
- rescue_args = node.children.first.children
30
- return unless rescue_args.any? { |a| targets_exception?(a) }
27
+ return unless node.exceptions.any? { |exception| targets_exception?(exception) }
31
28
 
32
29
  add_offense(node)
33
30
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
  return if allow_rbs_inline_annotation? && rbs_inline_annotation?(node.receiver)
46
46
 
47
47
  if node.method?(:[]=)
48
- handle_key_assignment(node) if node.arguments.size == 2
48
+ handle_key_assignment(node)
49
49
  elsif node.assignment_method?
50
50
  handle_attribute_assignment(node) if node.arguments.size == 1
51
51
  end
@@ -105,12 +105,13 @@ module RuboCop
105
105
  end
106
106
 
107
107
  def handle_key_assignment(node)
108
- value_node = node.arguments[1]
108
+ value_node = node.last_argument
109
+ node_arguments = node.arguments[0...-1]
109
110
 
110
- if value_node.send_type? && value_node.method?(:[]) &&
111
+ if value_node.respond_to?(:method?) && value_node.method?(:[]) &&
111
112
  node.receiver == value_node.receiver &&
112
- !node.first_argument.call_type? &&
113
- node.first_argument == value_node.first_argument
113
+ node_arguments.none?(&:call_type?) &&
114
+ node_arguments == value_node.arguments
114
115
  add_offense(node)
115
116
  end
116
117
  end
@@ -125,13 +125,13 @@ module RuboCop
125
125
  next false if assignment_node.shorthand_asgn?
126
126
  next false unless assignment_node.parent
127
127
 
128
- node_within_block_or_conditional =
129
- node_within_block_or_conditional?(assignment_node.parent, argument.scope.node)
128
+ conditional_assignment =
129
+ conditional_assignment?(assignment_node.parent, argument.scope.node)
130
130
 
131
131
  unless uses_var?(assignment_node, argument.name)
132
132
  # It's impossible to decide whether a branch or block is executed,
133
133
  # so the precise reassignment location is undecidable.
134
- next false if node_within_block_or_conditional
134
+ next false if conditional_assignment
135
135
 
136
136
  yield(assignment.node, location_known)
137
137
  break
@@ -147,13 +147,13 @@ module RuboCop
147
147
  node.source_range.begin_pos
148
148
  end
149
149
 
150
- # Check whether the given node is nested into block or conditional.
150
+ # Check whether the given node is always executed or not
151
151
  #
152
- def node_within_block_or_conditional?(node, stop_search_node)
152
+ def conditional_assignment?(node, stop_search_node)
153
153
  return false if node == stop_search_node
154
154
 
155
- node.conditional? || node.block_type? ||
156
- node_within_block_or_conditional?(node.parent, stop_search_node)
155
+ node.conditional? || node.type?(:block, :rescue) ||
156
+ conditional_assignment?(node.parent, stop_search_node)
157
157
  end
158
158
 
159
159
  # Get argument references without assignments' references
@@ -17,6 +17,7 @@ module RuboCop
17
17
  #
18
18
  # # good
19
19
  # CGI.escape('http://example.com')
20
+ # URI.encode_uri_component(uri) # Since Ruby 3.1
20
21
  # URI.encode_www_form([['example', 'param'], ['lang', 'en']])
21
22
  # URI.encode_www_form(page: 10, locale: 'en')
22
23
  # URI.encode_www_form_component('http://example.com')
@@ -27,6 +28,7 @@ module RuboCop
27
28
  #
28
29
  # # good
29
30
  # CGI.unescape(enc_uri)
31
+ # URI.decode_uri_component(uri) # Since Ruby 3.1
30
32
  # URI.decode_www_form(enc_uri)
31
33
  # URI.decode_www_form_component(enc_uri)
32
34
  class UriEscapeUnescape < Base
@@ -16,6 +16,12 @@ module RuboCop
16
16
  # enumerator.each { |item| item >= 2 } #=> [2, 3]
17
17
  # ----
18
18
  #
19
+ # NOTE: Return values in assignment method definitions such as `def foo=(arg)` are
20
+ # detected because they are in a void context. However, autocorrection does not remove
21
+ # the return value, as that would change behavior. In such cases, whether to remove
22
+ # the return value or rename the method to something more appropriate should be left to
23
+ # the user.
24
+ #
19
25
  # @example CheckForMethodsWithNoSideEffects: false (default)
20
26
  # # bad
21
27
  # def some_method
@@ -233,6 +239,7 @@ module RuboCop
233
239
 
234
240
  def autocorrect_void_expression(corrector, node)
235
241
  return if node.parent.if_type?
242
+ return if (def_node = node.each_ancestor(:any_def).first) && def_node.assignment_method?
236
243
 
237
244
  corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left))
238
245
  end
@@ -102,7 +102,7 @@ module RuboCop
102
102
  def reference_urls
103
103
  urls = cop_config
104
104
  .values_at('References', 'Reference') # Support legacy Reference key
105
- .flat_map { Array(_1) }
105
+ .flat_map { |url| Array(url) }
106
106
  .reject(&:empty?)
107
107
 
108
108
  urls unless urls.empty?
@@ -227,7 +227,7 @@ module RuboCop
227
227
 
228
228
  def chained_to_heredoc?(node)
229
229
  while (node = node.receiver)
230
- return true if node.type?(:str, :dstr, :xstr) && node.heredoc?
230
+ return true if node.any_str_type? && node.heredoc?
231
231
  end
232
232
 
233
233
  false
@@ -19,8 +19,7 @@ module RuboCop
19
19
  def check_end_kw_alignment(node, align_ranges)
20
20
  return if ignored_node?(node)
21
21
 
22
- end_loc = node.loc.end
23
- return if accept_end_kw_alignment?(end_loc)
22
+ return unless (end_loc = node.loc.end)
24
23
 
25
24
  matching = matching_ranges(end_loc, align_ranges)
26
25
 
@@ -57,11 +56,6 @@ module RuboCop
57
56
  add_offense(end_loc, message: msg) { |corrector| autocorrect(corrector, node) }
58
57
  end
59
58
 
60
- def accept_end_kw_alignment?(end_loc)
61
- end_loc.nil? || # Discard modifier forms of if/while/until.
62
- !/\A[ \t]*end/.match?(processed_source.lines[end_loc.line - 1])
63
- end
64
-
65
59
  def style_parameter_name
66
60
  'EnforcedStyleAlignWith'
67
61
  end
@@ -140,7 +140,7 @@ module RuboCop
140
140
  end
141
141
 
142
142
  def last_item_precedes_newline?(node)
143
- after_last_item = node.children.last.source_range.end.join(node.loc.end.begin)
143
+ after_last_item = node.children.last.source_range.end.join(node.source_range.end)
144
144
 
145
145
  after_last_item.source.start_with?(/,?\s*(#.*)?\n/)
146
146
  end
@@ -147,7 +147,9 @@ module RuboCop
147
147
  alias on_defs on_def
148
148
 
149
149
  def on_alias(node)
150
- handle_method_name(node.new_identifier, node.new_identifier.value)
150
+ return unless (new_identifier = node.new_identifier).sym_type?
151
+
152
+ handle_method_name(new_identifier, new_identifier.value)
151
153
  end
152
154
 
153
155
  private
@@ -198,7 +200,7 @@ module RuboCop
198
200
 
199
201
  if forbidden_name?(name.to_s)
200
202
  register_forbidden_name(node)
201
- elsif !OPERATOR_METHODS.include?(name)
203
+ elsif !OPERATOR_METHODS.include?(name.to_sym)
202
204
  check_name(node, name, range_position(node))
203
205
  end
204
206
  end
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # method calls are assumed to return boolean values. The cop does not make an assessment
15
15
  # if the return type is unknown (non-predicate method calls, variables, etc.).
16
16
  #
17
- # NOTE: Operator methods (`def ==`, etc.) are ignored.
17
+ # NOTE: The `initialize` method and operator methods (`def ==`, etc.) are ignored.
18
18
  #
19
19
  # By default, the cop runs in `conservative` mode, which allows a method to be named
20
20
  # with a question mark as long as at least one return value is boolean. In `aggressive`
@@ -113,6 +113,18 @@ module RuboCop
113
113
  # true
114
114
  # end
115
115
  #
116
+ # @example AllowedMethods: [call] (default)
117
+ # # good
118
+ # def call
119
+ # foo == bar
120
+ # end
121
+ #
122
+ # @example AllowedPatterns: [\Afoo]
123
+ # # good
124
+ # def foo?
125
+ # 'foo'
126
+ # end
127
+ #
116
128
  # @example AllowBangMethods: false (default)
117
129
  # # bad
118
130
  # def save!
@@ -149,7 +161,8 @@ module RuboCop
149
161
  private
150
162
 
151
163
  def allowed?(node)
152
- allowed_method?(node.method_name) ||
164
+ node.method?(:initialize) ||
165
+ allowed_method?(node.method_name) ||
153
166
  matches_allowed_pattern?(node.method_name) ||
154
167
  allowed_bang_method?(node) ||
155
168
  node.operator_method? ||
@@ -180,8 +193,7 @@ module RuboCop
180
193
  return_values << extract_return_value(return_node)
181
194
  end
182
195
 
183
- last_value = last_value(node)
184
- return_values << last_value if last_value
196
+ return_values << last_value(node)
185
197
 
186
198
  process_return_values(return_values)
187
199
  end
@@ -234,8 +246,9 @@ module RuboCop
234
246
  end
235
247
 
236
248
  def last_value(node)
237
- value = node.begin_type? ? node.children.last : node
238
- value&.return_type? ? extract_return_value(value) : value
249
+ value = node.begin_type? ? node.children.last || s(:nil) : node
250
+
251
+ value.return_type? ? extract_return_value(value) : value
239
252
  end
240
253
 
241
254
  def process_return_values(return_values)
@@ -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
@@ -10,6 +10,8 @@ module RuboCop
10
10
  # * `(array1 & array2).any?`
11
11
  # * `(array1.intersection(array2)).any?`
12
12
  # * `array1.any? { |elem| array2.member?(elem) }`
13
+ # * `(array1 & array2).count > 0`
14
+ # * `(array1 & array2).size > 0`
13
15
  #
14
16
  # can be replaced with `array1.intersect?(array2)`.
15
17
  #
@@ -51,6 +53,19 @@ module RuboCop
51
53
  # array1.intersect?(array2)
52
54
  # !array1.intersect?(array2)
53
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
+ #
54
69
  # @example AllCops:ActiveSupportExtensionsEnabled: false (default)
55
70
  # # good
56
71
  # (array1 & array2).present?
@@ -73,17 +88,33 @@ module RuboCop
73
88
  PREDICATES = %i[any? empty? none?].to_set.freeze
74
89
  ACTIVE_SUPPORT_PREDICATES = (PREDICATES + %i[present? blank?]).freeze
75
90
 
91
+ ARRAY_SIZE_METHODS = %i[count length size].to_set.freeze
92
+
76
93
  # @!method bad_intersection_check?(node, predicates)
77
94
  def_node_matcher :bad_intersection_check?, <<~PATTERN
78
- (call
95
+ $(call
79
96
  {
80
97
  (begin (send $_ :& $_))
81
- (call $_ :intersection $_)
98
+ (call $!nil? :intersection $_)
82
99
  }
83
100
  $%1
84
101
  )
85
102
  PATTERN
86
103
 
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
+
87
118
  # @!method any_none_block_intersection(node)
88
119
  def_node_matcher :any_none_block_intersection, <<~PATTERN
89
120
  {
@@ -104,15 +135,15 @@ module RuboCop
104
135
  PATTERN
105
136
 
106
137
  MSG = 'Use `%<replacement>s` instead of `%<existing>s`.'
107
- STRAIGHT_METHODS = %i[present? any?].freeze
108
- NEGATED_METHODS = %i[blank? empty? none?].freeze
138
+ STRAIGHT_METHODS = %i[present? any? > positive? !=].freeze
139
+ NEGATED_METHODS = %i[blank? empty? none? == zero?].freeze
109
140
  RESTRICT_ON_SEND = (STRAIGHT_METHODS + NEGATED_METHODS).freeze
110
141
 
111
142
  def on_send(node)
112
143
  return if node.block_literal?
113
- return unless (receiver, argument, method_name = bad_intersection?(node))
144
+ return unless (dot_node, receiver, argument, method_name = bad_intersection?(node))
114
145
 
115
- dot = node.loc.dot.source
146
+ dot = dot_node.loc.dot.source
116
147
  bang = straight?(method_name) ? '' : '!'
117
148
  replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
118
149
 
@@ -135,13 +166,16 @@ module RuboCop
135
166
  private
136
167
 
137
168
  def bad_intersection?(node)
138
- predicates = if active_support_extensions_enabled?
139
- ACTIVE_SUPPORT_PREDICATES
140
- else
141
- PREDICATES
142
- end
169
+ bad_intersection_check?(node, bad_intersection_predicates) ||
170
+ intersection_size_check?(node)
171
+ end
143
172
 
144
- 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
145
179
  end
146
180
 
147
181
  def straight?(method_name)
@@ -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