rubocop 1.79.2 → 1.80.2

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/exe/rubocop +1 -8
  4. data/lib/rubocop/cli.rb +5 -0
  5. data/lib/rubocop/cop/correctors/alignment_corrector.rb +6 -3
  6. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  7. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  8. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -0
  9. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  10. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -2
  11. data/lib/rubocop/cop/lint/self_assignment.rb +5 -4
  12. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  13. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  14. data/lib/rubocop/cop/naming/method_name.rb +1 -1
  15. data/lib/rubocop/cop/naming/predicate_method.rb +3 -2
  16. data/lib/rubocop/cop/style/array_intersect.rb +45 -11
  17. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  18. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  19. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  20. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  21. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -11
  22. data/lib/rubocop/cop/style/safe_navigation.rb +18 -1
  23. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  24. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  25. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  26. data/lib/rubocop/cop/variable_force.rb +9 -7
  27. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  28. data/lib/rubocop/lsp/routes.rb +31 -2
  29. data/lib/rubocop/result_cache.rb +1 -1
  30. data/lib/rubocop/runner.rb +6 -4
  31. data/lib/rubocop/target_finder.rb +9 -9
  32. data/lib/rubocop/version.rb +1 -1
  33. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f846b1bf660549b8197375e32c8219bff1e42888b925976bd3514cd9b32c1d3b
4
- data.tar.gz: 4e862ade4bbe0c24c7734427695733316c893b5278f9868aa88c44255d03461c
3
+ metadata.gz: 427aedeb12fa3d9d84e7ccd8ea4c125b418a3170c0f91d02e66ef8717006fc0a
4
+ data.tar.gz: f5a5b09520f27ed77d9cc5e2077ce9a55fa77db97bdd1d593e399551abd45bb8
5
5
  SHA512:
6
- metadata.gz: 05ff2343d4ddcdd26cf94325a5b9026fb7468f456b5714da5108030c39fb0fcfb7218848596d07b04817052af53bc225e66b1f81c53f18adb780c2b4bd417012
7
- data.tar.gz: 9c2ce30fbd9467d85bed0aa581beaa225f3e5b032f0e7a5b09130c8eb2918b25e452f3704a9774e8199c1b9e1ccb1b7d10e558a710c7a73d9f9bd86940475ebf
6
+ metadata.gz: ddcf6fc4b8e900f900f5734c647b3d681d731d6d4bf37729c4960a751ace231d8835ca9800d4e499d1908f7e0474445bbf2490f5c8b9408d1301d6bff1196794
7
+ data.tar.gz: 94f856e78e6af3c0c78fd926268ae8349af663c2e7a1bc40a11842bb04db7a6baa1f239cb2c537ef8b442a4a76dfc09a8a7bfe10244abb2a78ed3b3b6600d38b
data/README.md CHANGED
@@ -51,7 +51,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
51
51
  in your `Gemfile`:
52
52
 
53
53
  ```rb
54
- gem 'rubocop', '~> 1.79', require: false
54
+ gem 'rubocop', '~> 1.80', require: false
55
55
  ```
56
56
 
57
57
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/exe/rubocop CHANGED
@@ -12,13 +12,6 @@ if RuboCop::Server.running?
12
12
  exit_status = RuboCop::Server::ClientCommand::Exec.new.run
13
13
  else
14
14
  require 'rubocop'
15
-
16
- cli = RuboCop::CLI.new
17
-
18
- time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
- exit_status = cli.run
20
- elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start
21
-
22
- puts "Finished in #{elapsed_time} seconds" if cli.options[:debug] || cli.options[:display_time]
15
+ exit_status = RuboCop::CLI.new.run
23
16
  end
24
17
  exit exit_status
data/lib/rubocop/cli.rb CHANGED
@@ -37,6 +37,8 @@ module RuboCop
37
37
  #
38
38
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
39
39
  def run(args = ARGV)
40
+ time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
41
+
40
42
  @options, paths = Options.new.parse(args)
41
43
  @env = Environment.new(@options, @config_store, paths)
42
44
 
@@ -72,6 +74,9 @@ module RuboCop
72
74
  warn e.message
