rubocop 1.69.2 → 1.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -5,6 +5,10 @@ module RuboCop
5
5
  # Common functionality for checking whether an AST node/token is aligned
6
6
  # with something on a preceding or following line
7
7
  module PrecedingFollowingAlignment
8
+ # Tokens that end with an `=`, as well as `<<`, that can be aligned together:
9
+ # `=`, `==`, `===`, `!=`, `<=`, `>=`, `<<` and operator assignment (`+=`, etc).
10
+ ASSIGNMENT_OR_COMPARISON_TOKENS = %i[tEQL tEQ tEQQ tNEQ tLEQ tGEQ tOP_ASGN tLSHFT].freeze
11
+
8
12
  private
9
13
 
10
14
  def allow_for_alignment?
@@ -19,16 +23,20 @@ module RuboCop
19
23
  aligned_with_adjacent_line?(range, method(:aligned_operator?))
20
24
  end
21
25
 
22
- def aligned_with_preceding_assignment(token)
26
+ # Allows alignment with a preceding operator that ends with an `=`,
27
+ # including assignment and comparison operators.
28
+ def aligned_with_preceding_equals_operator(token)
23
29
  preceding_line_range = token.line.downto(1)
24
30
 
25
- aligned_with_assignment(token, preceding_line_range)
31
+ aligned_with_equals_sign(token, preceding_line_range)
26
32
  end
27
33
 
28
- def aligned_with_subsequent_assignment(token)
34
+ # Allows alignment with a subsequent operator that ends with an `=`,
35
+ # including assignment and comparison operators.
36
+ def aligned_with_subsequent_equals_operator(token)
29
37
  subsequent_line_range = token.line.upto(processed_source.lines.length)
30
38
 
31
- aligned_with_assignment(token, subsequent_line_range)
39
+ aligned_with_equals_sign(token, subsequent_line_range)
32
40
  end
33
41
 
34
42
  def aligned_with_adjacent_line?(range, predicate)
@@ -75,11 +83,11 @@ module RuboCop
75
83
  end
76
84
 
77
85
  def aligned_token?(range, line, lineno)
78
- aligned_words?(range, line) || aligned_assignment?(range, lineno)
86
+ aligned_words?(range, line) || aligned_equals_operator?(range, lineno)
79
87
  end
80
88
 
81
89
  def aligned_operator?(range, line, lineno)
82
- aligned_identical?(range, line) || aligned_assignment?(range, lineno)
90
+ aligned_identical?(range, line) || aligned_equals_operator?(range, lineno)
83
91
  end
84
92
 
85
93
  def aligned_words?(range, line)
@@ -90,22 +98,24 @@ module RuboCop
90
98
  token == line[left_edge, token.length]
91
99
  end
92
100
 
93
- def aligned_assignment?(range, lineno)
94
- # Check that assignment is aligned with a previous assignment operator
95
- # ie. an equals sign, an operator assignment, or an append operator (`<<`)
101
+ def aligned_equals_operator?(range, lineno)
102
+ # Check that the operator is aligned with a previous assignment or comparison operator
103
+ # ie. an equals sign, an operator assignment (eg. `+=`), a comparison (`==`, `<=`, etc.).
104
+ # Since append operators (`<<`) are a type of assignment, they are allowed as well,
105
+ # despite not ending with a literal equals character.
96
106
  line_range = processed_source.buffer.line_range(lineno)
97
107
  return false unless line_range
98
108
 
99
109
  # Find the specific token to avoid matching up to operators inside strings
100
- assignment_token = processed_source.tokens_within(line_range).detect do |token|
101
- token.equal_sign? || token.type == :tLSHFT
110
+ operator_token = processed_source.tokens_within(line_range).detect do |token|
111
+ ASSIGNMENT_OR_COMPARISON_TOKENS.include?(token.type)
102
112
  end
103
113
 
