rubocop 1.69.2 → 1.71.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +36 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +3 -3
  6. data/lib/rubocop/cli/command/show_cops.rb +24 -2
  7. data/lib/rubocop/comment_config.rb +1 -1
  8. data/lib/rubocop/config.rb +13 -4
  9. data/lib/rubocop/config_loader.rb +4 -0
  10. data/lib/rubocop/config_loader_resolver.rb +14 -3
  11. data/lib/rubocop/config_validator.rb +18 -8
  12. data/lib/rubocop/cop/base.rb +6 -0
  13. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  14. data/lib/rubocop/cop/internal_affairs/cop_enabled.rb +85 -0
  15. data/lib/rubocop/cop/internal_affairs/location_expression.rb +2 -1
  16. data/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb +3 -2
  17. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +4 -3
  18. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +90 -0
  19. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +2 -1
  20. data/lib/rubocop/cop/internal_affairs/single_line_comparison.rb +5 -4
  21. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  22. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -8
  23. data/lib/rubocop/cop/layout/class_structure.rb +7 -7
  24. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  25. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +1 -1
  26. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
  27. data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -7
  28. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -7
  29. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +2 -7
  30. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +1 -1
  31. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -4
  32. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -0
  33. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
  34. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  35. data/lib/rubocop/cop/layout/line_length.rb +1 -0
  36. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +1 -1
  37. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +25 -0
  38. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -0
  39. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  40. data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -1
  41. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -0
  42. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -3
  43. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -3
  44. data/lib/rubocop/cop/lint/array_literal_in_regexp.rb +119 -0
  45. data/lib/rubocop/cop/lint/constant_reassignment.rb +152 -0
  46. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +1 -1
  47. data/lib/rubocop/cop/lint/duplicate_set_element.rb +20 -7
  48. data/lib/rubocop/cop/lint/float_comparison.rb +5 -2
  49. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  50. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +23 -5
  51. data/lib/rubocop/cop/lint/mixed_case_range.rb +1 -1
  52. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -1
  53. data/lib/rubocop/cop/lint/nested_method_definition.rb +5 -1
  54. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +4 -3
  55. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +17 -30
  56. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +2 -1
  57. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  58. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +2 -2
  59. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -1
  60. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
  61. data/lib/rubocop/cop/lint/shared_mutable_default.rb +65 -0
  62. data/lib/rubocop/cop/lint/syntax.rb +4 -1
  63. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -4
  64. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  65. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +2 -1
  66. data/lib/rubocop/cop/lint/void.rb +3 -2
  67. data/lib/rubocop/cop/metrics/collection_literal_length.rb +7 -0
  68. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
  69. data/lib/rubocop/cop/metrics/method_length.rb +8 -1
  70. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  71. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -7
  72. data/lib/rubocop/cop/mixin/comments_help.rb +2 -0
  73. data/lib/rubocop/cop/mixin/dig_help.rb +1 -1
  74. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  75. data/lib/rubocop/cop/mixin/hash_subset.rb +170 -0
  76. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +26 -16
  77. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  78. data/lib/rubocop/cop/mixin/statement_modifier.rb +8 -3
  79. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  80. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -2
  81. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  82. data/lib/rubocop/cop/security/compound_hash.rb +1 -0
  83. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -1
  84. data/lib/rubocop/cop/style/and_or.rb +1 -1
  85. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -4
  86. data/lib/rubocop/cop/style/array_first_last.rb +18 -2
  87. data/lib/rubocop/cop/style/block_delimiters.rb +6 -19
  88. data/lib/rubocop/cop/style/class_and_module_children.rb +5 -2
  89. data/lib/rubocop/cop/style/collection_methods.rb +1 -1
  90. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  91. data/lib/rubocop/cop/style/each_for_simple_loop.rb +4 -7
  92. data/lib/rubocop/cop/style/empty_else.rb +4 -2
  93. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  94. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  95. data/lib/rubocop/cop/style/exact_regexp_match.rb +3 -10
  96. data/lib/rubocop/cop/style/exponential_notation.rb +1 -1
  97. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  98. data/lib/rubocop/cop/style/float_division.rb +8 -4
  99. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  100. data/lib/rubocop/cop/style/hash_each_methods.rb +1 -1
  101. data/lib/rubocop/cop/style/hash_except.rb +9 -148
  102. data/lib/rubocop/cop/style/hash_slice.rb +65 -0
  103. data/lib/rubocop/cop/style/hash_syntax.rb +5 -2
  104. data/lib/rubocop/cop/style/it_assignment.rb +36 -0
  105. data/lib/rubocop/cop/style/map_to_set.rb +3 -2
  106. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +11 -1
  107. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -0
  108. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -1
  109. data/lib/rubocop/cop/style/missing_else.rb +2 -0
  110. data/lib/rubocop/cop/style/multiple_comparison.rb +26 -20
  111. data/lib/rubocop/cop/style/mutable_constant.rb +2 -2
  112. data/lib/rubocop/cop/style/object_then.rb +13 -15
  113. data/lib/rubocop/cop/style/open_struct_use.rb +4 -4
  114. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  115. data/lib/rubocop/cop/style/raise_args.rb +6 -4
  116. data/lib/rubocop/cop/style/random_with_offset.rb +3 -3
  117. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +2 -1
  118. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  119. data/lib/rubocop/cop/style/redundant_freeze.rb +1 -1
  120. data/lib/rubocop/cop/style/redundant_initialize.rb +12 -3
  121. data/lib/rubocop/cop/style/redundant_line_continuation.rb +34 -13
  122. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -4
  123. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +3 -0
  124. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  125. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  126. data/lib/rubocop/cop/style/redundant_self_assignment.rb +6 -5
  127. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  128. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +2 -1
  129. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  130. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  131. data/lib/rubocop/cop/style/single_line_methods.rb +2 -3
  132. data/lib/rubocop/cop/style/slicing_with_range.rb +40 -11
  133. data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -1
  134. data/lib/rubocop/cop/style/string_methods.rb +1 -1
  135. data/lib/rubocop/cop/style/super_arguments.rb +63 -15
  136. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -1
  137. data/lib/rubocop/cop/style/yoda_condition.rb +8 -4
  138. data/lib/rubocop/cop/style/yoda_expression.rb +1 -0
  139. data/lib/rubocop/cop/util.rb +9 -2
  140. data/lib/rubocop/cops_documentation_generator.rb +13 -13
  141. data/lib/rubocop/directive_comment.rb +9 -8
  142. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  143. data/lib/rubocop/lsp/diagnostic.rb +189 -0
  144. data/lib/rubocop/lsp/logger.rb +2 -2
  145. data/lib/rubocop/lsp/routes.rb +7 -23
  146. data/lib/rubocop/lsp/runtime.rb +15 -49
  147. data/lib/rubocop/lsp/stdin_runner.rb +83 -0
  148. data/lib/rubocop/options.rb +2 -1
  149. data/lib/rubocop/path_util.rb +11 -8
  150. data/lib/rubocop/result_cache.rb +13 -13
  151. data/lib/rubocop/rspec/expect_offense.rb +6 -2
  152. data/lib/rubocop/rspec/shared_contexts.rb +4 -1
  153. data/lib/rubocop/runner.rb +5 -6
  154. data/lib/rubocop/target_finder.rb +1 -0
  155. data/lib/rubocop/target_ruby.rb +15 -0
  156. data/lib/rubocop/version.rb +1 -1
  157. data/lib/rubocop.rb +6 -0
  158. data/lib/ruby_lsp/rubocop/addon.rb +78 -0
  159. data/lib/ruby_lsp/rubocop/wraps_built_in_lsp_runtime.rb +50 -0
  160. metadata +17 -8
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for duplicate elements in Regexp character classes.
6
+ # Checks for duplicate elements in `Regexp` character classes.
7
7
  #
