rubocop 1.84.0 → 1.84.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbf40b2db6dffc0c0f6685b18afbef40441dfc3650f0176a701ca9442748d12b
4
- data.tar.gz: 4a40e7f7dbc92057286865804e49fa60b33391cd1947d87447fdaa18a1ad6dd2
3
+ metadata.gz: 358bce5232f7b6e4e490a3d173d09cb4d3df8fc9e92355b550d0047e10e0f462
4
+ data.tar.gz: 8e65d98bd3478db7eee250dd05ffc6fd4f4d17d144207950fc12f5e83f7995f6
5
5
  SHA512:
6
- metadata.gz: 0da21b5bc8b4db1ce03eefd577ee4e680354fb4b9bc8c4a1626b74dfe4d8ca4c31f4fcd629a635596db74d8d270686d3b353c3378ccd4d6d7b154129ea4af038
7
- data.tar.gz: 1b470746489d85337a2ce6f9304d69b875c079a72f383fd8bf2f5154535cac98618341022fc5a5bf1d1fdc5ebe02f6be69d82f64d97c04d250215d41bb7f598b
6
+ metadata.gz: 4b6cf48e7582909807829dd43e7fd379a9e4ec46cd387659a16321f4fd3ee7a321ac16859ce86870d41fae2602abde14197e1bb91094abb8bf9dd809dc6e648e
7
+ data.tar.gz: c619779f7ac9d8563aadc5f6675333f22d7c8d2300d6ac1c996260d442210f6b2b809f24e7fa1479b6b908a0ca16bc8f61bc23200f3e2f302e03369bce6dafac
data/config/default.yml CHANGED
@@ -3113,6 +3113,7 @@ Naming/PredicateMethod:
3113
3113
  AllowBangMethods: false
3114
3114
  # Methods that are known to not return a boolean value, despite ending in `?`.
3115
3115
  WaywardPredicates:
3116
+ - infinite?
3116
3117
  - nonzero?
3117
3118
 
3118
3119
  Naming/PredicatePrefix:
@@ -3980,7 +3981,7 @@ Style/EmptyCaseCondition:
3980
3981
  Style/EmptyClassDefinition:
3981
3982
  Description: 'Enforces consistent style for empty class definitions.'
3982
3983
  Enabled: pending
3983
- VersionAdded: '1.83'
3984
+ VersionAdded: '1.84'
3984
3985
  EnforcedStyle: class_definition
3985
3986
  SupportedStyles:
3986
3987
  - class_definition
@@ -4341,7 +4342,7 @@ Style/HashLookupMethod:
4341
4342
  Description: 'Enforces the use of either `Hash#[]` or `Hash#fetch` for hash lookup.'
4342
4343
  Enabled: false
4343
4344
  Safe: false
4344
- VersionAdded: '1.83'
4345
+ VersionAdded: '1.84'
4345
4346
  EnforcedStyle: brackets
4346
4347
  SupportedStyles:
4347
4348
  - brackets
@@ -4444,6 +4445,7 @@ Style/IfWithBooleanLiteralBranches:
4444
4445
  VersionAdded: '1.9'
4445
4446
  SafeAutoCorrect: false
4446
4447
  AllowedMethods:
4448
+ - infinite?
4447
4449
  - nonzero?
4448
4450
 
4449
4451
  Style/IfWithSemicolon:
@@ -4912,7 +4914,7 @@ Style/NegativeArrayIndex:
4912
4914
  Also handles range patterns with length calculations. Recognizes preserving methods
4913
4915
  and their combinations, allowing safe replacement when the receiver matches.
4914
4916
  Enabled: pending
4915
- VersionAdded: '1.83'
4917
+ VersionAdded: '1.84'
4916
4918
 
4917
4919
  Style/NestedFileDirname:
4918
4920
  Description: 'Checks for nested `File.dirname`.'
@@ -5317,6 +5319,7 @@ Style/RedundantCondition:
5317
5319
  VersionAdded: '0.76'
5318
5320
  VersionChanged: '1.73'
5319
5321
  AllowedMethods:
5322
+ - infinite?
5320
5323
  - nonzero?
5321
5324
 
5322
5325
  Style/RedundantConditional:
@@ -5569,7 +5572,7 @@ Style/ReverseFind:
5569
5572
  Description: 'Use `array.rfind` instead of `array.reverse.find`.'
5570
5573
  Enabled: pending
5571
5574
  Safe: false
5572
- VersionAdded: '1.83'
5575
+ VersionAdded: '1.84'
5573
5576
 
5574
5577
  Style/SafeNavigation:
5575
5578
  Description: >-
@@ -103,8 +103,9 @@ module RuboCop
103
103
 
104
104
  each_directive do |directive|
105
105
  if directive.push?
106
- @stack.push(snapshot_analyses(analyses))
107
- apply_push_args(analyses, directive)
106
+ resolved = resolve_push_cops(directive)
107
+ @stack.push(snapshot_cops(analyses, resolved.values.flatten))
108
+ apply_push(analyses, resolved, directive.line_number)
108
109
  elsif directive.pop?
109
110
  pop_state(analyses, directive.line_number) if @stack.any?
110
111
  else
@@ -121,48 +122,46 @@ module RuboCop
121
122
  end
122
123
  end
123
124
 
124
- def snapshot_analyses(analyses)
125
- analyses.transform_values { |a| CopAnalysis.new(a.line_ranges.dup, a.start_line_number) }
125
+ def snapshot_cops(analyses, cop_names)
126
+ cop_names.to_h { |name| [name, analyses[name].dup] }
126
127
  end