104
- aligned_with_preceding_assignment?(range, assignment_token) ||
105
- aligned_with_append_operator?(range, assignment_token)
114
+ aligned_with_preceding_equals?(range, operator_token) ||
115
+ aligned_with_append_operator?(range, operator_token)
106
116
  end
107
117
 
108
- def aligned_with_preceding_assignment?(range, token)
118
+ def aligned_with_preceding_equals?(range, token)
109
119
  return false unless token
110
120
 
111
121
  range.source[-1] == '=' && range.last_column == token.pos.last_column
@@ -123,7 +133,7 @@ module RuboCop
123
133
  range.source == line[range.column, range.size]
124
134
  end
125
135
 
126
- def aligned_with_assignment(token, line_range)
136
+ def aligned_with_equals_sign(token, line_range)
127
137
  token_line_indent = processed_source.line_indentation(token.line)
128
138
  assignment_lines = relevant_assignment_lines(line_range)
129
139
  relevant_line_number = assignment_lines[1]
@@ -135,7 +145,7 @@ module RuboCop
135
145
  return :none if relevant_indent < token_line_indent
136
146
  return :none unless processed_source.lines[relevant_line_number - 1]
137
147
 
138
- aligned_assignment?(token.pos, relevant_line_number) ? :yes : :no
148
+ aligned_equals_operator?(token.pos, relevant_line_number) ? :yes : :no
139
149
  end
140
150
 
141
151
  def assignment_lines
@@ -36,7 +36,7 @@ module RuboCop
36
36
  end
37
37
 
38
38
  def space_required_after?(token)
39
- (token.left_curly_brace? || token.type == :tLAMBEG) && space_required_after_lcurly?
39
+ token.left_curly_brace? && space_required_after_lcurly?
40
40
  end
41
41
 
42
42
  def space_required_after_lcurly?
@@ -96,7 +96,7 @@ module RuboCop
96
96
  end
97
97
 
98
98
  def max_line_length
99
- return unless config.for_cop('Layout/LineLength')['Enabled']
99
+ return unless config.cop_enabled?('Layout/LineLength')
100
100
 
101
101
  config.for_cop('Layout/LineLength')['Max']
102
102
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
  end
27
27
 
28
28
  def string_literals_config
29
- config.for_cop('Style/StringLiterals')
29
+ config.for_enabled_cop('Style/StringLiterals')
30
30
  end
31
31
  end
32
32
  end
@@ -138,7 +138,7 @@ module RuboCop
138
138
  def use_block_argument_as_local_variable?(node, last_argument)
139
139
  return false if node.body.nil?
140
140
 
141
- node.body.each_descendant(:lvar, :lvasgn).any? do |lvar|
141
+ node.body.each_node(:lvar, :lvasgn).any? do |lvar|
142
142
  !lvar.parent.block_pass_type? && lvar.node_parts[0].to_s == last_argument
143
143
  end
144
144
  end
@@ -112,6 +112,26 @@ module RuboCop
112
112
  # private attr :quux
113
113
  #
114
114
  # end
115
+ #
116
+ # @example AllowModifiersOnAliasMethod: true (default)
117
+ # # good
118
+ # class Foo
119
+ #
120
+ # public alias_method :bar, :foo
121
+ # protected alias_method :baz, :foo
122
+ # private alias_method :qux, :foo
123
+ #
124
+ # end
125
+ #
126
+ # @example AllowModifiersOnAliasMethod: false
127
+ # # bad
128
+ # class Foo
129
+ #
130
+ # public alias_method :bar, :foo
131
+ # protected alias_method :baz, :foo
132
+ # private alias_method :qux, :foo
133
+ #
134
+ # end
115
135
  class AccessModifierDeclarations < Base
116
136
  extend AutoCorrector
117
137
 
@@ -145,6 +165,12 @@ module RuboCop
145
165
  (send nil? {:attr :attr_reader :attr_writer :attr_accessor} _+))
146
166
  PATTERN
147
167
 
