rubocop 1.69.2 → 1.70.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +19 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +3 -3
  6. data/lib/rubocop/config.rb +13 -4
  7. data/lib/rubocop/config_loader.rb +4 -0
  8. data/lib/rubocop/config_loader_resolver.rb +14 -3
  9. data/lib/rubocop/config_validator.rb +18 -8
  10. data/lib/rubocop/cop/base.rb +6 -0
  11. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  12. data/lib/rubocop/cop/internal_affairs/cop_enabled.rb +85 -0
  13. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +4 -3
  14. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  15. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -7
  16. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  17. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
  18. data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -7
  19. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -7
  20. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -6
  21. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -4
  22. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -0
  23. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
  24. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  25. data/lib/rubocop/cop/layout/line_length.rb +1 -0
  26. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +24 -0
  27. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  28. data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -1
  29. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -3
  30. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -3
  31. data/lib/rubocop/cop/lint/constant_reassignment.rb +152 -0
  32. data/lib/rubocop/cop/lint/duplicate_set_element.rb +20 -7
  33. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +11 -3
  34. data/lib/rubocop/cop/lint/nested_method_definition.rb +5 -1
  35. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +4 -3
  36. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +6 -14
  37. data/lib/rubocop/cop/lint/shared_mutable_default.rb +65 -0
  38. data/lib/rubocop/cop/lint/syntax.rb +4 -1
  39. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -4
  40. data/lib/rubocop/cop/lint/void.rb +3 -2
  41. data/lib/rubocop/cop/metrics/method_length.rb +8 -1
  42. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -7
  43. data/lib/rubocop/cop/mixin/comments_help.rb +2 -0
  44. data/lib/rubocop/cop/mixin/dig_help.rb +1 -1
  45. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +26 -16
  46. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  47. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  48. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  49. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  50. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -1
  51. data/lib/rubocop/cop/style/and_or.rb +1 -1
  52. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -4
  53. data/lib/rubocop/cop/style/class_and_module_children.rb +5 -2
  54. data/lib/rubocop/cop/style/each_for_simple_loop.rb +3 -6
  55. data/lib/rubocop/cop/style/empty_else.rb +4 -2
  56. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  57. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  58. data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -9
  59. data/lib/rubocop/cop/style/exponential_notation.rb +1 -1
  60. data/lib/rubocop/cop/style/float_division.rb +8 -4
  61. data/lib/rubocop/cop/style/hash_except.rb +54 -67
  62. data/lib/rubocop/cop/style/hash_syntax.rb +5 -2
  63. data/lib/rubocop/cop/style/it_assignment.rb +36 -0
  64. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +11 -1
  65. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -0
  66. data/lib/rubocop/cop/style/missing_else.rb +2 -0
  67. data/lib/rubocop/cop/style/multiple_comparison.rb +26 -20
  68. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  69. data/lib/rubocop/cop/style/object_then.rb +13 -15
  70. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  71. data/lib/rubocop/cop/style/raise_args.rb +5 -3
  72. data/lib/rubocop/cop/style/random_with_offset.rb +3 -3
  73. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +2 -1
  74. data/lib/rubocop/cop/style/redundant_initialize.rb +12 -3
  75. data/lib/rubocop/cop/style/redundant_line_continuation.rb +7 -3
  76. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -4
  77. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +3 -0
  78. data/lib/rubocop/cop/style/redundant_self_assignment.rb +6 -5
  79. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  80. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +2 -1
  81. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  82. data/lib/rubocop/cop/style/single_line_methods.rb +2 -3
  83. data/lib/rubocop/cop/style/slicing_with_range.rb +40 -11
  84. data/lib/rubocop/cop/style/super_arguments.rb +63 -15
  85. data/lib/rubocop/cop/style/yoda_condition.rb +8 -4
  86. data/lib/rubocop/cop/style/yoda_expression.rb +1 -0
  87. data/lib/rubocop/cop/util.rb +9 -2
  88. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  89. data/lib/rubocop/lsp/diagnostic.rb +189 -0
  90. data/lib/rubocop/lsp/logger.rb +2 -2
  91. data/lib/rubocop/lsp/routes.rb +7 -23
  92. data/lib/rubocop/lsp/runtime.rb +15 -49
  93. data/lib/rubocop/lsp/stdin_runner.rb +83 -0
  94. data/lib/rubocop/path_util.rb +11 -8
  95. data/lib/rubocop/rspec/shared_contexts.rb +4 -1
  96. data/lib/rubocop/runner.rb +5 -6
  97. data/lib/rubocop/target_ruby.rb +15 -0
  98. data/lib/rubocop/version.rb +1 -1
  99. data/lib/rubocop.rb +3 -0
  100. data/lib/ruby_lsp/rubocop/addon.rb +78 -0
  101. data/lib/ruby_lsp/rubocop/wraps_built_in_lsp_runtime.rb +50 -0
  102. metadata +12 -4