127
128
 
128
- def pop_state(analyses, pop_line)
129
- analyses.each do |cop_name, analysis|
130
- next unless analysis.start_line_number
131
-
132
- analyses[cop_name] = CopAnalysis.new(
133
- analysis.line_ranges + [analysis.start_line_number...pop_line], nil
134
- )
129
+ def resolve_push_cops(directive)
130
+ directive.push_args.transform_values do |names|
131
+ names.flat_map { |name| expand_cop_name(name) }
135
132
  end
133
+ end
136
134
 
137
- @stack.pop.each do |cop_name, saved_analysis|
138
- current = analyses[cop_name]
139
- new_start = saved_analysis.start_line_number ? pop_line : nil
140
- analyses[cop_name] = CopAnalysis.new(current.line_ranges, new_start)
141
- end
135
+ def expand_cop_name(name)
136
+ registry = Cop::Registry.global
137
+ cops = registry.department?(name) ? registry.names_for_department(name) : [name]
138
+ cops.map { |c| qualified_cop_name(c) }
142
139
  end
143
140
 
144
- def apply_push_args(analyses, directive)
145
- directive.push_args.each do |operation, cop_names|
146
- cop_names.each do |cop_name|
147
- apply_cop_operation(analyses, operation, qualified_cop_name(cop_name),
148
- directive.line_number)
149
- end
141
+ def apply_push(analyses, resolved_cops, line)
142
+ resolved_cops.each do |op, cops|
143
+ cops.each { |cop| apply_cop_op(analyses, op, cop, line) }
150
144
  end
151
145
  end
152
146
 
153
- def apply_cop_operation(analyses, operation, cop_name, line)
154
- analysis = analyses[cop_name]
155
- start_line = analysis.start_line_number
156
-
157
- case operation
158
- when '+' # Enable cop
159
- return unless start_line
160
-
161
- analyses[cop_name] = CopAnalysis.new(analysis.line_ranges + [start_line..line], nil)
162
- when '-' # Disable cop
163
- return if start_line
147
+ def apply_cop_op(analyses, operation, cop, line)
148
+ analysis = analyses[cop]
149
+ if operation == '-' && !analysis.start_line_number
150
+ analyses[cop] = CopAnalysis.new(analysis.line_ranges, line)
151
+ elsif operation == '+' && analysis.start_line_number
152
+ analyses[cop] =
153
+ CopAnalysis.new(analysis.line_ranges + [analysis.start_line_number..line], nil)
154
+ end
155
+ end
164
156
 
165
- analyses[cop_name] = CopAnalysis.new(analysis.line_ranges, line)
157
+ def pop_state(analyses, line)
158
+ saved = @stack.pop
159
+ saved.each do |cop, old|
160
+ cur = analyses[cop]
161
+ new_range = cur.start_line_number ? [cur.start_line_number..(line - 1)] : []
162
+ ranges = cur.line_ranges + new_range
163
+ new_start = old.start_line_number ? line : nil
164
+ analyses[cop] = CopAnalysis.new(ranges, new_start)
166
165
  end
167
166
  end
168
167
 
@@ -151,31 +151,22 @@ module RuboCop
151
151
  # When testing a plugin using `rubocop/rspec/support`, the plugin is loaded automatically,
152
152
  # so this API is usually not needed. It is intended to be used only when implementing tests
153
153
  # that do not use `rubocop/rspec/support`.
154
- # rubocop:disable Metrics/MethodLength
155
154
  def inject_defaults!(config_yml_path)
156
155
  if Pathname(config_yml_path).directory?
157
- # TODO: Since the warning noise is expected to be high until some time after the release,
158
- # warnings will only be issued when `RUBYOPT=-w` is specified.
159
- # To proceed step by step, the next step is to remove `$VERBOSE` and always issue warning.
160
- # Eventually, `project_root` will no longer be accepted.
161
- if $VERBOSE
162
- warn Rainbow(<<~MESSAGE).yellow, uplevel: 1
163
- Use config YAML file path instead of project root directory.
164
- e.g., `path/to/config/default.yml`
165
- MESSAGE
166
- end
167
- # NOTE: For compatibility.
168
- project_root = config_yml_path
169
- path = File.join(project_root, 'config', 'default.yml')
170
- config = load_file(path)
171
- else
172
- hash = ConfigLoader.load_yaml_configuration(config_yml_path.to_s)
173
- config = Config.new(hash, config_yml_path).tap(&:make_excludes_absolute)
156
+ warn Rainbow(<<~MESSAGE).yellow, uplevel: 1
157
+ Use config YAML file path instead of project root directory.
158
+ e.g., `path/to/config/default.yml`
159
+ MESSAGE
160
+ raise ArgumentError,
161
+ 'Passing a project root directory to `inject_defaults!` is no longer supported.'
174
162
  end
175
163
 
164
+ path = config_yml_path.to_s
165
+ hash = ConfigLoader.load_yaml_configuration(path)
166
+ config = Config.new(hash, path).tap(&:make_excludes_absolute)
167
+
176
168
  @default_configuration = ConfigLoader.merge_with_default(config, path)
177
169
  end
178
- # rubocop:enable Metrics/MethodLength
179
170
 
180
171
  # Returns the path RuboCop inferred as the root of the project. No file
181
172
  # searches will go past this directory.