8
8
  # @example
9
9
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for duplicate literal, constant, or variable elements in Set.
6
+ # Checks for duplicate literal, constant, or variable elements in `Set` and `SortedSet`.
7
7
  #
8
8
  # @example
9
9
  #
@@ -25,17 +25,28 @@ module RuboCop
25
25
  # # good
26
26
  # [:foo, :bar].to_set
27
27
  #
28
+ # # bad
29
+ # SortedSet[:foo, :bar, :foo]
30
+ #
31
+ # # good
32
+ # SortedSet[:foo, :bar]
33
+ #
34
+ # # bad
35
+ # SortedSet.new([:foo, :bar, :foo])
36
+ #
37
+ # # good
38
+ # SortedSet.new([:foo, :bar])
28
39
  class DuplicateSetElement < Base
29
40
  extend AutoCorrector
30
41
 
31
- MSG = 'Remove the duplicate element in Set.'
42
+ MSG = 'Remove the duplicate element in %<class_name>s.'
32
43
  RESTRICT_ON_SEND = %i[\[\] new to_set].freeze
33
44
 
34
45
  # @!method set_init_elements(node)
35
46
  def_node_matcher :set_init_elements, <<~PATTERN
36
47
  {
37
- (send (const {nil? cbase} :Set) :[] $...)
38
- (send (const {nil? cbase} :Set) :new (array $...))
48
+ (send (const {nil? cbase} {:Set :SortedSet}) :[] $...)
49
+ (send (const {nil? cbase} {:Set :SortedSet}) :new (array $...))
39
50
  (call (array $...) :to_set)
40
51
  }