73
75
  warn e.backtrace
74
76
  STATUS_ERROR
77
+ ensure
78
+ elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start
79
+ puts "Finished in #{elapsed_time} seconds" if @options[:debug] || @options[:display_time]
75
80
  end
76
81
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
77
82
 
@@ -29,10 +29,13 @@ module RuboCop
29
29
  def align_end(corrector, processed_source, node, align_to)
30
30
  @processed_source = processed_source
31
31
  whitespace = whitespace_range(node)
32
- return false unless whitespace.source.strip.empty?
33
-
34
32
  column = alignment_column(align_to)
35
- corrector.replace(whitespace, ' ' * column)
33
+
34
+ if whitespace.source.strip.empty?
35
+ corrector.replace(whitespace, ' ' * column)
36
+ else
37
+ corrector.insert_after(whitespace, "\n#{' ' * column}")
38
+ end
36
39
  end
37
40
 
38
41
  private
@@ -6,7 +6,7 @@ module RuboCop
6
6
  class ForToEachCorrector
7
7
  extend NodePattern::Macros
8
8
 
9
- CORRECTION = '%<collection>s.each do |%<argument>s|'
9
+ CORRECTION = '%<collection>s%<dot>seach do |%<argument>s|'
10
10
 
11
11
  def initialize(for_node)
12
12
  @for_node = for_node
@@ -25,7 +25,12 @@ module RuboCop
25
25
  attr_reader :for_node, :variable_node, :collection_node
26
26
 
27
27
  def correction
28
- format(CORRECTION, collection: collection_source, argument: variable_node.source)
28
+ format(
29
+ CORRECTION,
30
+ collection: collection_source,
31
+ dot: collection_node.csend_type? ? '&.' : '.',
32
+ argument: variable_node.source
33
+ )
29
34
  end
30
35
 
31
36
  def collection_source
@@ -38,7 +38,7 @@ module RuboCop
38
38
  RESTRICT_ON_SEND = MODULE_INCLUSION_METHODS
39
39
 
40
40
  def on_send(node)
41
- return if node.receiver
41
+ return if node.receiver || node.arguments.empty?
42
42
  return if node.parent&.type?(:send, :any_block)
43
43
 
44
44
  return if next_line_empty_or_enable_directive_comment?(node.last_line)
@@ -10,6 +10,8 @@ module RuboCop
10
10
  # condition, an explicit `return` statement, etc. In other contexts, the second operand should
11
11
  # be indented regardless of enforced style.
12
12
  #
13
+ # In both styles, operators should be aligned when an assignment begins on the next line.
14
+ #
13
15
  # @example EnforcedStyle: aligned (default)
14
16
  # # bad
15
17
  # if a +
@@ -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
 
@@ -52,10 +52,9 @@ module RuboCop
52
52
  each_missing_enable do |cop, line_range|
53
53
  next if acceptable_range?(cop, line_range)
54
54
 
55
- range = source_range(processed_source.buffer, line_range.min, 0..0)
56
55
  comment = processed_source.comment_at_line(line_range.begin)
57
56
 
58
- add_offense(range, message: message(cop, comment))
57
+ add_offense(comment, message: message(cop, comment))
59
58
  end
60
59
  end
61
60
 
@@ -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
111
  if value_node.send_type? && 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
@@ -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
@@ -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
@@ -198,7 +198,7 @@ module RuboCop
198
198
 
199
199
  if forbidden_name?(name.to_s)
200
200
  register_forbidden_name(node)
201
- elsif !OPERATOR_METHODS.include?(name)
201
+ elsif !OPERATOR_METHODS.include?(name.to_sym)
202
202
  check_name(node, name, range_position(node))
203
203
  end