@@ -48,7 +48,7 @@ module RuboCop
48
48
 
49
49
  def on_new_investigation
50
50
  processed_source.lines.each_with_index do |line, index|
51
- next unless line.end_with?(' ', "\t")
51
+ next unless line.match?(/[[:blank:]]\z/)
52
52
 
53
53
  process_line(line, index + 1)
54
54
  end
@@ -84,7 +84,7 @@ module RuboCop
84
84
  end
85
85
 
86
86
  def whitespace_is_indentation?(range, level)
87
- range.source[/[ \t]+/].length <= level
87
+ range.source[/[[:blank:]]+/].length <= level
88
88
  end
89
89
 
90
90
  def whitespace_only?(range)
@@ -123,7 +123,9 @@ module RuboCop
123
123
  end
124
124
 
125
125
  def offense_range(lineno, line)
126
- source_range(processed_source.buffer, lineno, (line.rstrip.length)...(line.length))
126
+ source_range(
127
+ processed_source.buffer, lineno, (line.sub(/[[:blank:]]+\z/, '').length)...(line.length)
128
+ )
127
129
  end
128
130
  end
129
131
  end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Lint
6
+ # Checks for constant reassignments.
7
+ #
8
+ # Emulates Ruby's runtime warning "already initialized constant X"
9
+ # when a constant is reassigned in the same file and namespace using the
10
+ # `NAME = value` syntax.
11
+ #
12
+ # The cop cannot catch all offenses, like, for example, when a constant
13
+ # is reassigned in another file, or when using metaprogramming (`Module#const_set`).
14
+ #
15
+ # The cop only takes into account constants assigned in a "simple" way: directly
16
+ # inside class/module definition, or within another constant. Other type of assignments
17
+ # (e.g., inside a conditional) are disregarded.
18
+ #
19
+ # The cop also tracks constant removal using `Module#remove_const` with symbol
20
+ # or string argument.
21
+ #
22
+ # @example
23
+ # # bad
24
+ # X = :foo
25
+ # X = :bar
26
+ #
27
+ # # bad
28
+ # class A
29
+ # X = :foo
30
+ # X = :bar
31
+ # end
32
+ #
33
+ # # bad
34
+ # module A
35
+ # X = :foo
36
+ # X = :bar
37
+ # end
38
+ #
39
+ # # good - keep only one assignment
40
+ # X = :bar
41
+ #
42
+ # class A
43
+ # X = :bar
44
+ # end
45
+ #
46
+ # module A
47
+ # X = :bar
48
+ # end
49
+ #
50
+ # # good - use OR assignment
51
+ # X = :foo
52
+ # X ||= :bar
53
+ #
54
+ # # good - use conditional assignment
55
+ # X = :foo
56
+ # X = :bar unless defined?(X)
57
+ #
58
+ # # good - remove the assigned constant first
59
+ # class A
60
+ # X = :foo
61
+ # remove_const :X
62
+ # X = :bar
63
+ # end
64
+ #
65
+ class ConstantReassignment < Base
66
+ MSG = 'Constant `%<constant>s` is already assigned in this namespace.'
67
+
68
+ RESTRICT_ON_SEND = %i[remove_const].freeze
69
+
70
+ # @!method remove_constant(node)
71
+ def_node_matcher :remove_constant, <<~PATTERN
72
+ (send _ :remove_const
73
+ ({sym str} $_))
74
+ PATTERN
75
+
76
+ def on_casgn(node)
77
+ return unless fixed_constant_path?(node)
78
+ return unless simple_assignment?(node)
79
+ return if constant_names.add?(fully_qualified_constant_name(node))
80
+
81
+ add_offense(node, message: format(MSG, constant: node.name))
82
+ end
83
+
84
+ def on_send(node)
85
+ constant = remove_constant(node)
86
+
87
+ return unless constant
88
+
89
+ namespaces = ancestor_namespaces(node)
90
+
91
+ return if namespaces.none?
92
+
93
+ constant_names.delete(fully_qualified_name_for(namespaces, constant))
94
+ end
95
+
96
+ private
97
+
98
+ def fixed_constant_path?(node)
99
+ node.each_path.all? { |path| path.cbase_type? || path.const_type? || path.self_type? }
100
+ end
101
+
102
+ def simple_assignment?(node)
103
+ node.ancestors.all? do |ancestor|
104
+ return true if namespace_definition?(ancestor)
105
+
106
+ ancestor.begin_type? || ancestor.literal? || ancestor.casgn_type? ||
107
+ freeze_method?(ancestor)
108
+ end
109
+ end
110
+
111
+ def namespace_definition?(node)
112
+ node.module_type? || node.class_type?
113
+ end
114
+
115
+ def freeze_method?(node)
116
+ node.send_type? && node.method?(:freeze)
117
+ end
118
+
119
+ def fully_qualified_constant_name(node)
120
+ if node.absolute?
121
+ namespace = node.namespace.const_type? ? node.namespace.source : nil
122
+
123
+ "#{namespace}::#{node.name}"
124
+ else
125
+ constant_namespaces = ancestor_namespaces(node) + constant_namespaces(node)
126
+
127
+ fully_qualified_name_for(constant_namespaces, node.name)
128
+ end
129
+ end
130
+
131
+ def fully_qualified_name_for(namespaces, constant)
132
+ ['', *namespaces, constant].join('::')
133
+ end
134
+
135
+ def constant_namespaces(node)
136
+ node.each_path.select(&:const_type?).map(&:short_name)
137
+ end
138
+
139
+ def ancestor_namespaces(node)
140
+ node
141
+ .each_ancestor(:class, :module)
142
+ .map { |ancestor| ancestor.identifier.short_name }
143
+ .reverse
144
+ end
145
+
146
+ def constant_names
147
+ @constant_names ||= Set.new
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -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)
@@ -21,6 +21,7 @@ module RuboCop
21
21
  MSG = 'Literal interpolation detected.'