41
52
  PATTERN
@@ -51,7 +62,7 @@ module RuboCop
51
62
  next if !set_element.literal? && !set_element.const_type? && !set_element.variable?
52
63
 
53
64
  if seen_elements.include?(set_element)
54
- register_offense(set_element, set_elements[index - 1])
65
+ register_offense(set_element, set_elements[index - 1], node)
55
66
  else
56
67
  seen_elements << set_element
57
68
  end
@@ -61,8 +72,10 @@ module RuboCop
61
72
 
62
73
  private
63
74
 
64
- def register_offense(current_element, prev_element)
65
- add_offense(current_element) do |corrector|
75
+ def register_offense(current_element, prev_element, node)
76
+ class_name = node.receiver.const_type? ? node.receiver.const_name : 'Set'
77
+
78
+ add_offense(current_element, message: format(MSG, class_name: class_name)) do |corrector|
66
79
  range = prev_element.source_range.end.join(current_element.source_range.end)
67
80
 
68
81
  corrector.remove(range)
@@ -36,7 +36,8 @@ module RuboCop
36
36
  # # https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/
37
37
  #
38
38
  class FloatComparison < Base
39
- MSG = 'Avoid (in)equality comparisons of floats as they are unreliable.'
39
+ MSG_EQUALITY = 'Avoid equality comparisons of floats as they are unreliable.'
40
+ MSG_INEQUALITY = 'Avoid inequality comparisons of floats as they are unreliable.'
40
41
 
41
42
  EQUALITY_METHODS = %i[== != eql? equal?].freeze
42
43
  FLOAT_RETURNING_METHODS = %i[to_f Float fdiv].freeze
@@ -52,8 +53,10 @@ module RuboCop
52
53
 
53
54
  return if literal_safe?(lhs) || literal_safe?(rhs)
54
55
 
55
- add_offense(node) if float?(lhs) || float?(rhs)
56
+ message = node.method?(:!=) ? MSG_INEQUALITY : MSG_EQUALITY
57
+ add_offense(node, message: message) if float?(lhs) || float?(rhs)
56
58
  end
59
+ alias on_csend on_send
57
60
 
58
61
  private
59
62
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Identifies Float literals which are, like, really really really
6
+ # Identifies `Float` literals which are, like, really really really
7
7
  # really really really really really big. Too big. No-one needs Floats
8
8
  # that big. If you need a float that big, something is wrong with you.
9
9
  #
@@ -5,6 +5,9 @@ module RuboCop
5
5
  module Lint
6
6
  # Checks for interpolated literals.
7
7
  #
8
+ # NOTE: Array literals interpolated in regexps are not handled by this cop, but
9
+ # by `Lint/ArrayLiteralInRegexp` instead.
10
+ #
8
11
  # @example
9
12
  #
10
13
  # # bad
@@ -21,6 +24,7 @@ module RuboCop
21
24
  MSG = 'Literal interpolation detected.'
22
25
  COMPOSITE = %i[array hash pair irange erange].freeze
23
26
 