204
204
  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`
@@ -149,7 +149,8 @@ module RuboCop
149
149
  private
150
150
 
151
151
  def allowed?(node)
152
- allowed_method?(node.method_name) ||
152
+ node.method?(:initialize) ||
153
+ allowed_method?(node.method_name) ||
153
154
  matches_allowed_pattern?(node.method_name) ||
154
155
  allowed_bang_method?(node) ||
155
156
  node.operator_method? ||
@@ -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,9 +88,11 @@ 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
98
  (call $_ :intersection $_)
@@ -84,6 +101,20 @@ module RuboCop
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 $_ :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)
@@ -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
 
@@ -68,7 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  def autocorrect(corrector, node)
71
- if node.type?(:while_post, :until_post)
71
+ if node.post_condition_loop?
72
72
  replace_begin_end_with_modifier(corrector, node)
73
73
  elsif node.modifier_form?
74
74
  replace_source(corrector, node.source_range, modifier_replacement(node))
@@ -85,6 +85,29 @@ module RuboCop
85
85
  end
86
86
  alias on_defs on_def
87
87
 
88
+ def on_if(node)
89
+ return if node.modifier_form?
90
+
91
+ inspect_branches(node)
92
+ end
93
+
94
+ def on_case(node)
95
+ inspect_branches(node)
96
+ end
97
+ alias on_case_match on_case
98
+
99
+ def on_while(node)
100
+ return if node.modifier_form?
101
+
102
+ body = node.body
103
+
104
+ return unless body&.kwbegin_type?
105
+ return if body.rescue_node || body.ensure_node
106
+
107
+ register_offense(body)
108
+ end
109
+ alias on_until on_while
110
+
88
111
  def on_block(node)
89
112
  return if target_ruby_version < 2.5
90
113
  return if node.send_node.lambda_literal?
@@ -180,6 +203,8 @@ module RuboCop
180
203
  end
181
204
 
182
205
  def begin_block_has_multiline_statements?(node)
206
+ return false unless node.parent
207
+
183
208
  node.children.count >= 2
184
209
  end
185
210
 
@@ -199,6 +224,15 @@ module RuboCop
199
224
  def valid_begin_assignment?(node)
200
225
  node.parent&.assignment? && !node.children.one?
201
226
  end
227
+
228
+ def inspect_branches(node)
229
+ node.branches.each do |branch|
230
+ next unless branch&.kwbegin_type?
231
+ next if branch.rescue_node || branch.ensure_node
232
+
233
+ register_offense(branch)
234
+ end
235
+ end
202
236
  end
203
237
  end
204
238
  end
@@ -247,7 +247,7 @@ module RuboCop
247
247
  "#{if_branch.receiver.source} #{if_branch.method_name} (#{argument_source}"
248
248
  elsif if_branch.true_type?
249
249
  condition = if_branch.parent.condition
250
- return condition.source if condition.arguments.empty?
250
+ return condition.source if condition.arguments.empty? || condition.parenthesized?
251
251
 
252
252
  wrap_arguments_with_parens(condition)
253
253
  else
@@ -24,9 +24,6 @@ module RuboCop
24
24
  (send `{(send _recv _msg) str array hash const #variable?} :[] ...)
25
25
  PATTERN
26
26
 
27
- # @!method method_node_and_args(node)
28
- def_node_matcher :method_node_and_args, '$(call _recv _msg $...)'
29
-
30
27
  # @!method rescue?(node)
31
28
  def_node_matcher :rescue?, '{^resbody ^^resbody}'
32
29
 
@@ -220,7 +217,7 @@ module RuboCop
220
217
  end
221
218
 
222
219
  def call_node?(node)
223
- node.call_type? || (node.any_block_type? && !node.lambda_or_proc?)
220
+ node.call_type? || (node.any_block_type? && node.braces? && !node.lambda_or_proc?)
224
221
  end
225
222
 
226
223
  def check_send(begin_node, node)
@@ -228,7 +225,7 @@ module RuboCop
228
225
 
229
226
  return check_unary(begin_node, node) if node.unary_operation?
230
227
 
231
- return unless method_call_with_redundant_parentheses?(node)
228
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
232
229
  return if call_chain_starts_with_int?(begin_node, node) ||
233
230
  do_end_block_in_method_chain?(begin_node, node)
234
231
 
@@ -239,8 +236,7 @@ module RuboCop
239
236
  return if begin_node.chained?
240
237
 
241
238
  node = node.children.first while suspect_unary?(node)
242
-
243
- return if node.send_type? && !method_call_with_redundant_parentheses?(node)
239
+ return unless method_call_with_redundant_parentheses?(begin_node, node)
244
240
 
245
241
  offense(begin_node, 'a unary operation')
246
242
  end
@@ -302,13 +298,19 @@ module RuboCop
302
298
  end
303
299
  end
304
300
 
305
- def method_call_with_redundant_parentheses?(node)
306
- return false unless node.call_type?
301
+ def method_call_with_redundant_parentheses?(begin_node, node)
302
+ return false unless node.type?(:call, :super, :yield, :defined?)
307
303
  return false if node.prefix_not?
304
+ return true if singular_parenthesized_parent?(begin_node)
305
+
306
+ node.arguments.empty? || parentheses?(node) || square_brackets?(node)
307
+ end
308
308
 
309
- send_node, args = method_node_and_args(node)
309
+ def singular_parenthesized_parent?(begin_node)
310
+ return true unless begin_node.parent
311
+ return false if begin_node.parent.type?(:splat, :kwsplat)
310
312
 
311
- args.empty? || parentheses?(send_node) || square_brackets?(send_node)
313
+ parentheses?(begin_node) && begin_node.parent.children.one?
312
314
  end
313
315
 
314
316
  def only_begin_arg?(args)
@@ -142,6 +142,7 @@ module RuboCop
142
142
  # @!method strip_begin(node)
143
143
  def_node_matcher :strip_begin, '{ (begin $!begin) $!(begin) }'
144
144
 
145
+ # rubocop:disable Metrics/AbcSize
145
146
  def on_if(node)
146
147
  return if allowed_if_condition?(node)
147
148
 
@@ -155,9 +156,11 @@ module RuboCop
155
156
  removal_ranges = [begin_range(node, body), end_range(node, body)]
156
157
 
157
158
  report_offense(node, method_chain, method_call, *removal_ranges) do |corrector|
159
+ corrector.replace(receiver, checked_variable.source) if checked_variable.csend_type?
158
160
  corrector.insert_before(method_call.loc.dot, '&') unless method_call.safe_navigation?
159
161
  end
160
162
  end
163
+ # rubocop:enable Metrics/AbcSize
161
164
 
162
165
  def on_and(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
163
166
  collect_and_clauses(node).each do |(lhs, lhs_operator_range), (rhs, _rhs_operator_range)|
@@ -259,8 +262,14 @@ module RuboCop
259
262
  end
260
263
 
261
264
  def dotless_operator_call?(method_call)
265
+ return true if dotless_operator_method?(method_call)
266
+
262
267
  method_call = method_call.parent while method_call.parent.send_type?
263
268
 
269
+ dotless_operator_method?(method_call)
270
+ end
271
+
272
+ def dotless_operator_method?(method_call)
264
273
  return false if method_call.loc.dot
265
274
 
266
275
  method_call.method?(:[]) || method_call.method?(:[]=) || method_call.operator_method?
@@ -335,8 +344,16 @@ module RuboCop
335
344
 
336
345
  def matching_call_nodes?(left, right)
337
346
  return false unless left && right.respond_to?(:call_type?)
347
+ return false unless left.call_type? && right.call_type?
348
+
349
+ # Compare receiver and method name, but ignore the difference between
350
+ # safe navigation method call (`&.`) and dot method call (`.`).
351
+ left_receiver, left_method, *left_args = left.children
352
+ right_receiver, right_method, *right_args = right.children
338
353
 
339
- left.call_type? && right.call_type? && left.children == right.children
354
+ left_method == right_method &&
355
+ matching_nodes?(left_receiver, right_receiver) &&
356
+ left_args == right_args
340
357
  end
341
358
 
342
359
  def chain_length(method_chain, method)
@@ -100,7 +100,7 @@ module RuboCop
100
100
  node.receiver.str_type? &&
101
101
  node.first_argument.str_type? &&
102
102
  node.multiline? &&
103
- node.source =~ /\+\s*\n/
103
+ node.source.match?(/\+\s*\n/)
104
104
  end
105
105
 
106
106
  def find_topmost_plus_node(node)
@@ -141,22 +141,26 @@ module RuboCop
141
141
  end
142
142
 
143
143
  def replacement(parts)
144
- interpolated_parts = parts.map do |part|
145
- case part.type
146
- when :str
147
- adjust_str(part)
148
- when :dstr
149
- part.children.all?(&:str_type?) ? adjust_str(part) : part.value
150
- else
151
- "\#{#{part.source}}"
152
- end
153
- end
144
+ interpolated_parts = parts.map { |part| adjust_str(part) }
154
145
 
155
146
  "\"#{handle_quotes(interpolated_parts).join}\""
156
147
  end
157
148
 
158
- def adjust_str(node)
159
- single_quoted?(node) ? node.value.gsub(/(\\|")/, '\\\\\&') : node.value.inspect[1..-2]
149
+ def adjust_str(part)
150
+ case part.type
151
+ when :str
152
+ if single_quoted?(part)
153
+ part.value.gsub(/(\\|"|#\{|#@|#\$)/, '\\\\\&')
154
+ else
155
+ part.value.inspect[1..-2]
156
+ end
157
+ when :dstr, :begin
158
+ part.children.map do |child|
159
+ adjust_str(child)
160
+ end.join
161
+ else
162
+ "\#{#{part.source}}"
163
+ end
160
164
  end
161
165
 
162
166
  def handle_quotes(parts)
@@ -81,7 +81,7 @@ module RuboCop
81
81
 
82
82
  content = *sym
83
83
  content = content.map { |c| c.is_a?(AST::Node) ? c.source : c }.join
84
- content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s\(\)]*\))/, '')
84
+ content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s()]*\))/, '')
85
85
 