168
+ # @!method access_modifier_with_alias_method?, <<~PATTERN
169
+ def_node_matcher :access_modifier_with_alias_method?, <<~PATTERN
170
+ (send nil? {:private :protected :public :module_function}
171
+ (send nil? :alias_method _ _))
172
+ PATTERN
173
+
148
174
  def on_send(node)
149
175
  return if allowed?(node)
150
176
 
@@ -164,7 +190,8 @@ module RuboCop
164
190
  !node.access_modifier? ||
165
191
  ALLOWED_NODE_TYPES.include?(node.parent&.type) ||
166
192
  allow_modifiers_on_symbols?(node) ||
167
- allow_modifiers_on_attrs?(node)
193
+ allow_modifiers_on_attrs?(node) ||
194
+ allow_modifiers_on_alias_method?(node)
168
195
  end
169
196
 
170
197
  def autocorrect(corrector, node)
@@ -194,6 +221,10 @@ module RuboCop
194
221
  cop_config['AllowModifiersOnAttrs'] && access_modifier_with_attr?(node)
195
222
  end
196
223
 
224
+ def allow_modifiers_on_alias_method?(node)
225
+ cop_config['AllowModifiersOnAliasMethod'] && access_modifier_with_alias_method?(node)
226
+ end
227
+
197
228
  def offense?(node)
198
229
  (group_style? && access_modifier_is_inlined?(node) &&
199
230
  !node.parent&.if_type? && !right_siblings_same_inline_method?(node)) ||
@@ -127,7 +127,7 @@ module RuboCop
127
127
  end
128
128
 
129
129
  def correct_other(node, corrector)
130
- return if node.source_range.begin.is?('(')
130
+ return if node.parenthesized_call?
131
131
 
132
132
  corrector.wrap(node, '(', ')')
133
133
  end
@@ -540,10 +540,7 @@ module RuboCop
540
540
  end
541
541
 
542
542
  def explicit_block_name?
543
- block_forwarding_config = config.for_cop('Naming/BlockForwarding')
544
- return false unless block_forwarding_config['Enabled']
545
-
546
- block_forwarding_config['EnforcedStyle'] == 'explicit'
543
+ config.for_enabled_cop('Naming/BlockForwarding')['EnforcedStyle'] == 'explicit'
547
544
  end
548
545
  end
549
546
  end
@@ -126,9 +126,12 @@ module RuboCop
126
126
  end
127
127
 
128
128
  def unindent(corrector, node)
129
- return if node.body.children.last.nil?
129
+ return unless node.body.children.last
130
130
 
131
- column_delta = configured_indentation_width - leading_spaces(node.body.children.last).size
131
+ last_child_leading_spaces = leading_spaces(node.body.children.last)
132
+ return if leading_spaces(node).size == last_child_leading_spaces.size
133
+
134
+ column_delta = configured_indentation_width - last_child_leading_spaces.size
132
135
  return if column_delta.zero?
133
136
 
134
137
  AlignmentCorrector.correct(corrector, processed_source, node, column_delta)
@@ -53,8 +53,7 @@ module RuboCop
53
53
  (block
54
54
  (call
55
55
  (begin
56
- (${irange erange}
57
- (int $_) (int $_)))
56
+ ($range (int $_) (int $_)))
58
57
  :each)
59
58
  (args ...)
60
59
  ...)
@@ -65,8 +64,7 @@ module RuboCop
65
64
  (block
66
65
  (call
67
66
  (begin
68
- ({irange erange}
69
- (int 0) (int _)))
67
+ (range (int 0) (int _)))
70
68
  :each)
71
69
  (args ...)
72
70
  ...)
@@ -77,8 +75,7 @@ module RuboCop
77
75
  (block
78
76
  (call
79
77
  (begin
80
- ({irange erange}
81
- (int _) (int _)))
78
+ (range (int _) (int _)))
82
79
  :each)
83
80
  (args)
84
81
  ...)
@@ -131,6 +131,8 @@ module RuboCop
131
131
  extend AutoCorrector
132
132
 
133
133
  MSG = 'Redundant `else`-clause.'