27
+ # rubocop:disable Metrics/AbcSize
24
28
  def on_interpolation(begin_node)
25
29
  final_node = begin_node.children.last
26
30
  return unless offending?(final_node)
@@ -35,11 +39,18 @@ module RuboCop
35
39
  return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
36
40
 
37
41
  add_offense(final_node) do |corrector|
38
- return if final_node.dstr_type? # nested, fixed in next iteration
42
+ next if final_node.dstr_type? # nested, fixed in next iteration
43
+
44
+ replacement = if final_node.str_type? && !final_node.value.valid_encoding?
45
+ final_node.source.delete_prefix('"').delete_suffix('"')
46
+ else
47
+ expanded_value
48
+ end
39
49
 
40
- corrector.replace(final_node.parent, expanded_value)
50
+ corrector.replace(final_node.parent, replacement)
41
51
  end
42
52
  end
53
+ # rubocop:enable Metrics/AbcSize
43
54
 
44
55
  private
45
56
 
@@ -47,8 +58,10 @@ module RuboCop
47
58
  node &&
48
59
  !special_keyword?(node) &&
49
60
  prints_as_self?(node) &&
50
- # Special case for Layout/TrailingWhitespace
51
- !(space_literal?(node) && ends_heredoc_line?(node))
61
+ # Special case for `Layout/TrailingWhitespace`
62
+ !(space_literal?(node) && ends_heredoc_line?(node)) &&
63
+ # Handled by `Lint/ArrayLiteralInRegexp`
64
+ !array_in_regexp?(node)
52
65
  end
53
66
 
54
67
  def special_keyword?(node)
@@ -56,6 +69,11 @@ module RuboCop
56
69
  (node.str_type? && !node.loc.respond_to?(:begin)) || node.source_range.is?('__LINE__')
57
70
  end
58
71
 
72
+ def array_in_regexp?(node)
73
+ grandparent = node.parent.parent
74
+ node.array_type? && grandparent.regexp_type?
75
+ end
76
+
59
77
  # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
60
78
  def autocorrected_value(node)
61
79
  case node.type
@@ -168,7 +186,7 @@ module RuboCop
168
186
  end
169
187
 
170
188
  def space_literal?(node)
171
- node.str_type? && node.value.blank?
189
+ node.str_type? && node.value.valid_encoding? && node.value.blank?
172
190
  end
173
191
 
174
192
  def ends_heredoc_line?(node)
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # Offenses are registered for regexp character classes like `/[A-z]/`
9
9
  # as well as range objects like `('A'..'z')`.
10
10
  #
11
- # NOTE: Range objects cannot be autocorrected.
11
+ # NOTE: `Range` objects cannot be autocorrected.
12
12
  #
13
13
  # @safety
14
14
  # The cop autocorrects regexp character classes
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Do not mix named captures and numbered captures in a Regexp literal
6
+ # Do not mix named captures and numbered captures in a `Regexp` literal
7
7
  # because numbered capture is ignored if they're mixed.
8
8
  # Replace numbered captures with non-capturing groupings or
9
9
  # named captures.
@@ -96,7 +96,7 @@ module RuboCop
96
96
 
97
97
  def on_def(node)
98
98
  subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring
99
- return if node.defs_type? && subject.variable?
99
+ return if node.defs_type? && allowed_subject_type?(subject)
100
100
 
101
101
  def_ancestor = node.each_ancestor(:def, :defs).first
102
102
  return unless def_ancestor
@@ -117,6 +117,10 @@ module RuboCop
117
117
  child.class_constructor? || allowed_method_name?(child)
118
118
  end
119
119
 
120
+ def allowed_subject_type?(subject)
121
+ subject.variable? || subject.const_type? || subject.call_type?
122
+ end
123
+
120
124
  def allowed_method_name?(node)
121
125
  name = node.method_name
122
126
 
@@ -59,12 +59,12 @@ module RuboCop
59
59
 
60
60
  # @!method send_exist_node(node)
61
61
  def_node_search :send_exist_node, <<~PATTERN