86
86
  content.include?(' ') || DELIMITERS.any? do |delimiter|
87
87
  content_without_delimiter_pairs.include?(delimiter)
@@ -79,7 +79,7 @@ module RuboCop
79
79
  parent = parent.parent if parent&.begin_type?
80
80
  return false if parent.nil?
81
81
 
82
- parent.type?(:if, :while, :until) && parent.modifier_form?
82
+ parent.basic_conditional? && parent.modifier_form?
83
83
  end
84
84
 
85
85
  def capture_with_block!
@@ -238,11 +238,16 @@ module RuboCop
238
238
  end
239
239
 
240
240
  def process_loop(node)
241
- if POST_CONDITION_LOOP_TYPES.include?(node.type)
241
+ if node.post_condition_loop?
242
242
  # See the comment at the end of file for this behavior.
243
243
  condition_node, body_node = *node
244
244
  process_node(body_node)
245
245
  process_node(condition_node)
246
+ elsif node.for_type?
247
+ # In `for item in items` the rightmost expression is evaluated first.
248
+ process_node(node.collection)
249
+ process_node(node.variable)
250
+ process_node(node.body) if node.body
246
251
  else
247
252
  process_children(node)
248
253
  end
@@ -356,17 +361,14 @@ module RuboCop
356
361
  end