@@ -179,15 +179,24 @@ module RuboCop
179
179
 
180
180
  top_level_send = find_top_level_send(send_node)
181
181
  node_to_correct =
182
- if top_level_send != send_node || should_correct_entire_method_call?(top_level_send)
183
- top_level_send
184
- else
185
- node
186
- end
182
+ should_correct_entire_chain?(send_node, top_level_send) ? top_level_send : node
187
183
 
188
184
  AlignmentCorrector.correct(corrector, processed_source, node_to_correct, column_delta)
189
185
  end
190
186
 
187
+ def should_correct_entire_chain?(send_node, top_level_send)
188
+ return false unless style == :special_for_inner_method_call_in_parentheses
189
+ return false unless inner_call?(top_level_send)
190
+
191
+ top_level_send != send_node || begins_its_line?(top_level_send.loc.end)
192
+ end
193
+
194
+ def inner_call?(top_level_send)
195
+ outer_call = top_level_send.parent
196
+
197
+ outer_call&.send_type? && outer_call.parenthesized?
198
+ end
199
+
191
200
  def find_top_level_send(send_node)
192
201
  top_level_send = send_node
193
202
  while top_level_send.parent&.send_type? &&
@@ -198,14 +207,6 @@ module RuboCop
198
207
  top_level_send
199
208
  end
200
209
 
201
- def should_correct_entire_method_call?(send_node)
202
- return false unless style == :special_for_inner_method_call_in_parentheses
203
- return false unless special_inner_call_indentation?(send_node)
204
-
205
- closing_paren = send_node.loc.end
206
- closing_paren && begins_its_line?(closing_paren)
207
- end
208
-
209
210
  def bare_operator?(node)
210
211
  node.operator_method? && !node.dot?
211
212
  end
@@ -424,7 +424,7 @@ module RuboCop
424
424
  end
425
425
 
426
426
  def contains_access_modifier?(body_node)
427
- return false unless body_node.begin_type?
427
+ return false unless body_node&.begin_type?
428
428
 
429
429
  body_node.children.any? { |child| child.send_type? && child.bare_access_modifier? }
430
430
  end
@@ -473,10 +473,10 @@ module RuboCop
473
473
  end
474
474
 
475
475
  def block_body_indentation_base(node, end_loc)
476
- if style != :relative_to_receiver || !dot_on_new_line?(node)
477
- end_loc
478
- else
476
+ if dot_on_new_line?(node)
479
477
  node.send_node.loc.dot
478
+ else
479
+ end_loc
480
480
  end
481
481
  end
482
482
 
@@ -46,10 +46,12 @@ module RuboCop
46
46
  # .a
47
47
  # .b
48
48
  # .c
49
- class MultilineMethodCallIndentation < Base
49
+ #
50
+ class MultilineMethodCallIndentation < Base # rubocop:disable Metrics/ClassLength
50
51
  include ConfigurableEnforcedStyle
51
52
  include Alignment
52
53
  include MultilineExpressionIndentation
54
+ include RangeHelp
53
55
  extend AutoCorrector
54
56
 
55
57
  def validate_config
@@ -64,8 +66,39 @@ module RuboCop
64
66
 
65
67
  private
66
68
 
69
+ def find_base_receiver(node)
70
+ base_receiver = node
71
+ base_receiver = base_receiver.receiver while base_receiver.receiver
72
+ base_receiver
73
+ end
74
+
75
+ def find_pair_ancestor(node)
76
+ node.each_ancestor.find(&:pair_type?)
77
+ end
78
+
79
+ def unwrap_block_node(node)
80
+ node&.any_block_type? ? node.send_node : node
81
+ end
82
+
67
83
  def autocorrect(corrector, node)
68
- AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
84
+ if @send_node.block_node
85
+ correct_selector_only(corrector, node)
86
+ correct_block(corrector, @send_node.block_node)
87
+ else
88
+ AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
89
+ end
90
+ end
91
+
92
+ def correct_selector_only(corrector, node)
93
+ selector_line = processed_source.buffer.line_range(node.first_line)
94
+ selector_range = range_between(selector_line.begin_pos, selector_line.end_pos)
95
+ AlignmentCorrector.correct(corrector, processed_source, selector_range, @column_delta)
96
+ end
97
+
98
+ def correct_block(corrector, block_node)
99
+ AlignmentCorrector.correct(corrector, processed_source, block_node.body, @column_delta)
100
+ end_range = range_by_whole_lines(block_node.loc.end, include_final_newline: false)
101
+ AlignmentCorrector.correct(corrector, processed_source, end_range, @column_delta)
69
102
  end
70
103
 
71
104
  def relevant_node?(send_node)
@@ -84,11 +117,55 @@ module RuboCop
84
117
  end
85
118
  end
86
119
 
87
- # rubocop:disable Metrics/AbcSize
88
120
  def offending_range(node, lhs, rhs, given_style)
89
121
  return false unless begins_its_line?(rhs)
90
- return false if not_for_this_cop?(node)
91
122
 
