rubocop 1.88.2 → 1.89.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.
- checksums.yaml +4 -4
- data/config/default.yml +67 -11
- data/lib/rubocop/comment_config.rb +35 -19
- data/lib/rubocop/config.rb +53 -0
- data/lib/rubocop/config_validator.rb +38 -1
- data/lib/rubocop/cop/badge.rb +8 -0
- data/lib/rubocop/cop/base.rb +6 -4
- data/lib/rubocop/cop/bundler.rb +19 -0
- data/lib/rubocop/cop/corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
- data/lib/rubocop/cop/gemspec.rb +22 -0
- data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
- data/lib/rubocop/cop/generator.rb +4 -0
- data/lib/rubocop/cop/layout/class_structure.rb +8 -2
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
- data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
- data/lib/rubocop/cop/layout/line_length.rb +85 -12
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
- data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
- data/lib/rubocop/cop/layout.rb +114 -0
- data/lib/rubocop/cop/lazy_loader.rb +41 -0
- data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
- data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
- data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
- data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
- data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
- data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
- data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
- data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
- data/lib/rubocop/cop/lint/missing_super.rb +31 -1
- data/lib/rubocop/cop/lint/name_typo.rb +223 -0
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +37 -1
- data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
- data/lib/rubocop/cop/lint/void.rb +17 -6
- data/lib/rubocop/cop/lint.rb +176 -0
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics.rb +31 -0
- data/lib/rubocop/cop/migration/department_name.rb +0 -4
- data/lib/rubocop/cop/migration.rb +13 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
- data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
- data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
- data/lib/rubocop/cop/mixin/statement_modifier.rb +42 -6
- data/lib/rubocop/cop/mixin.rb +78 -77
- data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
- data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
- data/lib/rubocop/cop/naming/variable_number.rb +3 -1
- data/lib/rubocop/cop/naming.rb +31 -0
- data/lib/rubocop/cop/offense.rb +10 -6
- data/lib/rubocop/cop/registry.rb +153 -43
- data/lib/rubocop/cop/security.rb +19 -0
- data/lib/rubocop/cop/style/array_intersect.rb +24 -5
- data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
- data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
- data/lib/rubocop/cop/style/copyright.rb +6 -1
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
- data/lib/rubocop/cop/style/documentation.rb +47 -0
- data/lib/rubocop/cop/style/endless_method.rb +0 -10
- data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -48
- data/lib/rubocop/cop/style/lambda.rb +4 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
- data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
- data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
- data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
- data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
- data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
- data/lib/rubocop/cop/style/redundant_format.rb +12 -3
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
- data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
- data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
- data/lib/rubocop/cop/style/static_class.rb +21 -0
- data/lib/rubocop/cop/style.rb +310 -0
- data/lib/rubocop/cop/team.rb +48 -3
- data/lib/rubocop/cop/variable_force/variable.rb +18 -6
- data/lib/rubocop/cops_documentation_generator.rb +1 -1
- data/lib/rubocop/directive_comment.rb +2 -0
- data/lib/rubocop/feature_loader.rb +0 -6
- data/lib/rubocop/lsp/routes.rb +41 -3
- data/lib/rubocop/lsp/runtime.rb +4 -0
- data/lib/rubocop/lsp/server.rb +4 -0
- data/lib/rubocop/lsp/stdin_runner.rb +20 -0
- data/lib/rubocop/pending_cops_reporter.rb +1 -6
- data/lib/rubocop/project_index_loader.rb +16 -0
- data/lib/rubocop/result_cache.rb +6 -4
- data/lib/rubocop/rspec/shared_contexts.rb +14 -2
- data/lib/rubocop/runner.rb +45 -20
- data/lib/rubocop/server/cache.rb +10 -5
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +17 -626
- metadata +18 -3
data/lib/rubocop/cop/registry.rb
CHANGED
|
@@ -50,21 +50,37 @@ module RuboCop
|
|
|
50
50
|
|
|
51
51
|
def initialize(cops = [], options = {})
|
|
52
52
|
@departments = Set.new
|
|
53
|
+
# Maps each badge to its cop class, or to the fully qualified constant name (a `String`)
|
|
54
|
+
# when the cop is registered for lazy loading and its file is not loaded yet.
|
|
55
|
+
# A single insertion-ordered map keeps the registration order stable no matter when
|
|
56
|
+
# a lazy-loaded cop's file gets loaded, because reassigning an existing key preserves
|
|
57
|
+
# its position; the cop execution order depends on this order.
|
|
53
58
|
@cops_by_badge = {}
|
|
54
|
-
@lazy_loaded_cops_by_badge = {}
|
|
55
59
|
|
|
56
60
|
@enrollment_queue = cops
|
|
57
61
|
@options = options
|
|
58
62
|
|
|
59
63
|
@enabled_cache = {}.compare_by_identity
|
|
60
64
|
@disabled_cache = {}.compare_by_identity
|
|
65
|
+
@disabled_names_cache = {}.compare_by_identity
|
|
61
66
|
@warnings = {}
|
|
62
67
|
end
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
# Registers a cop by its badge and constant name without loading the class.
|
|
70
|
+
# The constant is resolved (loading the cop's file through `autoload`) only
|
|
71
|
+
# when the cop class itself is needed.
|
|
72
|
+
#
|
|
73
|
+
# @param badge [Badge, String] the badge, or the qualified cop name
|
|
74
|
+
# @param constant_name [String] the fully qualified constant name
|
|
75
|
+
def lazy_load(badge, constant_name)
|
|
76
|
+
badge = Badge.parse(badge) if badge.is_a?(String)
|
|
77
|
+
|
|
78
|
+
# File any cops enlisted earlier first, so that the registration
|
|
79
|
+
# order follows the call order.
|
|
80
|
+
clear_enrollment_queue
|
|
81
|
+
|
|
66
82
|
@departments << badge.department
|
|
67
|
-
@
|
|
83
|
+
@cops_by_badge[badge] = constant_name unless @cops_by_badge[badge].is_a?(Class)
|
|
68
84
|
end
|
|
69
85
|
|
|
70
86
|
def enlist(cop)
|
|
@@ -72,7 +88,16 @@ module RuboCop
|
|
|
72
88
|
end
|
|
73
89
|
|
|
74
90
|
def dismiss(cop)
|
|
75
|
-
|
|
91
|
+
dismissed = !@enrollment_queue.delete(cop).nil?
|
|
92
|
+
|
|
93
|
+
# A lazy-loaded cop that was not enlisted yet is dismissed by removing its registration.
|
|
94
|
+
# Anonymous cop classes cannot have a badge.
|
|
95
|
+
if !cop.name.nil? && @cops_by_badge[cop.badge].is_a?(String)
|
|
96
|
+
@cops_by_badge.delete(cop.badge)
|
|
97
|
+
dismissed = true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
raise "Cop #{cop} could not be dismissed" unless dismissed
|
|
76
101
|
end
|
|
77
102
|
|
|
78
103
|
# @return [Array<Symbol>] list of departments for current cops.
|
|
@@ -83,12 +108,26 @@ module RuboCop
|
|
|
83
108
|
|
|
84
109
|
# @return [Registry] Cops for that specific department.
|
|
85
110
|
def with_department(department)
|
|
86
|
-
|
|
111
|
+
filter_by_badge { |badge| badge.department == department }
|
|
87
112
|
end
|
|
88
113
|
|
|
89
114
|
# @return [Registry] Cops not for a specific department.
|
|
90
115
|
def without_department(department)
|
|
91
|
-
|
|
116
|
+
filter_by_badge { |badge| badge.department != department }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns a new registry containing the cops whose badge satisfies the given block,
|
|
120
|
+
# keeping lazy-loaded cops unloaded.
|
|
121
|
+
#
|
|
122
|
+
# @api private
|
|
123
|
+
# @return [Registry]
|
|
124
|
+
def filter_by_badge(options = {})
|
|
125
|
+
clear_enrollment_queue
|
|
126
|
+
copy = self.class.new([], options)
|
|
127
|
+
@cops_by_badge.each do |badge, cop_or_name|
|
|
128
|
+
copy.add_entry(badge, cop_or_name) if yield(badge)
|
|
129
|
+
end
|
|
130
|
+
copy
|
|
92
131
|
end
|
|
93
132
|
|
|
94
133
|
# @return [Boolean] Checks if given name is department
|
|
@@ -97,7 +136,7 @@ module RuboCop
|
|
|
97
136
|
end
|
|
98
137
|
|
|
99
138
|
def contains_cop_matching?(names)
|
|
100
|
-
|
|
139
|
+
registered_badges.any? { |badge| badge.match_name?(names) }
|
|
101
140
|
end
|
|
102
141
|
|
|
103
142
|
# Convert a user provided cop name into a properly namespaced name
|
|
@@ -160,9 +199,10 @@ module RuboCop
|
|
|
160
199
|
|
|
161
200
|
def unqualified_cop_names
|
|
162
201
|
clear_enrollment_queue
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
202
|
+
|
|
203
|
+
@unqualified_cop_names ||= @cops_by_badge.keys.to_set do |badge|
|
|
204
|
+
File.basename(badge.to_s)
|
|
205
|
+
end << 'RedundantCopDisableDirective'
|
|
166
206
|
end
|
|
167
207
|
|
|
168
208
|
def qualify_badge(badge)
|
|
@@ -187,51 +227,64 @@ module RuboCop
|
|
|
187
227
|
|
|
188
228
|
def length
|
|
189
229
|
clear_enrollment_queue
|
|
190
|
-
@cops_by_badge.size
|
|
230
|
+
@cops_by_badge.size
|
|
191
231
|
end
|
|
192
232
|
|
|
233
|
+
# Returns the enabled cop classes, loading lazy-loaded cops only when they are enabled.
|
|
234
|
+
#
|
|
235
|
+
# @return [Array<Class>]
|
|
193
236
|
def enabled(config)
|
|
194
|
-
@enabled_cache[config] ||=
|
|
237
|
+
@enabled_cache[config] ||= registered_badges.filter_map do |badge|
|
|
238
|
+
next unless enabled_cop_name?(badge.to_s, config)
|
|
239
|
+
|
|
240
|
+
cop_or_name = @cops_by_badge[badge]
|
|
241
|
+
cop_or_name.is_a?(String) ? load_lazy_cop(badge) : cop_or_name
|
|
242
|
+
end
|
|
195
243
|
end
|
|
196
244
|
|
|
197
245
|
def disabled(config)
|
|
198
246
|
@disabled_cache[config] ||= reject { |cop| enabled?(cop, config) }
|
|
199
247
|
end
|
|
200
248
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if options.fetch(:safe, false)
|
|
211
|
-
cop_enabled && cfg.fetch('Safe', true)
|
|
212
|
-
else
|
|
213
|
-
cop_enabled
|
|
249
|
+
# Returns the names of the disabled cops without loading any of them.
|
|
250
|
+
#
|
|
251
|
+
# @api private
|
|
252
|
+
# @return [Array<String>]
|
|
253
|
+
def disabled_names(config)
|
|
254
|
+
@disabled_names_cache[config] ||= registered_badges.filter_map do |badge|
|
|
255
|
+
name = badge.to_s
|
|
256
|
+
name unless enabled_cop_name?(name, config)
|
|
214
257
|
end
|
|
215
258
|
end
|
|
216
259
|
|
|
217
|
-
def
|
|
260
|
+
def enabled?(cop, config)
|
|
261
|
+
enabled_cop_name?(cop.cop_name, config)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# @param cop [Class, String, nil] the cop class or the qualified cop name
|
|
265
|
+
def enabled_pending_cop?(cop_cfg, config, cop = nil)
|
|
218
266
|
return false if @options[:disable_pending_cops]
|
|
267
|
+
return false unless cop_cfg.fetch('Enabled') == 'pending'
|
|
268
|
+
return true if @options[:enable_pending_cops]
|
|
269
|
+
return config.enabled_new_cops? unless cop
|
|
219
270
|
|
|
220
|
-
|
|
221
|
-
|
|
271
|
+
cop_name = cop.is_a?(String) ? cop : cop.cop_name
|
|
272
|
+
config.enabled_new_cop?(cop_name)
|
|
222
273
|
end
|
|
223
274
|
|
|
224
275
|
def names
|
|
225
276
|
clear_enrollment_queue
|
|
226
|
-
@cops_by_badge.keys.map(&:to_s)
|
|
277
|
+
@cops_by_badge.keys.map(&:to_s)
|
|
227
278
|
end
|
|
228
279
|
|
|
229
280
|
def cops_for_department(department)
|
|
230
|
-
|
|
281
|
+
with_department(department.to_sym).cops
|
|
231
282
|
end
|
|
232
283
|
|
|
233
284
|
def names_for_department(department)
|
|
234
|
-
|
|
285
|
+
department = department.to_sym
|
|
286
|
+
|
|
287
|
+
registered_badges.filter_map { |badge| badge.to_s if badge.department == department }
|
|
235
288
|
end
|
|
236
289
|
|
|
237
290
|
def ==(other)
|
|
@@ -259,7 +312,9 @@ module RuboCop
|
|
|
259
312
|
def find_by_cop_name(cop_name)
|
|
260
313
|
clear_enrollment_queue
|
|
261
314
|
badge = Badge.parse(cop_name)
|
|
262
|
-
@cops_by_badge[badge]
|
|
315
|
+
cop_or_name = @cops_by_badge[badge]
|
|
316
|
+
|
|
317
|
+
cop_or_name.is_a?(String) ? load_lazy_cop(badge) : cop_or_name
|
|
263
318
|
end
|
|
264
319
|
|
|
265
320
|
# When a cop name is given returns a single-element array with the cop class.
|
|
@@ -287,35 +342,90 @@ module RuboCop
|
|
|
287
342
|
@warnings[path]
|
|
288
343
|
end
|
|
289
344
|
|
|
345
|
+
protected
|
|
346
|
+
|
|
347
|
+
# Adds an already-loaded cop class or a lazy-load constant name under the given badge,
|
|
348
|
+
# used to build filtered copies.
|
|
349
|
+
def add_entry(badge, cop_or_name)
|
|
350
|
+
@departments << badge.department
|
|
351
|
+
@cops_by_badge[badge] = cop_or_name
|
|
352
|
+
end
|
|
353
|
+
|
|
290
354
|
private
|
|
291
355
|
|
|
292
356
|
def initialize_copy(reg)
|
|
293
|
-
|
|
357
|
+
super
|
|
358
|
+
@cops_by_badge = @cops_by_badge.dup
|
|
359
|
+
@enrollment_queue = @enrollment_queue.dup
|
|
360
|
+
@departments = @departments.dup
|
|
361
|
+
@enabled_cache = {}.compare_by_identity
|
|
362
|
+
@disabled_cache = {}.compare_by_identity
|
|
363
|
+
@disabled_names_cache = {}.compare_by_identity
|
|
364
|
+
@warnings = {}
|
|
365
|
+
@unqualified_cop_names = nil
|
|
294
366
|
end
|
|
295
367
|
|
|
296
368
|
def clear_enrollment_queue
|
|
297
369
|
return if @enrollment_queue.empty?
|
|
298
370
|
|
|
299
371
|
@enrollment_queue.each do |cop|
|
|
372
|
+
# Reassigning a badge that was registered for lazy loading keeps
|
|
373
|
+
# its original position in the insertion-ordered map.
|
|
300
374
|
@cops_by_badge[cop.badge] = cop
|
|
301
375
|
@departments << cop.department
|
|
302
376
|
end
|
|
303
377
|
@enrollment_queue = []
|
|
304
378
|
end
|
|
305
379
|
|
|
306
|
-
def
|
|
307
|
-
|
|
380
|
+
def registered_badges
|
|
381
|
+
clear_enrollment_queue
|
|
382
|
+
@cops_by_badge.keys
|
|
308
383
|
end
|
|
309
384
|
|
|
310
|
-
def
|
|
311
|
-
|
|
312
|
-
return unless constant_name
|
|
385
|
+
def enabled_cop_name?(cop_name, config)
|
|
386
|
+
return true if options[:only]&.include?(cop_name)
|
|
313
387
|
|
|
314
|
-
|
|
388
|
+
# We need to use the cop name in this case, because `for_cop` uses
|
|
389
|
+
# caching which expects cop names or cop classes as keys.
|
|
390
|
+
cfg = config.for_cop(cop_name)
|
|
391
|
+
|
|
392
|
+
cop_enabled = cfg.fetch('Enabled') == true || enabled_pending_cop?(cfg, config, cop_name)
|
|
393
|
+
|
|
394
|
+
if options.fetch(:safe, false)
|
|
395
|
+
cop_enabled && cfg.fetch('Safe', true)
|
|
396
|
+
else
|
|
397
|
+
cop_enabled
|
|
398
|
+
end
|
|
315
399
|
end
|
|
316
400
|
|
|
317
|
-
def
|
|
318
|
-
|
|
401
|
+
def load_all_lazy_cops
|
|
402
|
+
# Take a snapshot because loading a cop can load (and thereby register)
|
|
403
|
+
# another lazy-loaded cop, e.g. its superclass.
|
|
404
|
+
badges = @cops_by_badge.keys
|
|
405
|
+
badges.each { |badge| load_lazy_cop(badge) if @cops_by_badge[badge].is_a?(String) }
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# @return [Class, nil] the loaded cop class, or nil if the cop excluded
|
|
409
|
+
# itself from the registry while being loaded
|
|
410
|
+
def load_lazy_cop(badge)
|
|
411
|
+
constant_name = @cops_by_badge[badge]
|
|
412
|
+
return constant_name unless constant_name.is_a?(String)
|
|
413
|
+
|
|
414
|
+
cop = Kernel.const_get(constant_name)
|
|
415
|
+
if equal?(self.class.global)
|
|
416
|
+
# Resolving the constant fires the autoload, which makes
|
|
417
|
+
# `Base.inherited` enlist the class into the global registry.
|
|
418
|
+
# Enrolling through the queue keeps `exclude_from_registry` honored:
|
|
419
|
+
# a cop that dismissed itself leaves a dangling constant name behind
|
|
420
|
+
# and is removed from the registry.
|
|
421
|
+
clear_enrollment_queue
|
|
422
|
+
cop_or_name = @cops_by_badge[badge]
|
|
423
|
+
@cops_by_badge.delete(badge) if cop_or_name.is_a?(String)
|
|
424
|
+
cop_or_name.is_a?(Class) ? cop_or_name : nil
|
|
425
|
+
else
|
|
426
|
+
# `Base.inherited` enlists into the global registry, not this copy.
|
|
427
|
+
@cops_by_badge[badge] = cop
|
|
428
|
+
end
|
|
319
429
|
end
|
|
320
430
|
|
|
321
431
|
def resolve_badge(given_badge, real_badge, source_path, warn: true)
|
|
@@ -335,7 +445,7 @@ module RuboCop
|
|
|
335
445
|
|
|
336
446
|
def registered?(badge)
|
|
337
447
|
clear_enrollment_queue
|
|
338
|
-
@cops_by_badge.key?(badge)
|
|
448
|
+
@cops_by_badge.key?(badge)
|
|
339
449
|
end
|
|
340
450
|
end
|
|
341
451
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
# Cops for the `Security` department. The department's cops are registered for lazy loading
|
|
6
|
+
# and their files are loaded on demand.
|
|
7
|
+
module Security
|
|
8
|
+
extend LazyLoader
|
|
9
|
+
|
|
10
|
+
register_cop :CompoundHash, "#{__dir__}/security/compound_hash"
|
|
11
|
+
register_cop :Eval, "#{__dir__}/security/eval"
|
|
12
|
+
register_cop :IoMethods, "#{__dir__}/security/io_methods"
|
|
13
|
+
register_cop :JSONLoad, "#{__dir__}/security/json_load"
|
|
14
|
+
register_cop :MarshalLoad, "#{__dir__}/security/marshal_load"
|
|
15
|
+
register_cop :Open, "#{__dir__}/security/open"
|
|
16
|
+
register_cop :YAMLLoad, "#{__dir__}/security/yaml_load"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -30,6 +30,11 @@ module RuboCop
|
|
|
30
30
|
# only cases where exactly one argument is provided can be replaced with
|
|
31
31
|
# `Array#intersect?` and are handled by this cop.
|
|
32
32
|
#
|
|
33
|
+
# NOTE: In the block form, `include?` is only detected when its receiver is
|
|
34
|
+
# an array literal, because `include?` is defined with different semantics
|
|
35
|
+
# on many non-array classes (e.g. `String#include?` checks for substrings).
|
|
36
|
+
# `member?` does not have this restriction.
|
|
37
|
+
#
|
|
33
38
|
# @safety
|
|
34
39
|
# This cop cannot guarantee that `array1` and `array2` are
|
|
35
40
|
# actually arrays while method `intersect?` is for arrays only.
|
|
@@ -47,7 +52,7 @@ module RuboCop
|
|
|
47
52
|
#
|
|
48
53
|
# # bad
|
|
49
54
|
# array1.any? { |elem| array2.member?(elem) }
|
|
50
|
-
# array1.none? { |elem|
|
|
55
|
+
# array1.none? { |elem| [1, 2].include?(elem) }
|
|
51
56
|
#
|
|
52
57
|
# # good
|
|
53
58
|
# array1.intersect?(array2)
|
|
@@ -121,15 +126,15 @@ module RuboCop
|
|
|
121
126
|
(block
|
|
122
127
|
(call $_receiver ${:any? :none?})
|
|
123
128
|
(args (arg _key))
|
|
124
|
-
(send $
|
|
129
|
+
(send $!nil? ${:member? :include?} (lvar _key))
|
|
125
130
|
)
|
|
126
131
|
(numblock
|
|
127
132
|
(call $_receiver ${:any? :none?}) 1
|
|
128
|
-
(send $
|
|
133
|
+
(send $!nil? ${:member? :include?} (lvar :_1))
|
|
129
134
|
)
|
|
130
135
|
(itblock
|
|
131
136
|
(call $_receiver ${:any? :none?}) :it
|
|
132
|
-
(send $
|
|
137
|
+
(send $!nil? ${:member? :include?} (lvar :it))
|
|
133
138
|
)
|
|
134
139
|
}
|
|
135
140
|
PATTERN
|
|
@@ -156,7 +161,10 @@ module RuboCop
|
|
|
156
161
|
alias on_csend on_send
|
|
157
162
|
|
|
158
163
|
def on_block(node)
|
|
159
|
-
return unless (
|
|
164
|
+
return unless (captures = any_none_block_intersection(node))
|
|
165
|
+
|
|
166
|
+
receiver, method_name, argument, block_method = captures
|
|
167
|
+
return if uncorrectable_block_intersection?(node, method_name, argument, block_method)
|
|
160
168
|
|
|
161
169
|
dot = node.send_node.loc.dot.source
|
|
162
170
|
bang = method_name == :any? ? '' : '!'
|
|
@@ -186,6 +194,17 @@ module RuboCop
|
|
|
186
194
|
STRAIGHT_METHODS.include?(method_name.to_sym)
|
|
187
195
|
end
|
|
188
196
|
|
|
197
|
+
def uncorrectable_block_intersection?(node, method_name, argument, block_method)
|
|
198
|
+
# `a&.none? { |elem| b.include?(elem) }` returns `nil` when `a` is `nil`,
|
|
199
|
+
# but the negated rewrite `!a&.intersect?(b)` returns `true` there, flipping the result.
|
|
200
|
+
return true if method_name == :none? && node.send_node.safe_navigation?
|
|
201
|
+
|
|
202
|
+
# `include?` is defined with different semantics on many non-array classes
|
|
203
|
+
# (e.g. `String#include?` checks for substrings), so it is only a reliable
|
|
204
|
+
# signal of an intersection check when the receiver is an array literal.
|
|
205
|
+
block_method == :include? && !argument.array_type?
|
|
206
|
+
end
|
|
207
|
+
|
|
189
208
|
def register_offense(node, replacement)
|
|
190
209
|
message = format(MSG, replacement: replacement, existing: node.source)
|
|
191
210
|
|
|
@@ -23,10 +23,15 @@ module RuboCop
|
|
|
23
23
|
#
|
|
24
24
|
# Moving from `compact` to `nested` children requires knowledge of whether the
|
|
25
25
|
# outer parent is a module or a class. Moving from `nested` to `compact` requires
|
|
26
|
-
# verification that the outer parent is defined elsewhere. RuboCop does
|
|
27
|
-
# have the knowledge to perform either operation safely and thus requires
|
|
26
|
+
# verification that the outer parent is defined elsewhere. By default RuboCop does
|
|
27
|
+
# not have the knowledge to perform either operation safely and thus requires
|
|
28
28
|
# manual oversight.
|
|
29
29
|
#
|
|
30
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed,
|
|
31
|
+
# the project-wide index is consulted to resolve whether the outer parent is a
|
|
32
|
+
# class or a module, and compacting is skipped when the outer parent is not
|
|
33
|
+
# defined elsewhere.
|
|
34
|
+
#
|
|
30
35
|
# @example EnforcedStyle: nested (default)
|
|
31
36
|
# # bad
|
|
32
37
|
# class Foo::Bar
|
|
@@ -53,6 +58,7 @@ module RuboCop
|
|
|
53
58
|
class ClassAndModuleChildren < Base
|
|
54
59
|
include Alignment
|
|
55
60
|
include ConfigurableEnforcedStyle
|
|
61
|
+
include ProjectIndexHelp
|
|
56
62
|
include RangeHelp
|
|
57
63
|
extend AutoCorrector
|
|
58
64
|
|
|
@@ -60,7 +66,7 @@ module RuboCop
|
|
|
60
66
|
COMPACT_MSG = 'Use compact module/class definition instead of nested style.'
|
|
61
67
|
|
|
62
68
|
def on_class(node)
|
|
63
|
-
return if node.parent_class &&
|
|
69
|
+
return if node.parent_class && style_for_classes != :nested
|
|
64
70
|
|
|
65
71
|
check_style(node, node.body, style_for_classes)
|
|
66
72
|
end
|
|
@@ -72,7 +78,7 @@ module RuboCop
|
|
|
72
78
|
private
|
|
73
79
|
|
|
74
80
|
def nest_or_compact(corrector, node)
|
|
75
|
-
style = node.
|
|
81
|
+
style = style_for_kind(node.type)
|
|
76
82
|
|
|
77
83
|
if style == :nested
|
|
78
84
|
nest_definition(corrector, node)
|
|
@@ -82,21 +88,40 @@ module RuboCop
|
|
|
82
88
|
end
|
|
83
89
|
|
|
84
90
|
def nest_definition(corrector, node)
|
|
91
|
+
keyword = namespace_keyword(node)
|
|
92
|
+
# A namespace wrapper whose style resolves to `compact` would itself violate
|
|
93
|
+
# that style, making autocorrection ping-pong between the two forms.
|
|
94
|
+
return if style_for_kind(keyword.to_sym) == :compact
|
|
95
|
+
|
|
85
96
|
padding = indentation(node) + leading_spaces(node)
|
|
86
97
|
padding_for_trailing_end = padding.sub(' ' * node.loc.end.column, '')
|
|
87
98
|
|
|
88
|
-
|
|
99
|
+
corrector.replace(node.loc.keyword, keyword)
|
|
89
100
|
split_on_double_colon(corrector, node, padding)
|
|
90
101
|
add_trailing_end(corrector, node, padding_for_trailing_end)
|
|
91
102
|
end
|
|
92
103
|
|
|
93
|
-
def
|
|
104
|
+
def namespace_keyword(node)
|
|
105
|
+
indexed_namespace_keyword(node) || heuristic_namespace_keyword(node)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def indexed_namespace_keyword(node)
|
|
109
|
+
return nil unless project_index
|
|
110
|
+
|
|
111
|
+
declaration = resolve_constant_in_index(node.identifier.namespace)
|
|
112
|
+
|
|
113
|
+
case declaration
|
|
114
|
+
when Rubydex::Class then 'class'
|
|
115
|
+
when Rubydex::Module then 'module'
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def heuristic_namespace_keyword(node)
|
|
94
120
|
class_definition = node.left_sibling&.each_node(:class)&.find do |class_node|
|
|
95
121
|
class_node.identifier == node.identifier.namespace
|
|
96
122
|
end
|
|
97
|
-
namespace_keyword = class_definition ? 'class' : 'module'
|
|
98
123
|
|
|
99
|
-
|
|
124
|
+
class_definition ? 'class' : 'module'
|
|
100
125
|
end
|
|
101
126
|
|
|
102
127
|
def split_on_double_colon(corrector, node, padding)
|
|
@@ -114,11 +139,41 @@ module RuboCop
|
|
|
114
139
|
end
|
|
115
140
|
|
|
116
141
|
def compact_definition(corrector, node)
|
|
142
|
+
# Compacting produces a definition whose type's style resolves to `nested`,
|
|
143
|
+
# making autocorrection ping-pong between the two forms.
|
|
144
|
+
return if style_for_kind(node.body.type) == :nested
|
|
145
|
+
return unless compactible_namespace?(node)
|
|
146
|
+
|
|
117
147
|
compact_node(corrector, node)
|
|
118
148
|
remove_end(corrector, node.body)
|
|
119
149
|
unindent(corrector, node)
|
|
120
150
|
end
|
|
121
151
|
|
|
152
|
+
# Compacting removes this definition of the namespace, so the result raises
|
|
153
|
+
# `NameError` at load time unless the namespace is also defined somewhere else.
|
|
154
|
+
# With the project index this is verified before correcting; without it the
|
|
155
|
+
# correction is performed regardless (the cop's autocorrection is unsafe).
|
|
156
|
+
def compactible_namespace?(node)
|
|
157
|
+
return true unless project_index
|
|
158
|
+
|
|
159
|
+
declaration = resolve_constant_in_index(node.identifier)
|
|
160
|
+
return false unless declaration.is_a?(Rubydex::Namespace)
|
|
161
|
+
|
|
162
|
+
declaration.definitions.any? { |definition| definition_elsewhere?(definition, node) }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def definition_elsewhere?(definition, node)
|
|
166
|
+
location = definition.location
|
|
167
|
+
return true unless location.uri.start_with?(FILE_URI_PREFIX)
|
|
168
|
+
|
|
169
|
+
!same_file?(location.to_file_path, processed_source.file_path) ||
|
|
170
|
+
location.to_display.start_line != node.first_line
|
|
171
|
+
rescue StandardError
|
|
172
|
+
# A path that cannot be converted or compared cannot prove the
|
|
173
|
+
# namespace is defined elsewhere; err on not correcting.
|
|
174
|
+
false
|
|
175
|
+
end
|
|
176
|
+
|
|
122
177
|
def compact_node(corrector, node)
|
|
123
178
|
range = range_between(node.loc.keyword.begin_pos, node.body.loc.name.end_pos)
|
|
124
179
|
corrector.replace(range, compact_replacement(node))
|
|
@@ -162,23 +217,24 @@ module RuboCop
|
|
|
162
217
|
column_delta = configured_indentation_width - spaces_size(last_child_leading_spaces)
|
|
163
218
|
return if column_delta.zero?
|
|
164
219
|
|
|
165
|
-
AlignmentCorrector.correct(
|
|
220
|
+
AlignmentCorrector.correct(
|
|
221
|
+
corrector, processed_source, node, column_delta, tab_indentation: true
|
|
222
|
+
)
|
|
166
223
|
end
|
|
167
224
|
|
|
168
225
|
def leading_spaces(node)
|
|
169
226
|
node.source_range.source_line[/\A\s*/]
|
|
170
227
|
end
|
|
171
228
|
|
|
229
|
+
# A tab counts as `configured_indentation_width` columns, matching the
|
|
230
|
+
# width `AlignmentCorrector` uses to convert `column_delta` back into
|
|
231
|
+
# tabs. Using `Layout/IndentationStyle`'s own `IndentationWidth` here
|
|
232
|
+
# would make the delta and the correction disagree.
|
|
172
233
|
def spaces_size(spaces_string)
|
|
173
|
-
mapping = { "\t" =>
|
|
234
|
+
mapping = { "\t" => configured_indentation_width }
|
|
174
235
|
spaces_string.chars.sum { |character| mapping.fetch(character, 1) }
|
|
175
236
|
end
|
|
176
237
|
|
|
177
|
-
def tab_indentation_width
|
|
178
|
-
config.for_cop('Layout/IndentationStyle')['IndentationWidth'] ||
|
|
179
|
-
configured_indentation_width
|
|
180
|
-
end
|
|
181
|
-
|
|
182
238
|
def check_style(node, body, style)
|
|
183
239
|
return if node.identifier.namespace&.cbase_type?
|
|
184
240
|
return unless const_namespace?(node.identifier.namespace)
|
|
@@ -218,7 +274,7 @@ module RuboCop
|
|
|
218
274
|
end
|
|
219
275
|
|
|
220
276
|
def autocorrect(corrector, node)
|
|
221
|
-
return if node.class_type? && node.parent_class &&
|
|
277
|
+
return if node.class_type? && node.parent_class && style_for_classes != :nested
|
|
222
278
|
|
|
223
279
|
nest_or_compact(corrector, node)
|
|
224
280
|
end
|
|
@@ -231,12 +287,16 @@ module RuboCop
|
|
|
231
287
|
node.identifier.source.include?('::')
|
|
232
288
|
end
|
|
233
289
|
|
|
290
|
+
def style_for_kind(kind)
|
|
291
|
+
kind == :class ? style_for_classes : style_for_modules
|
|
292
|
+
end
|
|
293
|
+
|
|
234
294
|
def style_for_classes
|
|
235
|
-
cop_config['EnforcedStyleForClasses'] || style
|
|
295
|
+
cop_config['EnforcedStyleForClasses']&.to_sym || style
|
|
236
296
|
end
|
|
237
297
|
|
|
238
298
|
def style_for_modules
|
|
239
|
-
cop_config['EnforcedStyleForModules'] || style
|
|
299
|
+
cop_config['EnforcedStyleForModules']&.to_sym || style
|
|
240
300
|
end
|
|
241
301
|
end
|
|
242
302
|
end
|
|
@@ -203,6 +203,7 @@ module RuboCop
|
|
|
203
203
|
class ConditionalAssignment < Base
|
|
204
204
|
include ConditionalAssignmentHelper
|
|
205
205
|
include ConfigurableEnforcedStyle
|
|
206
|
+
include ReparsedEquivalence
|
|
206
207
|
extend AutoCorrector
|
|
207
208
|
|
|
208
209
|
MSG = 'Use the return of the conditional for variable assignment and comparison.'
|
|
@@ -288,6 +289,7 @@ module RuboCop
|
|
|
288
289
|
# else slot, but `else_branch` correctly reports there is no `else` clause.
|
|
289
290
|
return unless assignment.else_branch
|
|
290
291
|
return if allowed_single_line?([*branches, else_branch])
|
|
292
|
+
return unless safe_to_correct?(node)
|
|
291
293
|
|
|
292
294
|
add_offense(node, message: ASSIGN_TO_CONDITION_MSG) do |corrector|
|
|
293
295
|
autocorrect(corrector, node)
|
|
@@ -367,6 +369,7 @@ module RuboCop
|
|
|
367
369
|
return unless allowed_statements?(branches)
|
|
368
370
|
return if allowed_single_line?(branches)
|
|
369
371
|
return if correction_exceeds_line_limit?(node, branches)
|
|
372
|
+
return unless safe_to_correct?(node)
|
|
370
373
|
|
|
371
374
|
add_offense(node) { |corrector| autocorrect(corrector, node) }
|
|
372
375
|
end
|
|
@@ -379,6 +382,25 @@ module RuboCop
|
|
|
379
382
|
end
|
|
380
383
|
end
|
|
381
384
|
|
|
385
|
+
# The correction rewrites the conditional structurally, so full AST
|
|
386
|
+
# equivalence cannot be asserted, but the result must at least reparse
|
|
387
|
+
# to valid Ruby. Applying the correction to a throwaway buffer here
|
|
388
|
+
# also exercises the branch-shape handling before an offense is even
|
|
389
|
+
# reported, so unusual branches that the corrector cannot handle are
|
|
390
|
+
# left alone instead of crashing or emitting invalid code - the bug
|
|
391
|
+
# class behind most of this cop's history.
|
|
392
|
+
def safe_to_correct?(node)
|
|
393
|
+
correction_parses?(node)
|
|
394
|
+
rescue StandardError
|
|
395
|
+
false
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# `ReparsedEquivalence#correction_parses?` contract: apply the exact
|
|
399
|
+
# correction this cop would perform.
|
|
400
|
+
def apply_reparse_correction(corrector, node)
|
|
401
|
+
autocorrect(corrector, node)
|
|
402
|
+
end
|
|
403
|
+
|
|
382
404
|
def allowed_statements?(branches)
|
|
383
405
|
return false unless branches.all?
|
|
384
406
|
|
|
@@ -25,20 +25,25 @@ module RuboCop
|
|
|
25
25
|
MSG = 'Include a copyright notice matching /%<notice>s/ before any code.'
|
|
26
26
|
AUTOCORRECT_EMPTY_WARNING = 'An AutocorrectNotice must be defined in your RuboCop config'
|
|
27
27
|
|
|
28
|
+
# rubocop:disable Metrics/AbcSize
|
|
28
29
|
def on_new_investigation
|
|
29
30
|
return if notice.empty? || notice_found?(processed_source)
|
|
30
31
|
|
|
31
|
-
verify_autocorrect_notice!
|
|
32
32
|
message = format(MSG, notice: notice)
|
|
33
33
|
if processed_source.blank?
|
|
34
34
|
add_global_offense(message)
|
|
35
35
|
else
|
|
36
36
|
offense_range = source_range(processed_source.buffer, 1, 0)
|
|
37
37
|
add_offense(offense_range, message: message) do |corrector|
|
|
38
|
+
next unless autocorrect?
|
|
39
|
+
|
|
40
|
+
verify_autocorrect_notice!
|
|
41
|
+
|
|
38
42
|
autocorrect(corrector)
|
|
39
43
|
end
|
|
40
44
|
end
|
|
41
45
|
end
|
|
46
|
+
# rubocop:enable Metrics/AbcSize
|
|
42
47
|
|
|
43
48
|
private
|
|
44
49
|
|