357
362
 
358
363
  def reference_assignments(loop_assignments, loop_node)
359
- node = loop_assignments.first.node
360
-
361
364
  # If inside a branching statement, mark all as referenced.
362
365
  # Otherwise, mark only the last assignment as referenced.
363
366
  # Note that `rescue` must be considered as branching because of
364
367
  # the `retry` keyword.
365
- if node.each_ancestor(*BRANCH_NODES).any? || node.parent.each_descendant(*BRANCH_NODES).any?
366
- loop_assignments.each { |assignment| assignment.reference!(loop_node) }
367
- else
368
- loop_assignments.last&.reference!(loop_node)
368
+ loop_assignments.each do |assignment|
369
+ assignment.reference!(loop_node) if assignment.node.each_ancestor(*BRANCH_NODES).any?
369
370
  end
371
+ loop_assignments.last&.reference!(loop_node)
370
372
  end
371
373
 
372
374
  def scanned_node?(node)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Formatter
5
5
  # This formatter displays a YAML configuration file where all cops that
6
6
  # detected any offenses are configured to not detect the offense.
7
- class DisabledConfigFormatter < BaseFormatter
7
+ class DisabledConfigFormatter < BaseFormatter # rubocop:disable Metrics/ClassLength
8
8
  include PathUtil
9
9
 