123
+ @send_node = node # Store for use in autocorrect
124
+ pair_ancestor = find_pair_ancestor(node)
125
+ if hash_pair_aligned?(pair_ancestor, given_style)
126
+ return check_hash_pair_indentation(node, lhs, rhs)
127
+ end
128
+ if hash_pair_indented?(node, pair_ancestor, given_style)
129
+ return check_hash_pair_indented_style(rhs, pair_ancestor)
130
+ end
131
+
132
+ return false if !pair_ancestor && not_for_this_cop?(node)
133
+
134
+ check_regular_indentation(node, lhs, rhs, given_style)
135
+ end
136
+
137
+ def hash_pair_aligned?(pair_ancestor, given_style)
138
+ pair_ancestor && given_style == :aligned
139
+ end
140
+
141
+ def hash_pair_indented?(node, pair_ancestor, given_style)
142
+ pair_ancestor && given_style == :indented && find_base_receiver(node).hash_type?
143
+ end
144
+
145
+ def check_hash_pair_indented_style(rhs, pair_ancestor)
146
+ pair_key = pair_ancestor.key
147
+ double_indentation = configured_indentation_width * 2
148
+ correct_column = pair_key.source_range.column + double_indentation
149
+ @hash_pair_base_column = pair_key.source_range.column + configured_indentation_width
150
+
151
+ calculate_column_delta_offense(rhs, correct_column)
152
+ end
153
+
154
+ def check_hash_pair_indentation(node, lhs, rhs)
155
+ @base = find_hash_pair_alignment_base(node) || lhs.source_range
156
+
157
+ calculate_column_delta_offense(rhs, @base.column)
158
+ end
159
+
160
+ def find_hash_pair_alignment_base(node)
161
+ base_receiver = find_base_receiver(node.receiver)
162
+ return unless base_receiver.hash_type?
163
+
164
+ first_call = first_call_has_a_dot(node)
165
+ first_call.loc.dot.join(first_call.loc.selector)
166
+ end
167
+
168
+ def check_regular_indentation(node, lhs, rhs, given_style)
92
169
  @base = alignment_base(node, rhs, given_style)
93
170
  correct_column = if @base
94
171
  parent = node.parent
@@ -97,20 +174,22 @@ module RuboCop
97
174
  else
98
175
  indentation(lhs) + correct_indentation(node)
99
176
  end
177
+
178
+ calculate_column_delta_offense(rhs, correct_column)
179
+ end
180
+
181
+ def calculate_column_delta_offense(rhs, correct_column)
100
182
  @column_delta = correct_column - rhs.column
101
183
  rhs if @column_delta.nonzero?
102
184
  end
103
- # rubocop:enable Metrics/AbcSize
104
185
 
105
186
  def extra_indentation(given_style, parent)
106
- if given_style == :indented_relative_to_receiver
107
- if parent&.type?(:splat, :kwsplat)
108
- configured_indentation_width - parent.loc.operator.length
109
- else
110
- configured_indentation_width
111
- end
187
+ return 0 unless given_style == :indented_relative_to_receiver
188
+
189
+ if parent&.type?(:splat, :kwsplat)
190
+ configured_indentation_width - parent.loc.operator.length
112
191
  else
113
- 0
192
+ configured_indentation_width
114
193
  end
115
194
  end
116
195
 
@@ -129,7 +208,7 @@ module RuboCop
129
208
  end
130
209
 
131
210
  def should_align_with_base?
132
- @base && style != :indented_relative_to_receiver
211
+ @base && style == :aligned
133
212
  end
134
213
 
135
214
  def relative_to_receiver_message(rhs)
@@ -146,10 +225,16 @@ module RuboCop
146
225
  end
147
226
 
148
227
  def no_base_message(lhs, rhs, node)
149
- used_indentation = rhs.column - indentation(lhs)
228
+ if @hash_pair_base_column
229
+ used_indentation = rhs.column - @hash_pair_base_column
230
+ expected_indentation = configured_indentation_width
231
+ else
232
+ used_indentation = rhs.column - indentation(lhs)
233
+ expected_indentation = correct_indentation(node)
234
+ end
150
235
  what = operation_description(node, rhs)
151
236
 
152
- "Use #{correct_indentation(node)} (not #{used_indentation}) " \
237
+ "Use #{expected_indentation} (not #{used_indentation}) " \
153
238
  "spaces for indenting #{what} spanning multiple lines."
154
239
  end
155
240
 
@@ -157,8 +242,6 @@ module RuboCop
157
242
  case given_style
158
243
  when :aligned
159
244
  semantic_alignment_base(node, rhs) || syntactic_alignment_base(node, rhs)
160
- when :indented
161
- nil
162
245
  when :indented_relative_to_receiver
163
246
  receiver_alignment_base(node)
164
247
  end
@@ -195,29 +278,55 @@ module RuboCop
195
278
  # .b
196
279
  # .c
197
280
  def receiver_alignment_base(node)
198
- node = node.receiver while node.receiver
199
- node = node.parent
200
- node = node.parent until node.loc.dot
281
+ hash_method_base = find_hash_method_base_in_receiver_chain(node)
282
+ return hash_method_base if hash_method_base
283
+
284
+ first_call = first_call_has_a_dot(node)
285
+ first_call.receiver.source_range
286
+ end
287
+
288
+ def find_hash_method_base_in_receiver_chain(node)
289
+ receiver_chain = unwrap_block_node(node.receiver)
290
+ while receiver_chain&.call_type?
291
+ base_receiver = unwrap_block_node(receiver_chain.receiver)
292
+ if alignment_base_for_chained_receiver?(receiver_chain, base_receiver)
293
+ return receiver_chain.loc.dot.join(receiver_chain.loc.selector)
294
+ end
295
+
296
+ receiver_chain = base_receiver
297
+ end
298
+ end
201
299
 