22
22
  COMPOSITE = %i[array hash pair irange erange].freeze
23
23
 
24
+ # rubocop:disable Metrics/AbcSize
24
25
  def on_interpolation(begin_node)
25
26
  final_node = begin_node.children.last
26
27
  return unless offending?(final_node)
@@ -35,11 +36,18 @@ module RuboCop
35
36
  return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
36
37
 
37
38
  add_offense(final_node) do |corrector|
38
- return if final_node.dstr_type? # nested, fixed in next iteration
39
+ next if final_node.dstr_type? # nested, fixed in next iteration
39
40
 
40
- corrector.replace(final_node.parent, expanded_value)
41
+ replacement = if final_node.str_type? && !final_node.value.valid_encoding?
42
+ final_node.source.delete_prefix('"').delete_suffix('"')
43
+ else
44
+ expanded_value
45
+ end
46
+
47
+ corrector.replace(final_node.parent, replacement)
41
48
  end
42
49
  end
50
+ # rubocop:enable Metrics/AbcSize
43
51
 
44
52
  private
45
53
 
@@ -168,7 +176,7 @@ module RuboCop
168
176
  end
169
177
 
170
178
  def space_literal?(node)
171
- node.str_type? && node.value.blank?
179
+ node.str_type? && node.value.valid_encoding? && node.value.blank?
172
180
  end
173
181
 
174
182
  def ends_heredoc_line?(node)