134
+ NIL_STYLES = %i[nil both].freeze
135
+ EMPTY_STYLES = %i[empty both].freeze
134
136
 
135
137
  def on_normal_if_unless(node)
136
138
  check(node)
@@ -150,11 +152,11 @@ module RuboCop
150
152
  end
151
153
 
152
154
  def nil_style?
153
- style == :nil || style == :both
155
+ NIL_STYLES.include?(style)
154
156
  end
155
157
 
156
158
  def empty_style?
157
- style == :empty || style == :both
159
+ EMPTY_STYLES.include?(style)
158
160
  end
159
161
 
160
162
  def empty_check(node)
@@ -136,7 +136,7 @@ module RuboCop
136
136
  def frozen_strings?
137
137
  return true if frozen_string_literals_enabled?
138
138
 
139
- frozen_string_cop_enabled = config.for_cop('Style/FrozenStringLiteralComment')['Enabled']
139
+ frozen_string_cop_enabled = config.cop_enabled?('Style/FrozenStringLiteralComment')
140
140
  frozen_string_cop_enabled &&
141
141
  !frozen_string_literals_disabled? &&
142
142
  string_literals_frozen_by_default?.nil?
@@ -109,7 +109,7 @@ module RuboCop
109
109
  end
110
110
 
111
111
  def max_line_length
112
- return unless config.for_cop('Layout/LineLength')['Enabled']
112
+ return unless config.cop_enabled?('Layout/LineLength')
113
113
 
114
114
  config.for_cop('Layout/LineLength')['Max']
115
115
  end
@@ -40,15 +40,8 @@ module RuboCop
40
40
  def on_send(node)
41
41
  return unless (receiver = node.receiver)
42
42
  return unless (regexp = exact_regexp_match(node))
43
-
44
- parsed_regexp = begin
45
- Regexp::Parser.parse(regexp)
46
- rescue Regexp::Parser::Error
47
- # Upon encountering an invalid regular expression,
48
- # we aim to proceed and identify any remaining potential offenses.
49
- end
50
-
51
- return unless parsed_regexp && exact_match_pattern?(parsed_regexp)
43
+ return unless (parsed_regexp = parse_regexp(regexp))
44
+ return unless exact_match_pattern?(parsed_regexp)
52
45
 
53
46
  prefer = "#{receiver.source} #{new_method(node)} '#{parsed_regexp[1].text}'"
54
47
 
@@ -8,7 +8,7 @@ module RuboCop
8
8
  #
9
9
  # * `scientific` which enforces a mantissa between 1 (inclusive) and 10 (exclusive).
10
10
  # * `engineering` which enforces the exponent to be a multiple of 3 and the mantissa
11
- # to be between 0.1 (inclusive) and 10 (exclusive).
11
+ # to be between 0.1 (inclusive) and 1000 (exclusive).
12
12
  # * `integral` which enforces the mantissa to always be a whole number without
13
13
  # trailing zeroes.
14
14
  #
@@ -65,19 +65,23 @@ module RuboCop
65
65
 
66
66
  # @!method right_coerce?(node)
67
67
  def_node_matcher :right_coerce?, <<~PATTERN