202
- node&.receiver&.source_range
300
+ def alignment_base_for_chained_receiver?(receiver_chain, base_receiver)
301
+ base_receiver&.hash_type? ||
302
+ method_on_receiver_last_line?(receiver_chain, base_receiver, :begin)
203
303
  end
204
304
 
205
305
  def semantic_alignment_node(node)
206
306
  return if argument_in_method_call(node, :with_parentheses)
207
307
 
208
- dot_right_above = get_dot_right_above(node)
209
- return dot_right_above if dot_right_above
210
-
211
- if (multiline_block_chain_node = find_multiline_block_chain_node(node))
212
- return multiline_block_chain_node
213
- end
308
+ get_dot_right_above(node) ||
309
+ find_multiline_block_chain_node(node) ||
310
+ first_call_alignment_node(node)
311
+ end
214
312
 
313
+ def first_call_alignment_node(node)
215
314
  node = first_call_has_a_dot(node)
315
+ base_receiver = find_base_receiver(node)
316
+
317
+ return node if method_on_receiver_last_line?(node, base_receiver, :array)
216
318
  return if node.loc.dot.line != node.first_line
319
+ return if method_on_receiver_last_line?(node, base_receiver, :begin)
217
320
 
218
321
  node
219
322
  end
220
323
 
324
+ def method_on_receiver_last_line?(node, base_receiver, type)
325
+ base_receiver &&
326
+ node.loc.dot.line == base_receiver.last_line &&
327
+ base_receiver.type?(type)
328
+ end
329
+
221
330
  def get_dot_right_above(node)
222
331
  node.each_ancestor.find do |a|
223
332
  dot = a.loc.dot if a.loc?(:dot)
@@ -228,23 +337,30 @@ module RuboCop
228
337
  end
229
338
 
230
339
  def find_multiline_block_chain_node(node)
231
- return unless (block_node = node.each_descendant(:any_block).first)
232
- return unless block_node.multiline? && block_node.parent.call_type?
340
+ return find_continuation_receiver(node) if node.block_node
233
341
 
234
- if node.receiver.call_type?
235
- node.receiver
236
- else
237
- block_node.parent
238
- end
342
+ handle_descendant_block(node)
343
+ end
344
+
345
+ def find_continuation_receiver(node)
346
+ receiver = node.receiver
347
+ return unless receiver.call_type? && receiver.loc.dot && receiver.receiver
348
+ return unless receiver.loc.dot.line > receiver.receiver.last_line
349
+
350
+ receiver
351
+ end
352
+
353
+ def handle_descendant_block(node)
354
+ block_node = node.each_descendant(:any_block).first
355
+ return unless block_node&.multiline?
356
+
357
+ node.receiver.call_type? ? node.receiver : block_node.parent
239
358
  end
240
359
 
241
360
  def first_call_has_a_dot(node)
242
- # descend to root of method chain
243
- node = node.receiver while node.receiver
244
- # ascend to first call which has a dot
245
- node = node.parent
361
+ base = find_base_receiver(node)
362
+ node = base.parent
246
363
  node = node.parent until node.loc?(:dot)
247
-
248
364
  node
249
365
  end
250
366
 
@@ -137,6 +137,17 @@ module RuboCop
137
137
  # true
138
138
  # end
139
139
  #
140
+ # @example WaywardPredicates: ['infinite?', 'nonzero?'] (default)
141
+ # # good
142
+ # def non_predicate_method(num)
143
+ # num.infinite?
144
+ # end
145
+ #
146
+ # # good
147
+ # def non_predicate_method(num)
148
+ # num.nonzero?
149
+ # end
150
+ #
140
151
  class PredicateMethod < Base
141
152
  include AllowedMethods
142
153
  include AllowedPattern
@@ -196,7 +196,7 @@ module RuboCop
196
196
  end
197
197
  end
198
198
 
199
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
199
+ # rubocop:disable Metrics/AbcSize
200
200
  def autocorrect(corrector, node, condition, replacement, guard)
201
201
  corrector.replace(node.loc.keyword.join(condition.source_range), replacement)
202
202
 
@@ -205,10 +205,10 @@ module RuboCop
205
205
 
206
206
  corrector.replace(node.loc.begin, "\n") if node.then?
207
207
 
208
- if if_branch&.send_type? && heredoc?(if_branch.last_argument)
209
- autocorrect_heredoc_argument(corrector, node, if_branch, else_branch, guard)
210
- elsif else_branch&.send_type? && heredoc?(else_branch.last_argument)
211
- autocorrect_heredoc_argument(corrector, node, else_branch, if_branch, guard)
208
+ if (if_heredoc = find_heredoc_argument(if_branch))
209
+ autocorrect_heredoc_argument(corrector, node, if_heredoc, else_branch, guard)
210
+ elsif (else_heredoc = find_heredoc_argument(else_branch))
211
+ autocorrect_heredoc_argument(corrector, node, else_heredoc, if_branch, guard)
212
212
  else
213
213
  corrector.remove(node.loc.end)
214
214
  return unless node.else?
@@ -217,21 +217,31 @@ module RuboCop
217
217
  corrector.remove(range_of_branch_to_remove(node, guard))
218
218
  end
219
219
  end
220
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
220
+ # rubocop:enable Metrics/AbcSize
221
221
 
222
222
  def heredoc?(argument)
223
223
  argument.respond_to?(:heredoc?) && argument.heredoc?
224
224
  end
225
225
 