62
- $(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
62
+ $(send (const {cbase nil?} {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
63
63
  PATTERN
64
64
 
65
65
  # @!method receiver_and_method_name(node)
66
66
  def_node_matcher :receiver_and_method_name, <<~PATTERN
67
- (send (const nil? $_) $_ ...)
67
+ (send (const {cbase nil?} $_) $_ ...)
68
68
  PATTERN
69
69
 
70
70
  # @!method force?(node)
@@ -78,7 +78,7 @@ module RuboCop
78
78
  PATTERN
79
79
 
80
80
  def on_send(node)
81
- return unless node.receiver
81
+ return unless node.receiver&.const_type?
82
82
  return unless if_node_child?(node)
83
83
  return if explicit_not_force?(node)
84
84
  return unless (exist_node = send_exist_node(node.parent).first)
@@ -116,6 +116,7 @@ module RuboCop
116
116
 
117
117
  def message_remove_file_exist_check(node)
118
118
  receiver, method_name = receiver_and_method_name(node)
119
+
119
120
  format(MSG_REMOVE_FILE_EXIST_CHECK, receiver: receiver, method_name: method_name)
120
121
  end
121
122
 
@@ -4,30 +4,27 @@ module RuboCop
4
4
  module Cop
5
5
  module Lint
6
6
  # Certain numeric operations have a constant result, usually 0 or 1.
7
- # Subtracting a number from itself or multiplying it by 0 will always return 0.
8
- # Additionally, a variable modulo 0 or itself will always return 0.
7
+ # Multiplying a number by 0 will always return 0.
9
8
  # Dividing a number by itself or raising it to the power of 0 will always return 1.
10
9
  # As such, they can be replaced with that result.
11
10
  # These are probably leftover from debugging, or are mistakes.
12
11
  # Other numeric operations that are similarly leftover from debugging or mistakes
13
12
  # are handled by Lint/UselessNumericOperation.
14
13
  #
14
+ # NOTE: This cop doesn't detect offenses for the `-` and `%` operator because it
15
+ # can't determine the type of `x`. If `x` is an `Array` or `String`, it doesn't perform
16
+ # a numeric operation.
17
+ #
15
18
  # @example
16
19
  #
17
20
  # # bad
18
- # x - x
19
21
  # x * 0
20
- # x % 1
21
- # x % x
22
22
  #
23
23
  # # good
24
24
  # 0
25
25
  #
26
26
  # # bad
27
- # x -= x
28
27
  # x *= 0
29
- # x %= 1
30
- # x %= x
31
28
  #
32
29
  # # good
33
30
  # x = 0
@@ -49,57 +46,47 @@ module RuboCop
49
46
  class NumericOperationWithConstantResult < Base
50
47
  extend AutoCorrector
51
48
  MSG = 'Numeric operation with a constant result detected.'
52
- RESTRICT_ON_SEND = %i[- * / % **].freeze
49
+ RESTRICT_ON_SEND = %i[* / **].freeze
53
50
 
54
51
  # @!method operation_with_constant_result?(node)
55
52
  def_node_matcher :operation_with_constant_result?,
56
- '(send (send nil? $_) $_ ({int | send nil?} $_))'
53
+ '(call (call nil? $_lhs) $_operation ({int | call nil?} $_rhs))'
57
54
 
58
55
  # @!method abbreviated_assignment_with_constant_result?(node)
59
56
  def_node_matcher :abbreviated_assignment_with_constant_result?,
60
- '(op-asgn (lvasgn $_) $_ ({int | lvar} $_))'
57
+ '(op-asgn (lvasgn $_lhs) $_operation ({int lvar} $_rhs))'
61
58
 
62
59
  def on_send(node)
63
- return unless operation_with_constant_result?(node)
64
-
65
- variable, operation, number = operation_with_constant_result?(node)
66
- result = constant_result?(variable, operation, number)
67
- return unless result
60
+ return unless (lhs, operation, rhs = operation_with_constant_result?(node))
61
+ return unless (result = constant_result?(lhs, operation, rhs))
68
62
 
69
63
  add_offense(node) do |corrector|
70
64
  corrector.replace(node, result.to_s)
71
65
  end
72
66
  end
67
+ alias on_csend on_send
73
68
 
74
69
  def on_op_asgn(node)
75
- return unless abbreviated_assignment_with_constant_result?(node)
76
-
77
- variable, operation, number = abbreviated_assignment_with_constant_result?(node)
78
- result = constant_result?(variable, operation, number)
79
- return unless result
70
+ return unless (lhs, operation, rhs = abbreviated_assignment_with_constant_result?(node))
71
+ return unless (result = constant_result?(lhs, operation, rhs))
80
72
 
81
73
  add_offense(node) do |corrector|
82
- corrector.replace(node, "#{variable} = #{result}")
74
+ corrector.replace(node, "#{lhs} = #{result}")
83
75
  end
84
76
  end
85
77
 
86
78
  private
87
79
 
88
- # rubocop :disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
89
- def constant_result?(variable, operation, number)
90
- if number.to_s == '0'
80
+ def constant_result?(lhs, operation, rhs)
81
+ if rhs.to_s == '0'
91
82
  return 0 if operation == :*
92
83
  return 1 if operation == :**
93
- elsif number.to_s == '1'
94
- return 0 if operation == :%
95
- elsif number == variable
96
- return 0 if %i[- %].include?(operation)
84
+ elsif rhs == lhs
97
85
  return 1 if operation == :/
98
86
  end
99
87
  # If we weren't able to find any matches, return false so we can bail out.
100
88
  false
101
89
  end
102
- # rubocop :enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
103
90
  end
104
91
  end
105
92
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Looks for references of Regexp captures that are out of range
6
+ # Looks for references of `Regexp` captures that are out of range
7
7
  # and thus always returns nil.
8
8
  #
9
9
  # @safety
@@ -61,6 +61,7 @@ module RuboCop
61
61
  check_regexp(node.receiver)
62
62
  end
63
63
  end
64
+ alias after_csend after_send
64
65
 
65
66
  def on_when(node)
66
67
  regexp_conditions = node.conditions.select(&:regexp_type?)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for redundant quantifiers inside Regexp literals.
6
+ # Checks for redundant quantifiers inside `Regexp` literals.
7
7
  #
8
8
  # It is always allowed when interpolation is used in a regexp literal,
9
9
  # because it's unknown what kind of string will be expanded as a result:
@@ -29,7 +29,7 @@ module RuboCop
29
29
  RESTRICT_ON_SEND = %i[print puts warn].freeze
30
30
 
31
31
  # @!method to_s_without_args?(node)
32
- def_node_matcher :to_s_without_args?, '(send _ :to_s)'
32
+ def_node_matcher :to_s_without_args?, '(call _ :to_s)'
33
33
 
34
34
  def on_interpolation(begin_node)
35
35
  final_node = begin_node.children.last
@@ -42,7 +42,7 @@ module RuboCop
42
42
  def on_send(node)
43
43
  return if node.receiver
44
44
 
45
- node.each_child_node(:send) do |child|
45
+ node.each_child_node(:send, :csend) do |child|
46
46
  next if !child.method?(:to_s) || child.arguments.any?
47
47
 
48
48
  register_offense(child, "`#{node.method_name}`")
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Lint
6
- # Checks for `rescue` blocks targeting the Exception class.
6
+ # Checks for `rescue` blocks targeting the `Exception` class.
7
7
  #
8
8
  # @example
9
9
  #
@@ -97,12 +97,19 @@ module RuboCop
97
97
  end
98
98
 
99
99
  def require_parentheses?(send_node)
100
+ return true if operator_inside_hash?(send_node)
100
101
  return false unless send_node.comparison_method?
101
102
  return false unless (node = send_node.parent)
102
103
 
103
104
  (node.respond_to?(:logical_operator?) && node.logical_operator?) ||
104
105
  (node.respond_to?(:comparison_method?) && node.comparison_method?)
105
106
  end
107
+
108
+ def operator_inside_hash?(send_node)
109
+ # If an operator call (without a dot) is inside a hash, it needs
110
+ # to be parenthesized when converted to safe navigation.
111
+ send_node.parent&.pair_type? && !send_node.loc.dot
112
+ end
106
113
  end
107
114
  end
108
115
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for `Hash` creation with a mutable default value.
7
+ # Creating a `Hash` in such a way will share the default value
8
+ # across all keys, causing unexpected behavior when modifying it.
9
+ #
10
+ # For example, when the `Hash` was created with an `Array` as the argument,
11
+ # calling `hash[:foo] << 'bar'` will also change the value of all
12
+ # other keys that have not been explicitly assigned to.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # Hash.new([])
17
+ # Hash.new({})
18
+ # Hash.new(Array.new)
19
+ # Hash.new(Hash.new)
20
+ #
21
+ # # okay -- In rare cases that intentionally have this behavior,
22
+ # # without disabling the cop, you can set the default explicitly.
23
+ # h = Hash.new
24
+ # h.default = []
25
+ # h[:a] << 1
26
+ # h[:b] << 2
27
+ # h # => {:a => [1, 2], :b => [1, 2]}
28
+ #
29
+ # # okay -- beware this will discard mutations and only remember assignments
30
+ # Hash.new { Array.new }
31
+ # Hash.new { Hash.new }
32
+ # Hash.new { {} }
33
+ # Hash.new { [] }
34
+ #
35
+ # # good - frozen solution will raise an error when mutation attempted
36
+ # Hash.new([].freeze)
37
+ # Hash.new({}.freeze)
38
+ #
39
+ # # good - using a proc will create a new object for each key
40
+ # h = Hash.new
41
+ # h.default_proc = ->(h, k) { [] }
42
+ # h.default_proc = ->(h, k) { {} }
43
+ #
44
+ # # good - using a block will create a new object for each key
45
+ # Hash.new { |h, k| h[k] = [] }
46
+ # Hash.new { |h, k| h[k] = {} }
47
+ class SharedMutableDefault < Base
48
+ MSG = 'Do not create a Hash with a mutable default value ' \
49
+ 'as the default value can accidentally be changed.'
50
+ RESTRICT_ON_SEND = %i[new].freeze
51
+
52
+ # @!method hash_initialized_with_mutable_shared_object?(node)
53
+ def_node_matcher :hash_initialized_with_mutable_shared_object?, <<~PATTERN
54
+ (send (const {nil? cbase} :Hash) :new {array hash (send (const {nil? cbase} {:Array :Hash}) :new)})
55
+ PATTERN
56
+
57
+ def on_send(node)
58
+ return unless hash_initialized_with_mutable_shared_object?(node)
59
+
60
+ add_offense(node)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -6,9 +6,12 @@ module RuboCop
6
6
  # Repacks Parser's diagnostics/errors
7
7
  # into RuboCop's offenses.
8
8
  class Syntax < Base
9
+ LEVELS = %i[error fatal].freeze
10
+
9
11
  def on_other_file
10
12
  add_offense_from_error(processed_source.parser_error) if processed_source.parser_error
11
- processed_source.diagnostics.each do |diagnostic|
13
+ syntax_errors = processed_source.diagnostics.select { |d| LEVELS.include?(d.level) }
14
+ syntax_errors.each do |diagnostic|
12
15
  add_offense_from_diagnostic(diagnostic, processed_source.ruby_version)
13
16
  end
14
17
  super
@@ -55,12 +55,9 @@ module RuboCop
55
55
  return if node.each_descendant(:dstr).any?
56
56
 
57
57
  regexp_constructor(node) do |text|
58
- Regexp::Parser.parse(text.value)&.each_expression do |expr|
58
+ parse_regexp(text.value)&.each_expression do |expr|
59
59
  detect_offenses(text, expr)
60
60
  end
61
- rescue Regexp::Parser::Error
62
- # Upon encountering an invalid regular expression,
63
- # we aim to proceed and identify any remaining potential offenses.
64
61
  end
65
62
  end
66
63
 
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # rescue, ensure, etc.
18
18
  #
19
19
  # This cop's autocorrection avoids cases like `a ||= 1` because removing assignment from
20
- # operator assignment can cause NameError if this assignment has been used to declare
20
+ # operator assignment can cause `NameError` if this assignment has been used to declare
21
21
  # a local variable. For example, replacing `a ||= 1` with `a || 1` may cause
22
22
  # "undefined local variable or method `a' for main:Object (NameError)".
23
23
  #
@@ -35,7 +35,7 @@ module RuboCop
35
35
  RESTRICT_ON_SEND = %i[+ - * / **].freeze
36
36
 
37
37
  # @!method useless_operation?(node)
38
- def_node_matcher :useless_operation?, '(send (send nil? $_) $_ (int $_))'
38
+ def_node_matcher :useless_operation?, '(call (call nil? $_) $_ (int $_))'
39
39
 
40
40
  # @!method useless_abbreviated_assignment?(node)
41
41
  def_node_matcher :useless_abbreviated_assignment?, '(op-asgn (lvasgn $_) $_ (int $_))'
@@ -50,6 +50,7 @@ module RuboCop
50
50
  corrector.replace(node, variable)
51
51
  end
52
52
  end
53
+ alias on_csend on_send
53
54
 
54
55
  def on_op_asgn(node)
55
56
  return unless useless_abbreviated_assignment?(node)
@@ -113,7 +113,8 @@ module RuboCop
113
113
  end
114
114
 
115
115
  def check_expression(expr)
116
- expr = expr.body if expr.if_type? && expr.modifier_form?
116
+ expr = expr.body if expr.if_type?
117
+ return unless expr
117
118
 
118
119
  check_literal(expr)
119
120
  check_var(expr)
@@ -229,7 +230,7 @@ module RuboCop
229
230
  end
230
231
 
231
232
  def autocorrect_void_expression(corrector, node)
232
- return if node.parent.if_type? && node.parent.modifier_form?
233
+ return if node.parent.if_type?
233
234
 
234
235
  corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left))
235
236
  end
@@ -52,12 +52,19 @@ module RuboCop
52
52
  'Prefer reading the data from an external source.'
53
53
  RESTRICT_ON_SEND = [:[]].freeze
54
54
 
55
+ # @!method set_const?(node)
56
+ def_node_matcher :set_const?, <<~PATTERN
57
+ (const {cbase nil?} :Set)
58
+ PATTERN
59
+
55
60
  def on_array(node)
56
61
  add_offense(node) if node.children.length >= collection_threshold
57
62
  end
58
63
  alias on_hash on_array
59
64
 
60
65
  def on_index(node)
66
+ return unless set_const?(node.receiver)
67
+
61
68
  add_offense(node) if node.arguments.length >= collection_threshold
62
69
  end
63
70
 
@@ -36,7 +36,7 @@ module RuboCop
36
36
  include MethodComplexity
37
37
  include Utils::IteratingBlock
38
38
 
39
- MSG = 'Cyclomatic complexity for %<method>s is too high. [%<complexity>d/%<max>d]'
39
+ MSG = 'Cyclomatic complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
40
40
  COUNTED_NODES = %i[if while until for csend block block_pass
41
41
  rescue when in_pattern and or or_asgn and_asgn].freeze
42
42
 
@@ -48,7 +48,7 @@ module RuboCop
48
48
  LABEL = 'Method'
49
49
 
50
50
  def on_def(node)
51
- return if allowed_method?(node.method_name) || matches_allowed_pattern?(node.method_name)
51
+ return if allowed?(node.method_name)
52
52
 
53
53
  check_code_length(node)
54
54
  end
@@ -57,6 +57,9 @@ module RuboCop
57
57
  def on_block(node)
58
58
  return unless node.method?(:define_method)
59
59
 
60
+ method_name = node.send_node.first_argument
61
+ return if method_name.basic_literal? && allowed?(method_name.value)
62
+
60
63
  check_code_length(node)
61
64
  end
62
65
  alias on_numblock on_block
@@ -66,6 +69,10 @@ module RuboCop
66
69
  def cop_label
67
70
  LABEL
68
71
  end
72
+
73
+ def allowed?(method_name)
74
+ allowed_method?(method_name) || matches_allowed_pattern?(method_name)
75
+ end
69
76
  end
70
77
  end
71
78
  end
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # end # ===
28
28
  # end # 7 complexity points
29
29
  class PerceivedComplexity < CyclomaticComplexity
30
- MSG = 'Perceived complexity for %<method>s is too high. [%<complexity>d/%<max>d]'
30
+ MSG = 'Perceived complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
31
31
 
32
32
  COUNTED_NODES = (CyclomaticComplexity::COUNTED_NODES - [:when] + [:case]).freeze
33
33