68
- (send _ :/ (send _ :to_f))
68
+ (send _ :/ #to_f_method?)
69
69
  PATTERN
70
70
  # @!method left_coerce?(node)
71
71
  def_node_matcher :left_coerce?, <<~PATTERN
72
- (send (send _ :to_f) :/ _)
72
+ (send #to_f_method? :/ _)
73
73
  PATTERN
74
74
  # @!method both_coerce?(node)
75
75
  def_node_matcher :both_coerce?, <<~PATTERN
76
- (send (send _ :to_f) :/ (send _ :to_f))
76
+ (send #to_f_method? :/ #to_f_method?)
77
77
  PATTERN
78
78
  # @!method any_coerce?(node)
79
79
  def_node_matcher :any_coerce?, <<~PATTERN
80
- {(send _ :/ (send _ :to_f)) (send (send _ :to_f) :/ _)}
80
+ {(send _ :/ #to_f_method?) (send #to_f_method? :/ _)}
81
+ PATTERN
82
+ # @!method to_f_method?(node)
83
+ def_node_matcher :to_f_method?, <<~PATTERN
84
+ (send !nil? :to_f)
81
85
  PATTERN
82
86
 
83
87
  def on_send(node)
@@ -10,8 +10,10 @@ module RuboCop
10
10
  # (`Hash#except` was added in Ruby 3.0.)
11
11
  #
12
12
  # For safe detection, it is limited to commonly used string and symbol comparisons
13
- # when used `==`.
14
- # And do not check `Hash#delete_if` and `Hash#keep_if` to change receiver object.
13
+ # when using `==` or `!=`.
14
+ #
15
+ # This cop doesn't check for `Hash#delete_if` and `Hash#keep_if` because they
16
+ # modify the receiver.
15
17
  #
16
18
  # @safety
17
19
  # This cop is unsafe because it cannot be guaranteed that the receiver
@@ -51,44 +53,31 @@ module RuboCop
51
53
  MSG = 'Use `%<prefer>s` instead.'
52
54
  RESTRICT_ON_SEND = %i[reject select filter].freeze
53
55
 
54
- # @!method bad_method_with_poro?(node)
55
- def_node_matcher :bad_method_with_poro?, <<~PATTERN
56
- (block
57
- (call _ _)
58
- (args
59
- $(arg _)
60
- (arg _))
61
- {
62
- $(send
63
- _ {:== :!= :eql? :include?} _)
64
- (send
65
- $(send
66
- _ {:== :!= :eql? :include?} _) :!)
67
- })
68
- PATTERN
56
+ SUBSET_METHODS = %i[== != eql? include?].freeze
57
+ ACTIVE_SUPPORT_SUBSET_METHODS = (SUBSET_METHODS + %i[in? exclude?]).freeze
69
58
 
70
- # @!method bad_method_with_active_support?(node)
71
- def_node_matcher :bad_method_with_active_support?, <<~PATTERN
59
+ # @!method block_with_first_arg_check?(node)
60
+ def_node_matcher :block_with_first_arg_check?, <<~PATTERN
72
61
  (block
73
- (send _ _)
62
+ (call _ _)
74
63
  (args
75
- $(arg _)
64
+ $(arg _key)
76
65
  (arg _))
77
66
  {
78
67
  $(send
79
- _ {:== :!= :eql? :in? :include? :exclude?} _)
68
+ {(lvar _key) $_ _ | _ $_ (lvar _key)})
80
69
  (send
81
70
  $(send
82
- _ {:== :!= :eql? :in? :include? :exclude?} _) :!)
71
+ {(lvar _key) $_ _ | _ $_ (lvar _key)}) :!)
83
72
  })
84
73
  PATTERN
85
74
 
86
75
  def on_send(node)
87
76
  block = node.parent
88
- return unless bad_method?(block) && semantically_except_method?(node, block)
77
+ return unless extracts_hash_subset?(block) && semantically_except_method?(node, block)
89
78
 
90
79
  except_key = except_key(block)
91
- return if except_key.nil? || !safe_to_register_offense?(block, except_key)
80
+ return unless safe_to_register_offense?(block, except_key)
92
81
 
93
82
  range = offense_range(node)
94
83
  preferred_method = "except(#{except_key_source(except_key)})"
@@ -101,42 +90,40 @@ module RuboCop
101
90
 
102
91
  private
103
92
 
104
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
105
- def bad_method?(block)
106
- if active_support_extensions_enabled?
107
- bad_method_with_active_support?(block) do |key_arg, send_node|
108
- if send_node.method?(:in?) && send_node.receiver&.source != key_arg.source
109
- return false
110
- end
111
- return true if !send_node.method?(:include?) && !send_node.method?(:exclude?)
112
-
113
- send_node.first_argument&.source == key_arg.source
114
- end
115
- else
116
- bad_method_with_poro?(block) do |key_arg, send_node|
117
- !send_node.method?(:include?) || send_node.first_argument&.source == key_arg.source
93
+ def extracts_hash_subset?(block)
94
+ block_with_first_arg_check?(block) do |key_arg, send_node, method|
95
+ return false unless supported_subset_method?(method)
96
+
97
+ case method
98
+ when :include?, :exclude?
99
+ send_node.first_argument.source == key_arg.source
100
+ when :in?
101
+ send_node.receiver.source == key_arg.source
102
+ else
103
+ true
118
104
  end
119
105
  end
120
106
  end
121
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
122
107
 
123
- def semantically_except_method?(send, block)
124
- body = block.body
108
+ def supported_subset_method?(method)
109
+ if active_support_extensions_enabled?
110
+ ACTIVE_SUPPORT_SUBSET_METHODS.include?(method)
111
+ else
112
+ SUBSET_METHODS.include?(method)
113
+ end
114
+ end
125
115
 
126
- negated = body.method?('!')
127
- body = body.receiver if negated
116
+ def semantically_except_method?(node, block)
117
+ body, negated = extract_body_if_negated(block.body)
128
118
 
129
- case send.method_name
130
- when :reject
131
- body.method?('==') || body.method?('eql?') || included?(negated, body)
132
- when :select, :filter
133
- body.method?('!=') || not_included?(negated, body)
119
+ if node.method?('reject')
120
+ body.method?('==') || body.method?('eql?') || included?(body, negated)
134
121
  else
135
- false
122
+ body.method?('!=') || not_included?(body, negated)
136
123
  end
137
124
  end
138
125
 
139
- def included?(negated, body)
126
+ def included?(body, negated)
140
127
  if negated
141
128
  body.method?('exclude?')
142
129
  else
@@ -144,25 +131,26 @@ module RuboCop
144
131
  end
145
132
  end
146
133
 
147
- def not_included?(negated, body)
148
- included?(!negated, body)
134
+ def not_included?(body, negated)
135
+ included?(body, !negated)
149
136
  end
150
137
 
151
138
  def safe_to_register_offense?(block, except_key)
152
- extracted = extract_body_if_negated(block.body)
153
- if extracted.method?('in?') || extracted.method?('include?') ||
154
- extracted.method?('exclude?')
155
- return true
156
- end
157
- return true if block.body.method?('eql?')
139
+ body = block.body
158
140
 
159
- except_key.sym_type? || except_key.str_type?
141
+ if body.method?('==') || body.method?('!=')
142
+ except_key.sym_type? || except_key.str_type?
143
+ else
144
+ true
145
+ end
160
146
  end
161
147
 
162
148
  def extract_body_if_negated(body)
163
- return body unless body.method?('!')
164
-
165
- body.receiver
149
+ if body.method?('!')
150
+ [body.receiver, true]
151
+ else
152
+ [body, false]
153
+ end
166
154
  end
167
155
 
168
156
  def except_key_source(key)
@@ -187,12 +175,11 @@ module RuboCop
187
175
  end
188
176
 
189
177
  def except_key(node)
190
- key_argument = node.argument_list.first.source
191
- body = extract_body_if_negated(node.body)
178
+ key_arg = node.argument_list.first.source
179
+ body, = extract_body_if_negated(node.body)
192
180
  lhs, _method_name, rhs = *body
193
- return if [lhs, rhs].map(&:source).none?(key_argument)
194
181
 
195
- [lhs, rhs].find { |operand| operand.source != key_argument }
182
+ lhs.source == key_arg ? rhs : lhs
196
183
  end
197
184
 
198
185
  def offense_range(node)
@@ -137,6 +137,7 @@ module RuboCop
137
137
  MSG_19 = 'Use the new Ruby 1.9 hash syntax.'
138
138
  MSG_NO_MIXED_KEYS = "Don't mix styles in the same hash."
139
139
  MSG_HASH_ROCKETS = 'Use hash rockets syntax.'
140
+ NO_MIXED_KEYS_STYLES = %i[ruby19_no_mixed_keys no_mixed_keys].freeze
140
141
 
141
142
  def on_hash(node)
142
143
  pairs = node.pairs
@@ -196,7 +197,7 @@ module RuboCop
196
197
  def autocorrect(corrector, node)
197
198
  if style == :hash_rockets || force_hash_rockets?(node.parent.pairs)
198
199
  autocorrect_hash_rockets(corrector, node)
199
- elsif style == :ruby19_no_mixed_keys || style == :no_mixed_keys
200
+ elsif NO_MIXED_KEYS_STYLES.include?(style)
200
201
  autocorrect_no_mixed_keys(corrector, node)
201
202
  else
202
203
  autocorrect_ruby19(corrector, node)
@@ -272,7 +273,9 @@ module RuboCop
272
273
  end
273
274
 
274
275
  def argument_without_space?(node)
275
- node.argument? && node.source_range.begin_pos == node.parent.loc.selector.end_pos
276
+ return false if !node.argument? || !node.parent.loc.selector
277
+
278
+ node.source_range.begin_pos == node.parent.loc.selector.end_pos
276
279
  end
277
280
 
278
281
  def autocorrect_hash_rockets(corrector, pair_node)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for assignments to a local `it` variable inside a block
7
+ # where `it` can refer to the first anonymous parameter as of Ruby 3.4.
8
+ #
9
+ # Although Ruby allows reassigning `it` in these cases, it could
10
+ # cause confusion if `it` is used as a block parameter elsewhere.
11
+ # For consistency, this also applies to numblocks and blocks with
12
+ # parameters, even though `it` cannot be used in those cases.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # foo { it = 5 }
17
+ # foo { |bar| it = bar }
18
+ # foo { it = _2 }
19
+ #
20
+ # # good - use a different variable name
21
+ # foo { var = 5 }
22
+ # foo { |bar| var = bar }
23
+ # foo { bar = _2 }
24
+ class ItAssignment < Base
25
+ MSG = '`it` is the default block parameter; consider another name.'
26
+
27
+ def on_lvasgn(node)
28
+ return unless node.name == :it
29
+ return unless node.each_ancestor(:block, :numblock).any?
30
+
31
+ add_offense(node.loc.name)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -135,17 +135,20 @@ module RuboCop
135
135
  node.parent&.class_type? && node.parent.single_line?
136
136
  end
137
137
 
138
- def call_with_ambiguous_arguments?(node) # rubocop:disable Metrics/PerceivedComplexity
138
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
139
+ def call_with_ambiguous_arguments?(node)
139
140
  call_with_braced_block?(node) ||
140
141
  call_in_argument_with_block?(node) ||
141
142
  call_as_argument_or_chain?(node) ||
142
143
  call_in_match_pattern?(node) ||
143
144
  hash_literal_in_arguments?(node) ||
145
+ ambiguous_range_argument?(node) ||
144
146
  node.descendants.any? do |n|
145
147
  n.forwarded_args_type? || n.block_type? || n.numblock_type? ||
146
148
  ambiguous_literal?(n) || logical_operator?(n)
147
149
  end
148
150
  end
151
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
149
152
 
150
153
  def call_with_braced_block?(node)
151
154
  (node.call_type? || node.super_type?) && node.block_node&.braces?
@@ -177,6 +180,13 @@ module RuboCop
177
180
  end
178
181
  end
179
182
 
183
+ def ambiguous_range_argument?(node)
184
+ return true if (first_arg = node.first_argument)&.range_type? && first_arg.begin.nil?
185
+ return true if (last_arg = node.last_argument)&.range_type? && last_arg.end.nil?
186
+
187
+ false
188
+ end
189
+
180
190
  def allowed_multiline_call_with_parentheses?(node)
181
191
  cop_config['AllowParenthesesInMultilineCall'] && node.multiline?
182
192
  end