226
- def autocorrect_heredoc_argument(corrector, node, heredoc_branch, leave_branch, guard)
226
+ def find_heredoc_argument(node)
227
+ return unless node&.call_type?
228
+
229
+ last_arg = node.last_argument
230
+
231
+ if heredoc?(last_arg)
232
+ last_arg
233
+ elsif last_arg&.call_type?
234
+ find_heredoc_argument(last_arg)
235
+ end
236
+ end
237
+
238
+ def autocorrect_heredoc_argument(corrector, node, heredoc_node, leave_branch, guard)
227
239
  remove_whole_lines(corrector, node.loc.end)
228
240
  return unless node.else?
229
241
 
230
242
  if leave_branch
231
243
  remove_whole_lines(corrector, leave_branch.source_range)
232
- corrector.insert_after(
233
- heredoc_branch.last_argument.loc.heredoc_end, "\n#{leave_branch.source}"
234
- )
244
+ corrector.insert_after(heredoc_node.loc.heredoc_end, "\n#{leave_branch.source}")
235
245
  end
236
246
 
237
247
  remove_whole_lines(corrector, node.loc.else)
@@ -71,7 +71,8 @@ module RuboCop
71
71
  # if short_condition # a long comment that makes it too long if it were just a single line
72
72
  # do_something
73
73
  # end
74
- class IfUnlessModifier < Base
74
+ #
75
+ class IfUnlessModifier < Base # rubocop:disable Metrics/ClassLength
75
76
  include StatementModifier
76
77
  include LineLengthHelp
77
78
  include AllowedPattern
@@ -230,15 +231,6 @@ module RuboCop
230
231
  true
231
232
  end
232
233
 
233
- def too_long_line_based_on_allow_qualified_name?(line)
234
- if allow_qualified_name?
235
- namespace_range = find_excessive_range(line, :namespace)
236
- return false if namespace_range && allowed_position?(line, namespace_range)
237
- end
238
-
239
- true
240
- end
241
-
242
234
  def line_length_enabled_at_line?(line)
243
235
  processed_source.comment_config.cop_enabled_at_line?('Layout/LineLength', line)
244
236
  end
@@ -248,7 +240,44 @@ module RuboCop
248
240
  end
249
241
 
250
242
  def non_eligible_node?(node)
251
- non_simple_if_unless?(node) || node.chained? || node.nested_conditional? || super
243
+ non_simple_if_unless?(node) || node.chained? || node.nested_conditional? ||
244
+ multiline_inside_collection?(node) || super
245
+ end
246
+
247
+ def multiline_inside_collection?(node)
248
+ return false if node.modifier_form?
249
+ return false unless (collection = find_containing_collection(node))
250
+
251
+ collection.children.any? { |child| sibling_if_shares_line?(child, node) }
252
+ end
253
+
254
+ def sibling_if_shares_line?(child, node)
255
+ inner = unwrap_begin(child)
256
+ inner&.if_type? && shares_line_with?(inner, node)
257
+ end
258
+
259
+ def unwrap_begin(node)
260
+ return unless node.is_a?(RuboCop::AST::Node)
261
+
262
+ node = node.value if node.pair_type?
263
+ node.begin_type? ? node.children.first : node
264
+ end
265
+
266
+ def shares_line_with?(inner, node)
267
+ inner.loc.line == node.loc.end.line || inner.loc.end.line == node.loc.line
268
+ end
269
+
270
+ def find_containing_collection(node)
271
+ parent = node.parent
272
+ return collection_from_ancestor(parent) unless parent&.begin_type?
273
+
274
+ collection_from_ancestor(parent.parent)
275
+ end
276
+
277
+ def collection_from_ancestor(node)
278
+ return node if node&.type?(:array, :call)
279
+
280
+ node.parent if node&.type?(:pair)
252
281
  end
253
282
 
254
283
  def non_simple_if_unless?(node)
@@ -54,7 +54,10 @@ module RuboCop
54
54
  # # good (but potentially an unsafe correction)
55
55
  # foo.do_something?
56
56
  #
57
- # @example AllowedMethods: ['nonzero?'] (default)
57
+ # @example AllowedMethods: ['infinite?', 'nonzero?'] (default)
58
+ # # good
59
+ # num.infinite? ? true : false
60
+ #
58
61
  # # good
59
62
  # num.nonzero? ? true : false
60
63
  #
@@ -42,7 +42,7 @@ module RuboCop
42
42
  # Array.public_method_defined?(:foo)
43
43
  # Array.include?(:foo)
44
44
  #
45
- # @example AllowedMethods: [included_modules]
45
+ # @example AllowedMethods: [included_modules]
46
46
  #
47
47
  # # good
48
48
  # Array.included_modules.include?(:foo)
@@ -182,6 +182,8 @@ module RuboCop
182
182
  end
183
183
 
184
184
  def receivers_match?(length_receiver, array_receiver)
185
+ return array_receiver.self_type? unless length_receiver
186
+
185
187
  unless preserving_method?(array_receiver) && preserving_method?(length_receiver)
186
188
  return false
187
189
  end
@@ -58,7 +58,10 @@ module RuboCop
58
58
  # # good
59
59
  # a.nil? || a
60
60
  #
61
- # @example AllowedMethods: ['nonzero?'] (default)
61
+ # @example AllowedMethods: ['infinite?', 'nonzero?'] (default)
62
+ # # good
63
+ # num.infinite? ? true : false
64
+ #
62
65
  # # good
