rubocop 1.66.1 → 1.68.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/README.md +1 -1
- data/config/default.yml +55 -6
- data/config/internal_affairs.yml +11 -0
- data/lib/rubocop/cached_data.rb +12 -4
- data/lib/rubocop/cli/command/auto_generate_config.rb +6 -7
- data/lib/rubocop/cli/command/execute_runner.rb +1 -1
- data/lib/rubocop/cli/command/lsp.rb +2 -2
- data/lib/rubocop/cli/command/version.rb +2 -2
- data/lib/rubocop/config_loader_resolver.rb +3 -3
- data/lib/rubocop/config_validator.rb +2 -1
- data/lib/rubocop/cop/autocorrect_logic.rb +22 -2
- data/lib/rubocop/cop/base.rb +6 -2
- data/lib/rubocop/cop/bundler/gem_version.rb +1 -0
- data/lib/rubocop/cop/cop.rb +8 -0
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -12
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +10 -0
- data/lib/rubocop/cop/internal_affairs/cop_description.rb +0 -4
- data/lib/rubocop/cop/internal_affairs/redundant_message_argument.rb +6 -21
- data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +8 -1
- data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +0 -5
- data/lib/rubocop/cop/internal_affairs.rb +16 -0
- data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
- data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
- data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +8 -0
- data/lib/rubocop/cop/layout/indentation_width.rb +4 -5
- data/lib/rubocop/cop/layout/leading_comment_space.rb +56 -1
- data/lib/rubocop/cop/layout/space_before_brackets.rb +5 -5
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +4 -0
- data/lib/rubocop/cop/lint/ambiguous_range.rb +4 -1
- data/lib/rubocop/cop/lint/big_decimal_new.rb +4 -7
- data/lib/rubocop/cop/lint/boolean_symbol.rb +1 -1
- data/lib/rubocop/cop/lint/duplicate_branch.rb +39 -4
- data/lib/rubocop/cop/lint/duplicate_set_element.rb +74 -0
- data/lib/rubocop/cop/lint/ensure_return.rb +0 -3
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
- data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +10 -4
- data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +5 -14
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +25 -2
- data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +7 -0
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +5 -6
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -1
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +9 -0
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +107 -41
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +88 -0
- data/lib/rubocop/cop/lint/uri_regexp.rb +25 -7
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +4 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +10 -0
- data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +24 -0
- data/lib/rubocop/cop/mixin/frozen_string_literal.rb +3 -1
- data/lib/rubocop/cop/mixin/percent_array.rb +1 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +3 -2
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/naming/inclusive_language.rb +12 -3
- data/lib/rubocop/cop/naming/predicate_name.rb +1 -1
- data/lib/rubocop/cop/offense.rb +4 -5
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +12 -2
- data/lib/rubocop/cop/style/accessor_grouping.rb +10 -2
- data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +79 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +46 -6
- data/lib/rubocop/cop/style/bitwise_predicate.rb +100 -0
- data/lib/rubocop/cop/style/block_delimiters.rb +31 -3
- data/lib/rubocop/cop/style/collection_compact.rb +10 -10
- data/lib/rubocop/cop/style/combinable_defined.rb +115 -0
- data/lib/rubocop/cop/style/combinable_loops.rb +7 -0
- data/lib/rubocop/cop/style/commented_keyword.rb +7 -1
- data/lib/rubocop/cop/style/conditional_assignment.rb +1 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +1 -1
- data/lib/rubocop/cop/style/endless_method.rb +1 -14
- data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
- data/lib/rubocop/cop/style/guard_clause.rb +15 -2
- data/lib/rubocop/cop/style/hash_each_methods.rb +6 -0
- data/lib/rubocop/cop/style/hash_syntax.rb +2 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +1 -1
- data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -3
- data/lib/rubocop/cop/style/keyword_arguments_merging.rb +67 -0
- data/lib/rubocop/cop/style/lambda.rb +1 -1
- data/lib/rubocop/cop/style/map_into_array.rb +59 -8
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +12 -7
- data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +28 -39
- data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +4 -0
- data/lib/rubocop/cop/style/operator_method_call.rb +25 -6
- data/lib/rubocop/cop/style/redundant_begin.rb +4 -0
- data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +23 -5
- data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -11
- data/lib/rubocop/cop/style/require_order.rb +1 -1
- data/lib/rubocop/cop/style/rescue_modifier.rb +13 -1
- data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +54 -12
- data/lib/rubocop/cop/style/safe_navigation.rb +104 -50
- data/lib/rubocop/cop/style/safe_navigation_chain_length.rb +52 -0
- data/lib/rubocop/cop/style/select_by_regexp.rb +9 -6
- data/lib/rubocop/cop/style/semicolon.rb +1 -1
- data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +26 -5
- data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
- data/lib/rubocop/cop/team.rb +8 -1
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/cop/variable_force/assignment.rb +18 -3
- data/lib/rubocop/cop/variable_force/branch.rb +1 -1
- data/lib/rubocop/cop/variable_force/variable.rb +5 -1
- data/lib/rubocop/cop/variable_force/variable_table.rb +2 -2
- data/lib/rubocop/cops_documentation_generator.rb +81 -40
- data/lib/rubocop/file_finder.rb +9 -4
- data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -1
- data/lib/rubocop/lsp/runtime.rb +2 -0
- data/lib/rubocop/lsp/server.rb +0 -1
- data/lib/rubocop/rspec/expect_offense.rb +1 -0
- data/lib/rubocop/runner.rb +17 -6
- data/lib/rubocop/server/cache.rb +6 -1
- data/lib/rubocop/server/core.rb +1 -0
- data/lib/rubocop/target_ruby.rb +13 -13
- data/lib/rubocop/version.rb +28 -7
- data/lib/rubocop/yaml_duplication_checker.rb +20 -27
- data/lib/rubocop.rb +10 -0
- metadata +13 -4
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Looks for endless methods inside operations of lower precedence (`and`, `or`, and
|
7
|
+
# modifier forms of `if`, `unless`, `while`, `until`) that are ambiguous due to
|
8
|
+
# lack of parentheses. This may lead to unexpected behavior as the code may appear
|
9
|
+
# to use these keywords as part of the method but in fact they modify
|
10
|
+
# the method definition itself.
|
11
|
+
#
|
12
|
+
# In these cases, using a normal method definition is more clear.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
# def foo = true if bar
|
18
|
+
#
|
19
|
+
# # good - using a non-endless method is more explicit
|
20
|
+
# def foo
|
21
|
+
# true
|
22
|
+
# end if bar
|
23
|
+
#
|
24
|
+
# # ok - method body is explicit
|
25
|
+
# def foo = (true if bar)
|
26
|
+
#
|
27
|
+
# # ok - method definition is explicit
|
28
|
+
# (def foo = true) if bar
|
29
|
+
class AmbiguousEndlessMethodDefinition < Base
|
30
|
+
extend TargetRubyVersion
|
31
|
+
extend AutoCorrector
|
32
|
+
include EndlessMethodRewriter
|
33
|
+
include RangeHelp
|
34
|
+
|
35
|
+
minimum_target_ruby_version 3.0
|
36
|
+
|
37
|
+
MSG = 'Avoid using `%<keyword>s` statements with endless methods.'
|
38
|
+
|
39
|
+
# @!method ambiguous_endless_method_body(node)
|
40
|
+
def_node_matcher :ambiguous_endless_method_body, <<~PATTERN
|
41
|
+
^${
|
42
|
+
(if _ <def _>)
|
43
|
+
({and or} def _)
|
44
|
+
({while until} _ def)
|
45
|
+
}
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
def on_def(node)
|
49
|
+
return unless node.endless?
|
50
|
+
|
51
|
+
operation = ambiguous_endless_method_body(node)
|
52
|
+
return unless operation
|
53
|
+
|
54
|
+
return unless modifier_form?(operation)
|
55
|
+
|
56
|
+
add_offense(operation, message: format(MSG, keyword: keyword(operation))) do |corrector|
|
57
|
+
correct_to_multiline(corrector, node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def modifier_form?(operation)
|
64
|
+
return true if operation.and_type? || operation.or_type?
|
65
|
+
|
66
|
+
operation.modifier_form?
|
67
|
+
end
|
68
|
+
|
69
|
+
def keyword(operation)
|
70
|
+
if operation.respond_to?(:keyword)
|
71
|
+
operation.keyword
|
72
|
+
else
|
73
|
+
operation.operator
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -180,7 +180,8 @@ module RuboCop
|
|
180
180
|
end
|
181
181
|
|
182
182
|
def only_forwards_all?(send_classifications)
|
183
|
-
|
183
|
+
all_classifications = %i[all all_anonymous].freeze
|
184
|
+
send_classifications.all? { |_, c, _, _| all_classifications.include?(c) }
|
184
185
|
end
|
185
186
|
|
186
187
|
# rubocop:disable Metrics/MethodLength
|
@@ -188,8 +189,8 @@ module RuboCop
|
|
188
189
|
_rest_arg, _kwrest_arg, block_arg = *forwardable_args
|
189
190
|
registered_block_arg_offense = false
|
190
191
|
|
191
|
-
send_classifications.each do |send_node,
|
192
|
-
if !forward_rest && !forward_kwrest
|
192
|
+
send_classifications.each do |send_node, c, forward_rest, forward_kwrest, forward_block_arg| # rubocop:disable Layout/LineLength
|
193
|
+
if !forward_rest && !forward_kwrest && c != :all_anonymous
|
193
194
|
# Prevents `anonymous block parameter is also used within block (SyntaxError)` occurs
|
194
195
|
# in Ruby 3.3.0.
|
195
196
|
if outside_block?(forward_block_arg)
|
@@ -213,6 +214,7 @@ module RuboCop
|
|
213
214
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
214
215
|
def add_post_ruby_32_offenses(def_node, send_classifications, forwardable_args)
|
215
216
|
return unless use_anonymous_forwarding?
|
217
|
+
return if send_inside_block?(send_classifications)
|
216
218
|
|
217
219
|
rest_arg, kwrest_arg, block_arg = *forwardable_args
|
218
220
|
|
@@ -355,8 +357,15 @@ module RuboCop
|
|
355
357
|
cop_config.fetch('UseAnonymousForwarding', false)
|
356
358
|
end
|
357
359
|
|
360
|
+
def send_inside_block?(send_classifications)
|
361
|
+
send_classifications.any? do |send_node, *|
|
362
|
+
send_node.each_ancestor(:block, :numblock).any?
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
358
366
|
def add_parens_if_missing(node, corrector)
|
359
367
|
return if parentheses?(node)
|
368
|
+
return if node.send_type? && node.method?(:[])
|
360
369
|
|
361
370
|
add_parentheses(node, corrector)
|
362
371
|
end
|
@@ -374,6 +383,23 @@ module RuboCop
|
|
374
383
|
# @!method forwarded_block_arg?(node, block_name)
|
375
384
|
def_node_matcher :forwarded_block_arg?, '(block_pass {(lvar %1) nil?})'
|
376
385
|
|
386
|
+
# @!method def_all_anonymous_args?(node)
|
387
|
+
def_node_matcher :def_all_anonymous_args?, <<~PATTERN
|
388
|
+
(
|
389
|
+
def _
|
390
|
+
(args ... (restarg) (kwrestarg) (blockarg nil?))
|
391
|
+
_
|
392
|
+
)
|
393
|
+
PATTERN
|
394
|
+
|
395
|
+
# @!method send_all_anonymous_args?(node)
|
396
|
+
def_node_matcher :send_all_anonymous_args?, <<~PATTERN
|
397
|
+
(
|
398
|
+
send _ _
|
399
|
+
... (forwarded_restarg) (hash (forwarded_kwrestarg)) (block_pass nil?)
|
400
|
+
)
|
401
|
+
PATTERN
|
402
|
+
|
377
403
|
def initialize(def_node, send_node, referenced_lvars, forwardable_args, **config)
|
378
404
|
@def_node = def_node
|
379
405
|
@send_node = send_node
|
@@ -405,7 +431,9 @@ module RuboCop
|
|
405
431
|
def classification
|
406
432
|
return nil unless forwarded_rest_arg || forwarded_kwrest_arg || forwarded_block_arg
|
407
433
|
|
408
|
-
if
|
434
|
+
if ruby_32_only_anonymous_forwarding?
|
435
|
+
:all_anonymous
|
436
|
+
elsif can_forward_all?
|
409
437
|
:all
|
410
438
|
else
|
411
439
|
:rest_or_kwrest
|
@@ -414,16 +442,28 @@ module RuboCop
|
|
414
442
|
|
415
443
|
private
|
416
444
|
|
445
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
417
446
|
def can_forward_all?
|
418
447
|
return false if any_arg_referenced?
|
419
|
-
return false if
|
448
|
+
return false if ruby_30_or_lower_optarg?
|
449
|
+
return false if ruby_32_or_higher_missing_rest_or_kwest?
|
420
450
|
return false unless offensive_block_forwarding?
|
421
451
|
return false if additional_kwargs_or_forwarded_kwargs?
|
422
452
|
|
423
453
|
no_additional_args? || (target_ruby_version >= 3.0 && no_post_splat_args?)
|
424
454
|
end
|
455
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
456
|
+
|
457
|
+
# def foo(a = 41, ...) is a syntax error in 3.0.
|
458
|
+
def ruby_30_or_lower_optarg?
|
459
|
+
target_ruby_version <= 3.0 && @def_node.arguments.any?(&:optarg_type?)
|
460
|
+
end
|
461
|
+
|
462
|
+
def ruby_32_only_anonymous_forwarding?
|
463
|
+
def_all_anonymous_args?(@def_node) && send_all_anonymous_args?(@send_node)
|
464
|
+
end
|
425
465
|
|
426
|
-
def
|
466
|
+
def ruby_32_or_higher_missing_rest_or_kwest?
|
427
467
|
target_ruby_version >= 3.2 && !forwarded_rest_and_kwrest_args
|
428
468
|
end
|
429
469
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Prefer bitwise predicate methods over direct comparison operations.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# This cop is unsafe, as it can produce false positives if the receiver
|
10
|
+
# is not an `Integer` object.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# # bad - checks any set bits
|
15
|
+
# (variable & flags).positive?
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# variable.anybits?(flags)
|
19
|
+
#
|
20
|
+
# # bad - checks all set bits
|
21
|
+
# (variable & flags) == flags
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# variable.allbits?(flags)
|
25
|
+
#
|
26
|
+
# # bad - checks no set bits
|
27
|
+
# (variable & flags).zero?
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# variable.nobits?(flags)
|
31
|
+
#
|
32
|
+
class BitwisePredicate < Base
|
33
|
+
extend AutoCorrector
|
34
|
+
extend TargetRubyVersion
|
35
|
+
|
36
|
+
MSG = 'Replace with `%<preferred>s` for comparison with bit flags.'
|
37
|
+
RESTRICT_ON_SEND = %i[!= == > >= positive? zero?].freeze
|
38
|
+
|
39
|
+
minimum_target_ruby_version 2.5
|
40
|
+
|
41
|
+
# @!method anybits?(node)
|
42
|
+
def_node_matcher :anybits?, <<~PATTERN
|
43
|
+
{
|
44
|
+
(send #bit_operation? :positive?)
|
45
|
+
(send #bit_operation? :> (int 0))
|
46
|
+
(send #bit_operation? :>= (int 1))
|
47
|
+
(send #bit_operation? :!= (int 0))
|
48
|
+
}
|
49
|
+
PATTERN
|
50
|
+
|
51
|
+
# @!method allbits?(node)
|
52
|
+
def_node_matcher :allbits?, <<~PATTERN
|
53
|
+
{
|
54
|
+
(send (begin (send _ :& _flags)) :== _flags)
|
55
|
+
(send (begin (send _flags :& _)) :== _flags)
|
56
|
+
}
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
# @!method nobits?(node)
|
60
|
+
def_node_matcher :nobits?, <<~PATTERN
|
61
|
+
{
|
62
|
+
(send #bit_operation? :zero?)
|
63
|
+
(send #bit_operation? :== (int 0))
|
64
|
+
}
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method bit_operation?(node)
|
68
|
+
def_node_matcher :bit_operation?, <<~PATTERN
|
69
|
+
(begin
|
70
|
+
(send _ :& _))
|
71
|
+
PATTERN
|
72
|
+
|
73
|
+
def on_send(node)
|
74
|
+
return unless node.receiver.begin_type?
|
75
|
+
return unless (preferred_method = preferred_method(node))
|
76
|
+
|
77
|
+
bit_operation = node.receiver.children.first
|
78
|
+
lhs, _operator, rhs = *bit_operation
|
79
|
+
preferred = "#{lhs.source}.#{preferred_method}(#{rhs.source})"
|
80
|
+
|
81
|
+
add_offense(node, message: format(MSG, preferred: preferred)) do |corrector|
|
82
|
+
corrector.replace(node, preferred)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def preferred_method(node)
|
89
|
+
if anybits?(node)
|
90
|
+
'anybits?'
|
91
|
+
elsif allbits?(node)
|
92
|
+
'allbits?'
|
93
|
+
elsif nobits?(node)
|
94
|
+
'nobits?'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -176,6 +176,10 @@ module RuboCop
|
|
176
176
|
|
177
177
|
BRACES_REQUIRED_MESSAGE = "Brace delimiters `{...}` required for '%<method_name>s' method."
|
178
178
|
|
179
|
+
def self.autocorrect_incompatible_with
|
180
|
+
[Style::RedundantBegin]
|
181
|
+
end
|
182
|
+
|
179
183
|
def on_send(node)
|
180
184
|
return unless node.arguments?
|
181
185
|
return if node.parenthesized?
|
@@ -299,13 +303,28 @@ module RuboCop
|
|
299
303
|
|
300
304
|
def move_comment_before_block(corrector, comment, block_node, closing_brace)
|
301
305
|
range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace
|
306
|
+
|
307
|
+
# It is possible that there is code between the block and the comment
|
308
|
+
# which needs to be preserved and trimmed.
|
309
|
+
pre_comment_range = source_range_before_comment(range, comment)
|
310
|
+
|
302
311
|
corrector.remove(range_with_surrounding_space(comment.source_range, side: :right))
|
303
|
-
remove_trailing_whitespace(corrector,
|
304
|
-
corrector.insert_after(
|
312
|
+
remove_trailing_whitespace(corrector, pre_comment_range, comment)
|
313
|
+
corrector.insert_after(pre_comment_range, "\n")
|
305
314
|
|
306
315
|
corrector.insert_before(block_node, "#{comment.text}\n")
|
307
316
|
end
|
308
317
|
|
318
|
+
def source_range_before_comment(range, comment)
|
319
|
+
range = range.end.join(comment.source_range.begin)
|
320
|
+
|
321
|
+
# End the range before any whitespace that precedes the comment
|
322
|
+
trailing_whitespace_count = range.source[/\s+\z/]&.length
|
323
|
+
range = range.adjust(end_pos: -trailing_whitespace_count) if trailing_whitespace_count
|
324
|
+
|
325
|
+
range
|
326
|
+
end
|
327
|
+
|
309
328
|
def end_of_chain(node)
|
310
329
|
return end_of_chain(node.block_node) if with_block?(node)
|
311
330
|
return node unless node.chained?
|
@@ -341,8 +360,9 @@ module RuboCop
|
|
341
360
|
end
|
342
361
|
end
|
343
362
|
|
363
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
344
364
|
def proper_block_style?(node)
|
345
|
-
return true if require_braces?(node)
|
365
|
+
return true if require_braces?(node) || require_do_end?(node)
|
346
366
|
return special_method_proper_block_style?(node) if special_method?(node.method_name)
|
347
367
|
|
348
368
|
case style
|
@@ -352,6 +372,7 @@ module RuboCop
|
|
352
372
|
when :always_braces then braces_style?(node)
|
353
373
|
end
|
354
374
|
end
|
375
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
355
376
|
|
356
377
|
def require_braces?(node)
|
357
378
|
return false unless node.braces?
|
@@ -361,6 +382,13 @@ module RuboCop
|
|
361
382
|
end
|
362
383
|
end
|
363
384
|
|
385
|
+
def require_do_end?(node)
|
386
|
+
return false if node.braces? || node.multiline?
|
387
|
+
return false unless (resbody = node.each_descendant(:resbody).first)
|
388
|
+
|
389
|
+
resbody.children.first&.array_type?
|
390
|
+
end
|
391
|
+
|
364
392
|
def special_method?(method_name)
|
365
393
|
allowed_method?(method_name) ||
|
366
394
|
matches_allowed_pattern?(method_name) ||
|
@@ -21,6 +21,7 @@ module RuboCop
|
|
21
21
|
# array.reject(&:nil?)
|
22
22
|
# array.reject { |e| e.nil? }
|
23
23
|
# array.select { |e| !e.nil? }
|
24
|
+
# array.filter { |e| !e.nil? }
|
24
25
|
# array.grep_v(nil)
|
25
26
|
# array.grep_v(NilClass)
|
26
27
|
#
|
@@ -29,10 +30,9 @@ module RuboCop
|
|
29
30
|
#
|
30
31
|
# # bad
|
31
32
|
# hash.reject!(&:nil?)
|
32
|
-
# array.delete_if(&:nil?)
|
33
33
|
# hash.reject! { |k, v| v.nil? }
|
34
|
-
# array.delete_if { |e| e.nil? }
|
35
34
|
# hash.select! { |k, v| !v.nil? }
|
35
|
+
# hash.filter! { |k, v| !v.nil? }
|
36
36
|
#
|
37
37
|
# # good
|
38
38
|
# hash.compact!
|
@@ -48,14 +48,15 @@ module RuboCop
|
|
48
48
|
extend TargetRubyVersion
|
49
49
|
|
50
50
|
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
51
|
-
RESTRICT_ON_SEND = %i[reject
|
51
|
+
RESTRICT_ON_SEND = %i[reject reject! select select! filter filter! grep_v].freeze
|
52
52
|
TO_ENUM_METHODS = %i[to_enum lazy].freeze
|
53
|
+
FILTER_METHODS = %i[filter filter!].freeze
|
53
54
|
|
54
55
|
minimum_target_ruby_version 2.4
|
55
56
|
|
56
57
|
# @!method reject_method_with_block_pass?(node)
|
57
58
|
def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
|
58
|
-
(call !nil? {:reject :
|
59
|
+
(call !nil? {:reject :reject!}
|
59
60
|
(block_pass
|
60
61
|
(sym :nil?)))
|
61
62
|
PATTERN
|
@@ -64,7 +65,7 @@ module RuboCop
|
|
64
65
|
def_node_matcher :reject_method?, <<~PATTERN
|
65
66
|
(block
|
66
67
|
(call
|
67
|
-
!nil? {:reject :
|
68
|
+
!nil? {:reject :reject!})
|
68
69
|
$(args ...)
|
69
70
|
(call
|
70
71
|
$(lvar _) :nil?))
|
@@ -74,7 +75,7 @@ module RuboCop
|
|
74
75
|
def_node_matcher :select_method?, <<~PATTERN
|
75
76
|
(block
|
76
77
|
(call
|
77
|
-
!nil? {:select :select!})
|
78
|
+
!nil? {:select :select! :filter :filter!})
|
78
79
|
$(args ...)
|
79
80
|
(call
|
80
81
|
(call
|
@@ -87,11 +88,10 @@ module RuboCop
|
|
87
88
|
PATTERN
|
88
89
|
|
89
90
|
def on_send(node)
|
91
|
+
return if target_ruby_version < 2.6 && FILTER_METHODS.include?(node.method_name)
|
90
92
|
return unless (range = offense_range(node))
|
91
93
|
return if allowed_receiver?(node.receiver)
|
92
|
-
if
|
93
|
-
return
|
94
|
-
end
|
94
|
+
return if target_ruby_version <= 3.0 && to_enum_method?(node)
|
95
95
|
|
96
96
|
good = good_method_name(node)
|
97
97
|
message = format(MSG, good: good, bad: range.source)
|
@@ -127,7 +127,7 @@ module RuboCop
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def good_method_name(node)
|
130
|
-
if node.bang_method?
|
130
|
+
if node.bang_method?
|
131
131
|
'compact!'
|
132
132
|
else
|
133
133
|
'compact'
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for multiple `defined?` calls joined by `&&` that can be combined
|
7
|
+
# into a single `defined?`.
|
8
|
+
#
|
9
|
+
# When checking that a nested constant or chained method is defined, it is
|
10
|
+
# not necessary to check each ancestor or component of the chain.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# defined?(Foo) && defined?(Foo::Bar) && defined?(Foo::Bar::Baz)
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# defined?(Foo::Bar::Baz)
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# defined?(foo) && defined?(foo.bar) && defined?(foo.bar.baz)
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# defined?(foo.bar.baz)
|
24
|
+
class CombinableDefined < Base
|
25
|
+
extend AutoCorrector
|
26
|
+
include RangeHelp
|
27
|
+
|
28
|
+
MSG = 'Combine nested `defined?` calls.'
|
29
|
+
OPERATORS = %w[&& and].freeze
|
30
|
+
|
31
|
+
def on_and(node)
|
32
|
+
# Only register an offense if all `&&` terms are `defined?` calls
|
33
|
+
return unless (terms = terms(node)).all?(&:defined_type?)
|
34
|
+
|
35
|
+
calls = defined_calls(terms)
|
36
|
+
namespaces = namespaces(calls)
|
37
|
+
|
38
|
+
calls.each do |call|
|
39
|
+
next unless namespaces.any?(call)
|
40
|
+
|
41
|
+
add_offense(node) do |corrector|
|
42
|
+
remove_term(corrector, call)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def terms(node)
|
50
|
+
node.each_descendant.select do |descendant|
|
51
|
+
descendant.parent.and_type? && !descendant.and_type?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def defined_calls(nodes)
|
56
|
+
nodes.filter_map do |defined_node|
|
57
|
+
subject = defined_node.first_argument
|
58
|
+
subject if subject.const_type? || subject.call_type?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def namespaces(nodes)
|
63
|
+
nodes.filter_map do |node|
|
64
|
+
if node.respond_to?(:namespace)
|
65
|
+
node.namespace
|
66
|
+
elsif node.respond_to?(:receiver)
|
67
|
+
node.receiver
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_term(corrector, term)
|
73
|
+
term = term.parent until term.parent.and_type?
|
74
|
+
range = if term == term.parent.children.last
|
75
|
+
rhs_range_to_remove(term)
|
76
|
+
else
|
77
|
+
lhs_range_to_remove(term)
|
78
|
+
end
|
79
|
+
|
80
|
+
corrector.remove(range)
|
81
|
+
end
|
82
|
+
|
83
|
+
# If the redundant `defined?` node is the LHS of an `and` node,
|
84
|
+
# the term as well as the subsequent `&&`/`and` operator will be removed.
|
85
|
+
def lhs_range_to_remove(term)
|
86
|
+
source = @processed_source.buffer.source
|
87
|
+
|
88
|
+
pos = term.source_range.end_pos
|
89
|
+
pos += 1 until source[..pos].end_with?(*OPERATORS)
|
90
|
+
|
91
|
+
range_with_surrounding_space(
|
92
|
+
range: term.source_range.with(end_pos: pos + 1),
|
93
|
+
side: :right,
|
94
|
+
newlines: false
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
# If the redundant `defined?` node is the RHS of an `and` node,
|
99
|
+
# the term as well as the preceding `&&`/`and` operator will be removed.
|
100
|
+
def rhs_range_to_remove(term)
|
101
|
+
source = @processed_source.buffer.source
|
102
|
+
|
103
|
+
pos = term.source_range.begin_pos
|
104
|
+
pos -= 1 until source[pos, 3].start_with?(*OPERATORS)
|
105
|
+
|
106
|
+
range_with_surrounding_space(
|
107
|
+
range: term.source_range.with(begin_pos: pos - 1),
|
108
|
+
side: :right,
|
109
|
+
newlines: false
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -7,6 +7,9 @@ module RuboCop
|
|
7
7
|
# can be combined into a single loop. It is very likely that combining them
|
8
8
|
# will make the code more efficient and more concise.
|
9
9
|
#
|
10
|
+
# NOTE: Autocorrection is not applied when the block variable names differ in separate loops,
|
11
|
+
# as it is impossible to determine which variable name should be prioritized.
|
12
|
+
#
|
10
13
|
# @safety
|
11
14
|
# The cop is unsafe, because the first loop might modify state that the
|
12
15
|
# second loop depends on; these two aren't combinable.
|
@@ -61,6 +64,7 @@ module RuboCop
|
|
61
64
|
|
62
65
|
MSG = 'Combine this loop with the previous loop.'
|
63
66
|
|
67
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
64
68
|
def on_block(node)
|
65
69
|
return unless node.parent&.begin_type?
|
66
70
|
return unless collection_looping_method?(node)
|
@@ -68,9 +72,12 @@ module RuboCop
|
|
68
72
|
return unless node.body && node.left_sibling.body
|
69
73
|
|
70
74
|
add_offense(node) do |corrector|
|
75
|
+
next unless node.arguments == node.left_sibling.arguments
|
76
|
+
|
71
77
|
combine_with_left_sibling(corrector, node)
|
72
78
|
end
|
73
79
|
end
|
80
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
74
81
|
|
75
82
|
alias on_numblock on_block
|
76
83
|
|
@@ -10,7 +10,7 @@ module RuboCop
|
|
10
10
|
#
|
11
11
|
# Note that some comments
|
12
12
|
# (`:nodoc:`, `:yields:`, `rubocop:disable` and `rubocop:todo`)
|
13
|
-
# are allowed.
|
13
|
+
# and RBS::Inline annotation comments are allowed.
|
14
14
|
#
|
15
15
|
# Autocorrection removes comments from `end` keyword and keeps comments
|
16
16
|
# for `class`, `module`, `def` and `begin` above the keyword.
|
@@ -82,6 +82,8 @@ module RuboCop
|
|
82
82
|
|
83
83
|
def offensive?(comment)
|
84
84
|
line = source_line(comment)
|
85
|
+
return false if rbs_inline_annotation?(line, comment)
|
86
|
+
|
85
87
|
KEYWORD_REGEXES.any? { |r| r.match?(line) } &&
|
86
88
|
ALLOWED_COMMENT_REGEXES.none? { |r| r.match?(line) }
|
87
89
|
end
|
@@ -89,6 +91,10 @@ module RuboCop
|
|
89
91
|
def source_line(comment)
|
90
92
|
comment.source_range.source_line
|
91
93
|
end
|
94
|
+
|
95
|
+
def rbs_inline_annotation?(line, comment)
|
96
|
+
comment.text.start_with?('#:') && line.start_with?(/\A\s*def\s/)
|
97
|
+
end
|
92
98
|
end
|
93
99
|
end
|
94
100
|
end
|
@@ -73,7 +73,7 @@ module RuboCop
|
|
73
73
|
elsif_branches << node.if_branch
|
74
74
|
|
75
75
|
else_branch = node.else_branch
|
76
|
-
if else_branch&.if_type? && else_branch
|
76
|
+
if else_branch&.if_type? && else_branch.elsif?
|
77
77
|
expand_elsif(else_branch, elsif_branches)
|
78
78
|
else
|
79
79
|
elsif_branches << else_branch
|
@@ -36,7 +36,7 @@ module RuboCop
|
|
36
36
|
def on_class(node)
|
37
37
|
return unless data_define?(node.parent_class)
|
38
38
|
|
39
|
-
add_offense(node.parent_class
|
39
|
+
add_offense(node.parent_class) do |corrector|
|
40
40
|
corrector.remove(range_with_surrounding_space(node.loc.keyword, newlines: false))
|
41
41
|
corrector.replace(node.loc.operator, '=')
|
42
42
|
|
@@ -48,6 +48,7 @@ module RuboCop
|
|
48
48
|
#
|
49
49
|
class EndlessMethod < Base
|
50
50
|
include ConfigurableEnforcedStyle
|
51
|
+
include EndlessMethodRewriter
|
51
52
|
extend TargetRubyVersion
|
52
53
|
extend AutoCorrector
|
53
54
|
|
@@ -81,20 +82,6 @@ module RuboCop
|
|
81
82
|
|
82
83
|
add_offense(node) { |corrector| correct_to_multiline(corrector, node) }
|
83
84
|
end
|
84
|
-
|
85
|
-
def correct_to_multiline(corrector, node)
|
86
|
-
replacement = <<~RUBY.strip
|
87
|
-
def #{node.method_name}#{arguments(node)}
|
88
|
-
#{node.body.source}
|
89
|
-
end
|
90
|
-
RUBY
|
91
|
-
|
92
|
-
corrector.replace(node, replacement)
|
93
|
-
end
|
94
|
-
|
95
|
-
def arguments(node, missing = '')
|
96
|
-
node.arguments.any? ? node.arguments.source : missing
|
97
|
-
end
|
98
85
|
end
|
99
86
|
end
|
100
87
|
end
|
@@ -136,7 +136,7 @@ module RuboCop
|
|
136
136
|
actual: line_node.source,
|
137
137
|
expected: expected)
|
138
138
|
|
139
|
-
add_offense(line_node
|
139
|
+
add_offense(line_node, message: message) do |corrector|
|
140
140
|
corrector.replace(line_node, expected)
|
141
141
|
end
|
142
142
|
end
|