10
10
  HEADING = <<~COMMENTS
@@ -17,6 +17,22 @@ module RuboCop
17
17
  # versions of RuboCop, may require this file to be generated again.
18
18
  COMMENTS
19
19
 
20
+ EXCLUDED_CONFIG_KEYS = %w[
21
+ AutoCorrect
22
+ Description
23
+ Enabled
24
+ Exclude
25
+ Include
26
+ Reference
27
+ References
28
+ Safe
29
+ SafeAutoCorrect
30
+ StyleGuide
31
+ VersionAdded
32
+ VersionChanged
33
+ VersionRemoved
34
+ ].freeze
35
+
20
36
  @config_to_allow_offenses = {}
21
37
  @detected_styles = {}
22
38
 
@@ -163,10 +179,7 @@ module RuboCop
163
179
  end
164
180
 
165
181
  def cop_config_params(default_cfg, cfg)
166
- default_cfg.keys -
167
- %w[Description StyleGuide Reference References Enabled Exclude Safe
168
- SafeAutoCorrect VersionAdded VersionChanged VersionRemoved] -
169
- cfg.keys
182
+ default_cfg.keys - EXCLUDED_CONFIG_KEYS - cfg.keys
170
183
  end
171
184
 
172
185
  def output_cop_param_comments(output_buffer, params, default_cfg)
@@ -51,7 +51,7 @@ module RuboCop
51
51
  capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
52
52
  document_formatting_provider: true,
53
53
  text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
54
- change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL,
54
+ change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::INCREMENTAL,
55
55
  open_close: true
56
56
  )
57
57
  )
@@ -76,7 +76,12 @@ module RuboCop
76
76
 
77
77
  handle 'textDocument/didChange' do |request|
78
78
  params = request[:params]
79
- result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
79
+ file_uri = params[:textDocument][:uri]
80
+ text = @text_cache[file_uri]
81
+ params[:contentChanges].each do |content|
82
+ text = change_text(text, content[:text], content[:range])
83
+ end
84
+ result = diagnostic(file_uri, text)
80
85
  @server.write(result)
81
86
  end
82
87
 
@@ -219,6 +224,30 @@ module RuboCop
219
224
  }
220
225
  end
221
226
 
227
+ def change_text(orig_text, text, range)
228
+ return text unless range
229
+
230
+ start_pos = text_pos(orig_text, range[:start])
231
+ end_pos = text_pos(orig_text, range[:end])
232
+ text_bin = orig_text.b
233
+ text_bin[start_pos...end_pos] = text.b
234
+ text_bin.force_encoding(orig_text.encoding)
235
+ end
236
+
237
+ def text_pos(text, range)
238
+ line = range[:line]
239
+ char = range[:character]
240
+ pos = 0
241
+ text.each_line.with_index do |l, i|
242
+ if i == line
243
+ pos += l.encode('utf-16be').b[0, char * 2].encode('utf-8', 'utf-16be').bytesize
244
+ return pos
245
+ end
246
+ pos += l.bytesize
247
+ end
248
+ pos
249
+ end
250
+
222
251
  def convert_file_uri_to_path(uri)
223
252
  URI.decode_www_form_component(uri.delete_prefix('file://'))
224
253
  end
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # Provides functionality for caching RuboCop runs.
10
10
  # @api private
11
11
  class ResultCache