63
66
  # num.nonzero? ? true : false
64
67
  #
@@ -177,8 +177,8 @@ module RuboCop
177
177
  # @return [Array<cop>]
178
178
  def roundup_relevant_cops(processed_source)
179
179
  cops.select do |cop|
180
- next true if processed_source.comment_config.cop_opted_in?(cop)
181
180
  next false if cop.excluded_file?(processed_source.file_path)
181
+ next true if processed_source.comment_config.cop_opted_in?(cop)
182
182
  next false unless @registry.enabled?(cop, @config)
183
183
 
184
184
  support_target_ruby_version?(cop) && support_target_rails_version?(cop)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'disable_comment_edits'
3
4
  require_relative 'severity'
4
5
 
5
6
  #
@@ -16,11 +17,12 @@ module RuboCop
16
17
  # Diagnostic for Language Server Protocol of RuboCop.
17
18
  # @api private
18
19
  class Diagnostic
19
- def initialize(document_encoding, offense, uri, cop_class)
20
+ def initialize(document_encoding, offense, uri, cop_class, processed_source = nil)
20
21
  @document_encoding = document_encoding
21
22
  @offense = offense
22
23
  @uri = uri
23
24
  @cop_class = cop_class
25
+ @processed_source = processed_source
24
26
  end
25
27
 
26
28
  def to_lsp_code_actions
@@ -141,25 +143,11 @@ module RuboCop
141
143
  # rubocop:enable Metrics/MethodLength
142
144
 
143
145
  def line_disable_comment
144
- new_text = if @offense.source_line.include?(' # rubocop:disable ')
145
- ",#{@offense.cop_name}"
146
- else
147
- " # rubocop:disable #{@offense.cop_name}"
148
- end
149
-
150
- eol = LanguageServer::Protocol::Interface::Position.new(
151
- line: @offense.line - 1,
152
- character: to_position_character(@offense.source_line.length)
153
- )
154
-
155
- # TODO: fails for multiline strings - may be preferable to use block
156
- # comments to disable some offenses
157
- inline_comment = LanguageServer::Protocol::Interface::TextEdit.new(
158
- range: LanguageServer::Protocol::Interface::Range.new(start: eol, end: eol),
159
- new_text: new_text
160
- )
161
-
162
- [inline_comment]
146
+ DisableCommentEdits.new(
147
+ offense: @offense,
148
+ document_encoding: @document_encoding,
149
+ processed_source: @processed_source
150
+ ).edits
163
151
  end
164
152
 
165
153
  def to_position_character(utf8_index)
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module LSP
5
+ # Builds LSP text edits for rubocop:disable comments.
6
+ # @api private
7
+ class DisableCommentEdits
8
+ def initialize(offense:, document_encoding:, processed_source:)
9
+ @offense = offense
10
+ @document_encoding = document_encoding
11
+ @processed_source = processed_source
12
+ end
13
+
14
+ def edits
15
+ literal_range = multiline_literal_range
16
+ return block_disable_comments(literal_range) if literal_range
17
+
18
+ inline_disable_comment
19
+ end
20
+
21
+ private
22
+
23
+ def inline_disable_comment
24
+ eol = position(@offense.line - 1, @offense.source_line.length, @offense.source_line)
25
+ [text_edit(eol, inline_comment_text)]
26
+ end
27
+
28
+ def inline_comment_text
29
+ if @offense.source_line.include?(' # rubocop:disable ')
30
+ ",#{@offense.cop_name}"
31
+ else
32
+ " # rubocop:disable #{@offense.cop_name}"
33
+ end
34
+ end
35
+
36
+ def block_disable_comments(range)
37
+ full_line_range = range_by_lines(range)
38
+ leading_whitespace = full_line_range.source_line[/^\s*/].to_s
39
+ [
40
+ disable_edit(full_line_range.first_line, leading_whitespace),
41
+ enable_edit(full_line_range, leading_whitespace)
42
+ ]
43
+ end
44
+
45
+ def disable_edit(first_line, leading_whitespace)
46
+ position = position(first_line - 1, 0, '')
47
+ text_edit(position, "#{leading_whitespace}# rubocop:disable #{@offense.cop_name}\n")
48
+ end
49
+
50
+ def enable_edit(full_line_range, leading_whitespace)
51
+ last_line = full_line_range.last_line
52
+ last_line_text = full_line_range.source_buffer.source_line(last_line)
53
+ position = position(last_line - 1, last_line_text.length, last_line_text)
54
+ text_edit(position, "\n#{leading_whitespace}# rubocop:enable #{@offense.cop_name}")
55
+ end
56
+
57
+ def text_edit(position, new_text)
58
+ range = LanguageServer::Protocol::Interface::Range.new(start: position, end: position)
59
+ LanguageServer::Protocol::Interface::TextEdit.new(range: range, new_text: new_text)
60
+ end
61
+
62
+ def position(line, utf8_index, line_text)
63
+ LanguageServer::Protocol::Interface::Position.new(
64
+ line: line,
65
+ character: position_character(utf8_index, line_text)
66
+ )
67
+ end
68
+
69
+ def position_character(utf8_index, line_text)
70
+ str = line_text[0, utf8_index]
71
+ if @document_encoding == Encoding::UTF_16LE || @document_encoding.nil?
72
+ str.length + str.b.count("\xf0-\xff".b)
73
+ else
74
+ str.length
75
+ end
76
+ end
77
+
78
+ def multiline_literal_range
79
+ return unless @processed_source&.ast
80
+
81
+ offense_range = @offense.location
82
+ multiline_ranges&.find do |range|
83
+ eol_comment_would_be_inside_literal?(offense_range, range)
84
+ end
85
+ end
86
+
87
+ def multiline_ranges
88
+ @processed_source.ast.each_node.filter_map do |node|
89
+ if surrounding_heredoc?(node)
90
+ heredoc_range(node)
91
+ elsif string_continuation?(node)
92
+ range_by_lines(node.source_range)
93
+ elsif surrounding_percent_array?(node) || multiline_string?(node)
94
+ node.source_range
95
+ end
96
+ end
97
+ end
98
+
99
+ def eol_comment_would_be_inside_literal?(offense_range, literal_range)
100
+ offense_line = offense_range.line
101
+ offense_line >= literal_range.first_line && offense_line < literal_range.last_line
102
+ end
103
+
104
+ def surrounding_heredoc?(node)
105
+ node.any_str_type? && node.heredoc?
106
+ end
107
+
108
+ def heredoc_range(node)
109
+ node.source_range.join(node.loc.heredoc_end)
110
+ end
111
+
112
+ def surrounding_percent_array?(node)
113
+ node.array_type? && node.percent_literal?
114
+ end
115
+
116
+ def string_continuation?(node)
117
+ node.any_str_type? && node.source.match?(/\\\s*$/)
118
+ end
119
+
120
+ def multiline_string?(node)
121
+ node.dstr_type? && node.multiline?
122
+ end
123
+
124
+ def range_by_lines(range)
125
+ begin_of_first_line = range.begin_pos - range.column
126
+
127
+ last_line = range.source_buffer.source_line(range.last_line)
128
+ last_line_offset = last_line.length - range.last_column
129
+ end_of_last_line = range.end_pos + last_line_offset
130
+
131
+ Parser::Source::Range.new(range.source_buffer, begin_of_first_line, end_of_last_line)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -49,15 +49,25 @@ module RuboCop
49
49
  diagnostic_options[:only] = config_only_options if @lint_mode || @layout_mode