@@ -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,7 +46,7 @@ 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?,
@@ -85,21 +82,16 @@ module RuboCop
85
82
 
86
83
  private
87
84
 
88
- # rubocop :disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
89
85
  def constant_result?(variable, operation, number)
90
86
  if number.to_s == '0'
91
87
  return 0 if operation == :*
92
88
  return 1 if operation == :**
93
- elsif number.to_s == '1'
94
- return 0 if operation == :%
95
89
  elsif number == variable
96
- return 0 if %i[- %].include?(operation)
97
90
  return 1 if operation == :/
98
91
  end
99
92
  # If we weren't able to find any matches, return false so we can bail out.
100
93
  false
101
94
  end
102
- # rubocop :enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
103
95
  end
104
96
  end
105
97
  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
 
@@ -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
@@ -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
@@ -43,7 +43,7 @@ module RuboCop
43
43
  # (Note: Passes may not happen exactly in this sequence.)
44
44
  module CheckLineBreakable
45
45
  def extract_breakable_node(node, max)
46
- if node.send_type?
46
+ if node.call_type?
47
47
  return if chained_to_heredoc?(node)
48
48
 
49
49
  args = process_args(node.arguments)
@@ -74,9 +74,9 @@ module RuboCop
74
74
  def extract_first_element_over_column_limit(node, elements, max)
75
75
  line = node.first_line
76
76
 
77
- # If a `send` node is not parenthesized, don't move the first element, because it
77
+ # If a `send` or `csend` node is not parenthesized, don't move the first element, because it
78
78
  # can result in changed behavior or a syntax error.
79
- if node.send_type? && !node.parenthesized? && !first_argument_is_heredoc?(node)
79
+ if node.call_type? && !node.parenthesized? && !first_argument_is_heredoc?(node)
80
80
  elements = elements.drop(1)
81
81
  end
82
82
 
@@ -98,10 +98,10 @@ module RuboCop
98
98
  end
99
99
 
100
100
  # @api private
101
- # If a send node contains a heredoc argument, splitting cannot happen
101
+ # If a `send` or `csend` node contains a heredoc argument, splitting cannot happen
102
102
  # after the heredoc or else it will cause a syntax error.
103
103
  def shift_elements_for_heredoc_arg(node, elements, index)
104
- return index unless node.send_type? || node.array_type?
104
+ return index unless node.call_type? || node.array_type?
105
105
 
106
106
  heredoc_index = elements.index { |arg| arg.respond_to?(:heredoc?) && arg.heredoc? }
107
107
  return index unless heredoc_index
@@ -156,7 +156,7 @@ module RuboCop
156
156
 
157
157
  if ancestor.hash_type? || ancestor.array_type?
158
158
  elements = ancestor.children
159
- elsif ancestor.send_type?
159
+ elsif ancestor.call_type?
160
160
  elements = process_args(ancestor.arguments)
161
161
  else
162
162
  next
@@ -176,7 +176,7 @@ module RuboCop
176
176
  return children_could_be_broken_up?(ancestor.children)
177
177
  end
178
178
 
179
- next unless ancestor.send_type?
179
+ next unless ancestor.call_type?
180
180
 
181
181
  args = process_args(ancestor.arguments)
182
182
  return children_could_be_broken_up?(args) if breakable_collection?(ancestor, args)
@@ -73,6 +73,8 @@ module RuboCop
73
73
  node.else_branch.loc.line
74
74
  elsif node.elsif?
75
75
  node.each_ancestor(:if).find(&:if?).loc.end.line
76
+ elsif node.if? && node.parent && parentheses?(node.parent)
77
+ node.parent.loc.end.line
76
78
  end
77
79
  elsif node.block_type? || node.numblock_type?
78
80
  node.loc.end.line
@@ -20,7 +20,7 @@ module RuboCop
20
20
  private
21
21
 
22
22
  def dig_chain_enabled?
23
- @config.for_cop('Style/DigChain')['Enabled']
23
+ @config.cop_enabled?('Style/DigChain')
24
24
  end
25
25
  end
26
26
  end