12
- NON_CHANGING = %i[color format formatters out debug fail_level
12
+ NON_CHANGING = %i[color format formatters out debug display_time fail_level
13
13
  fix_layout autocorrect safe_autocorrect autocorrect_all
14
14
  cache fail_fast stdin parallel].freeze
15
15
 
@@ -273,7 +273,8 @@ module RuboCop
273
273
  end
274
274
 
275
275
  def do_inspection_loop(file)
276
- processed_source = get_processed_source(file)
276
+ # We can reuse the prism result since the source did not change yet.
277
+ processed_source = get_processed_source(file, @prism_result)
277
278
  # This variable is 2d array used to track corrected offenses after each
278
279
  # inspection iteration. This is used to output meaningful infinite loop
279
280
  # error message.
@@ -295,7 +296,8 @@ module RuboCop
295
296
  # loop if we find any.
296
297
  break unless updated_source_file
297
298
 
298
- processed_source = get_processed_source(file)
299
+ # Autocorrect has happened, don't use the prism result since it is stale.
300
+ processed_source = get_processed_source(file, nil)
299
301
  end
300
302
 
301
303
  # Return summary of corrected offenses after all iterations
@@ -482,7 +484,7 @@ module RuboCop
482
484
  end
483
485
 
484
486
  # rubocop:disable Metrics/MethodLength
485
- def get_processed_source(file)
487
+ def get_processed_source(file, prism_result)
486
488
  config = @config_store.for_file(file)
487
489
  ruby_version = config.target_ruby_version
488
490
  parser_engine = config.parser_engine
@@ -493,7 +495,7 @@ module RuboCop
493
495
  ruby_version,
494
496
  file,
495
497
  parser_engine: parser_engine,
496
- prism_result: @prism_result
498
+ prism_result: prism_result
497
499
  )
498
500
  else
499
501
  begin
@@ -42,14 +42,12 @@ module RuboCop
42
42
  # Support Windows: Backslashes from command-line -> forward slashes
43
43
  base_dir = base_dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
44
44
  all_files = find_files(base_dir, File::FNM_DOTMATCH)
45
- # use file.include? for performance optimization
46
- hidden_files = all_files.select { |file| file.include?(HIDDEN_PATH_SUBSTRING) }.sort
47
45
  base_dir_config = @config_store.for(base_dir)
48
46
 
49
- target_files = if base_dir.include?(HIDDEN_PATH_SUBSTRING)
47
+ target_files = if hidden_path?(base_dir)
50
48
  all_files.select { |file| ruby_file?(file) }
51
49
  else
52
- all_files.select { |file| to_inspect?(file, hidden_files, base_dir_config) }
50
+ all_files.select { |file| to_inspect?(file, base_dir_config) }
53
51
  end
54
52
 
55
53
  target_files.sort_by!(&order)
@@ -74,18 +72,20 @@ module RuboCop
74
72
 
75
73
  private
76
74
 
77
- def to_inspect?(file, hidden_files, base_dir_config)
75
+ def to_inspect?(file, base_dir_config)
78
76
  return false if base_dir_config.file_to_exclude?(file)
79
- return true if !hidden_files.bsearch do |hidden_file|
80
- file <=> hidden_file
81
- end && ruby_file?(file)
77
+ return true if !hidden_path?(file) && ruby_file?(file)
82
78
 
83
79
  base_dir_config.file_to_include?(file)
84
80
  end
85
81
 
82
+ def hidden_path?(path)
83
+ path.include?(HIDDEN_PATH_SUBSTRING)
84
+ end
85
+
86
86
  def wanted_dir_patterns(base_dir, exclude_pattern, flags)
87
87
  # Escape glob characters in base_dir to avoid unwanted behavior.
88
- base_dir = base_dir.gsub(/[\\\{\}\[\]\*\?]/) do |reserved_glob_character|
88
+ base_dir = base_dir.gsub(/[\\{}\[\]*?]/) do |reserved_glob_character|
89
89
  "\\#{reserved_glob_character}"
90
90
  end
91
91
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.79.2'
6
+ STRING = '1.80.2'
7
7
 
8
8
  MSG = '%<version>s (using %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.79.2
4
+ version: 1.80.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -9,7 +9,7 @@ authors:
9
9
  - Yuji Nakayama
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2025-08-05 00:00:00.000000000 Z
12
+ date: 2025-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -1090,9 +1090,9 @@ licenses:
1090
1090
  - MIT
1091
1091
  metadata:
1092
1092
  homepage_uri: https://rubocop.org/
1093
- changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.79.2
1093
+ changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.80.2
1094
1094
  source_code_uri: https://github.com/rubocop/rubocop/
1095
- documentation_uri: https://docs.rubocop.org/rubocop/1.79/
1095
+ documentation_uri: https://docs.rubocop.org/rubocop/1.80/
1096
1096
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues
1097
1097
  rubygems_mfa_required: 'true'
1098
1098
  rdoc_options: []