50
50
 
51
51
  @runner.run(path, text, diagnostic_options, prism_result: prism_result)
52
+ processed_source = @runner.processed_source
53
+ config = @runner.config_for_working_directory
52
54
  @runner.offenses.map do |offense|
53
- Diagnostic.new(
54
- document_encoding, offense, path, @cop_registry[offense.cop_name]&.first
55
- ).to_lsp_diagnostic(@runner.config_for_working_directory)
55
+ build_diagnostic(offense, path, document_encoding, processed_source, config)
56
56
  end
57
57
  end
58
58
 
59
59
  private
60
60
 
61
+ def build_diagnostic(offense, path, document_encoding, processed_source, config)
62
+ Diagnostic.new(
63
+ document_encoding,
64
+ offense,
65
+ path,
66
+ @cop_registry[offense.cop_name]&.first,
67
+ processed_source
68
+ ).to_lsp_diagnostic(config)
69
+ end
70
+
61
71
  def config_only_options
62
72
  only_options = []
63
73
  only_options << 'Lint' if @lint_mode
@@ -17,7 +17,7 @@ module RuboCop
17
17
  class StdinRunner < RuboCop::Runner
18
18
  class ConfigurationError < StandardError; end
19
19
 
20
- attr_reader :offenses, :config_for_working_directory
20
+ attr_reader :offenses, :config_for_working_directory, :processed_source
21
21
 
22
22
  DEFAULT_RUBOCOP_OPTIONS = {
23
23
  stderr: true,
@@ -45,6 +45,7 @@ module RuboCop
45
45
  @options[:stdin] = contents
46
46
 
47
47
  @prism_result = prism_result
48
+ @processed_source = nil
48
49
 
49
50
  @offenses = []
50
51
  @warnings = []
@@ -64,6 +65,12 @@ module RuboCop
64
65
  def file_finished(_file, offenses)
65
66
  @offenses = offenses
66
67
  end
68
+
69
+ def do_inspection_loop(file)
70
+ source, offenses = super
71
+ @processed_source = source
72
+ [source, offenses]
73
+ end
67
74
  end
68
75
  end
69
76
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.84.0'
6
+ STRING = '1.84.1'
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.84.0
4
+ version: 1.84.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -1038,6 +1038,7 @@ files:
1038
1038
  - lib/rubocop/lockfile.rb
1039
1039
  - lib/rubocop/lsp.rb
1040
1040
  - lib/rubocop/lsp/diagnostic.rb
1041
+ - lib/rubocop/lsp/disable_comment_edits.rb
1041
1042
  - lib/rubocop/lsp/logger.rb
1042
1043
  - lib/rubocop/lsp/routes.rb
1043
1044
  - lib/rubocop/lsp/runtime.rb
@@ -1096,7 +1097,7 @@ licenses:
1096
1097
  - MIT
1097
1098
  metadata:
1098
1099
  homepage_uri: https://rubocop.org/
1099
- changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.84.0
1100
+ changelog_uri: https://github.com/rubocop/rubocop/releases/tag/v1.84.1
1100
1101
  source_code_uri: https://github.com/rubocop/rubocop/
1101
1102
  documentation_uri: https://docs.rubocop.org/rubocop/1.84/
1102
1103
  bug_tracker_uri: https://github.com/rubocop/rubocop/issues