rubocop 1.87.0 → 1.88.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/default.yml +80 -72
- data/config/obsoletion.yml +21 -1
- data/lib/rubocop/cli/command/auto_generate_config.rb +6 -0
- data/lib/rubocop/cop/base.rb +30 -10
- data/lib/rubocop/cop/bundler/gem_comment.rb +5 -3
- data/lib/rubocop/cop/commissioner.rb +13 -11
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +7 -1
- data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +8 -1
- data/lib/rubocop/cop/gemspec/development_dependencies.rb +1 -1
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
- data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -1
- data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +5 -3
- data/lib/rubocop/cop/layout/block_alignment.rb +58 -4
- data/lib/rubocop/cop/layout/class_structure.rb +7 -3
- data/lib/rubocop/cop/layout/comment_indentation.rb +9 -2
- data/lib/rubocop/cop/layout/condition_position.rb +13 -3
- data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
- data/lib/rubocop/cop/layout/empty_comment.rb +8 -10
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +14 -1
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +13 -14
- data/lib/rubocop/cop/layout/indentation_width.rb +28 -0
- data/lib/rubocop/cop/layout/line_length.rb +10 -5
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +6 -1
- data/lib/rubocop/cop/layout/space_around_operators.rb +6 -2
- data/lib/rubocop/cop/lint/ambiguous_assignment.rb +1 -11
- data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +1 -10
- data/lib/rubocop/cop/lint/assignment_in_condition.rb +13 -1
- data/lib/rubocop/cop/lint/circular_argument_reference.rb +1 -3
- data/lib/rubocop/cop/lint/debugger.rb +13 -2
- data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -7
- data/lib/rubocop/cop/lint/empty_block.rb +3 -3
- data/lib/rubocop/cop/lint/ensure_return.rb +19 -1
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +3 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -0
- data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +5 -1
- data/lib/rubocop/cop/lint/interpolation_check.rb +18 -3
- data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +1 -1
- data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +11 -1
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -11
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +4 -4
- data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +16 -0
- data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
- data/lib/rubocop/cop/lint/number_conversion.rb +13 -4
- data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +10 -0
- data/lib/rubocop/cop/lint/ordered_magic_comments.rb +7 -7
- data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
- data/lib/rubocop/cop/lint/rand_one.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +4 -1
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +4 -1
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +15 -4
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +14 -7
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +4 -0
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +7 -0
- data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_with_object.rb +5 -0
- data/lib/rubocop/cop/lint/refinement_import_methods.rb +8 -1
- data/lib/rubocop/cop/lint/regexp_as_condition.rb +9 -1
- data/lib/rubocop/cop/lint/require_parentheses.rb +13 -4
- data/lib/rubocop/cop/lint/require_range_parentheses.rb +2 -1
- data/lib/rubocop/cop/lint/require_relative_self_path.rb +5 -5
- data/lib/rubocop/cop/lint/rescue_type.rb +1 -1
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -0
- data/lib/rubocop/cop/lint/safe_navigation_with_empty.rb +1 -1
- data/lib/rubocop/cop/lint/script_permission.rb +5 -1
- data/lib/rubocop/cop/lint/self_assignment.rb +24 -1
- data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -1
- data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +14 -0
- data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -1
- data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +12 -0
- data/lib/rubocop/cop/lint/symbol_conversion.rb +21 -4
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +35 -2
- data/lib/rubocop/cop/lint/to_json.rb +8 -1
- data/lib/rubocop/cop/lint/top_level_return_with_argument.rb +1 -1
- data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +4 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +35 -9
- data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
- data/lib/rubocop/cop/lint/useless_assignment.rb +10 -5
- data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +7 -3
- data/lib/rubocop/cop/lint/useless_setter_call.rb +4 -1
- data/lib/rubocop/cop/lint/useless_times.rb +22 -1
- data/lib/rubocop/cop/lint/void.rb +2 -2
- data/lib/rubocop/cop/metrics/collection_literal_length.rb +1 -1
- data/lib/rubocop/cop/metrics/method_length.rb +1 -1
- data/lib/rubocop/cop/metrics/perceived_complexity.rb +38 -7
- data/lib/rubocop/cop/mixin/allowed_methods.rb +5 -5
- data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
- data/lib/rubocop/cop/mixin/forbidden_pattern.rb +5 -1
- data/lib/rubocop/cop/mixin/hash_subset.rb +8 -0
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
- data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
- data/lib/rubocop/cop/naming/file_name.rb +4 -3
- data/lib/rubocop/cop/naming/inclusive_language.rb +8 -2
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +9 -0
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +9 -3
- data/lib/rubocop/cop/security/io_methods.rb +1 -1
- data/lib/rubocop/cop/security/marshal_load.rb +1 -1
- data/lib/rubocop/cop/style/accessor_grouping.rb +11 -1
- data/lib/rubocop/cop/style/alias.rb +1 -1
- data/lib/rubocop/cop/style/and_or.rb +1 -1
- data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -1
- data/lib/rubocop/cop/style/array_first_last.rb +12 -1
- data/lib/rubocop/cop/style/array_intersect.rb +8 -4
- data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +3 -0
- data/lib/rubocop/cop/style/block_delimiters.rb +16 -2
- data/lib/rubocop/cop/style/case_equality.rb +14 -2
- data/lib/rubocop/cop/style/class_equality_comparison.rb +21 -13
- data/lib/rubocop/cop/style/class_methods_definitions.rb +11 -5
- data/lib/rubocop/cop/style/collection_compact.rb +1 -1
- data/lib/rubocop/cop/style/colon_method_call.rb +13 -6
- data/lib/rubocop/cop/style/combinable_loops.rb +5 -0
- data/lib/rubocop/cop/style/comparable_clamp.rb +12 -1
- data/lib/rubocop/cop/style/concat_array_literals.rb +5 -1
- data/lib/rubocop/cop/style/conditional_assignment.rb +6 -1
- data/lib/rubocop/cop/style/constant_visibility.rb +4 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
- data/lib/rubocop/cop/style/date_time.rb +2 -2
- data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
- data/lib/rubocop/cop/style/dig_chain.rb +5 -0
- data/lib/rubocop/cop/style/dir_empty.rb +4 -0
- data/lib/rubocop/cop/style/documentation_method.rb +5 -1
- data/lib/rubocop/cop/style/empty_case_condition.rb +12 -2
- data/lib/rubocop/cop/style/empty_class_definition.rb +8 -1
- data/lib/rubocop/cop/style/empty_heredoc.rb +4 -0
- data/lib/rubocop/cop/style/empty_literal.rb +7 -2
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +30 -20
- data/lib/rubocop/cop/style/env_home.rb +4 -0
- data/lib/rubocop/cop/style/even_odd.rb +11 -1
- data/lib/rubocop/cop/style/exact_regexp_match.rb +8 -1
- data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
- data/lib/rubocop/cop/style/file_null.rb +4 -2
- data/lib/rubocop/cop/style/file_write.rb +17 -14
- data/lib/rubocop/cop/style/format_string.rb +13 -1
- data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
- data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
- data/lib/rubocop/cop/style/hash_slice.rb +16 -0
- data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
- data/lib/rubocop/cop/style/if_unless_modifier.rb +5 -5
- data/lib/rubocop/cop/style/if_with_semicolon.rb +9 -1
- data/lib/rubocop/cop/style/inline_comment.rb +1 -1
- data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
- data/lib/rubocop/cop/style/keyword_arguments_merging.rb +4 -0
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +7 -3
- data/lib/rubocop/cop/style/lambda.rb +7 -1
- data/lib/rubocop/cop/style/lambda_call.rb +11 -0
- data/lib/rubocop/cop/style/map_compact_with_conditional_block.rb +11 -0
- data/lib/rubocop/cop/style/map_into_array.rb +1 -1
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -5
- data/lib/rubocop/cop/style/method_def_parentheses.rb +5 -1
- data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +10 -7
- data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
- data/lib/rubocop/cop/style/module_function.rb +4 -2
- data/lib/rubocop/cop/style/multiline_if_then.rb +1 -1
- data/lib/rubocop/cop/style/multiline_memoization.rb +7 -1
- data/lib/rubocop/cop/style/multiline_method_signature.rb +11 -4
- data/lib/rubocop/cop/style/mutable_constant.rb +105 -11
- data/lib/rubocop/cop/style/nil_lambda.rb +8 -0
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/open_struct_use.rb +1 -1
- data/lib/rubocop/cop/style/option_hash.rb +1 -1
- data/lib/rubocop/cop/style/optional_arguments.rb +1 -0
- data/lib/rubocop/cop/style/parallel_assignment.rb +19 -3
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
- data/lib/rubocop/cop/style/perl_backrefs.rb +5 -3
- data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +13 -1
- data/lib/rubocop/cop/style/redundant_exception.rb +6 -0
- data/lib/rubocop/cop/style/redundant_filter_chain.rb +1 -1
- data/lib/rubocop/cop/style/redundant_format.rb +29 -0
- data/lib/rubocop/cop/style/redundant_heredoc_delimiter_quotes.rb +6 -1
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +11 -3
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -4
- data/lib/rubocop/cop/style/redundant_self.rb +9 -0
- data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +23 -4
- data/lib/rubocop/cop/style/semicolon.rb +20 -5
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +17 -4
- data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
- data/lib/rubocop/cop/style/struct_inheritance.rb +3 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +11 -0
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
- data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
- data/lib/rubocop/cop/style/while_until_do.rb +7 -0
- data/lib/rubocop/cop/style/word_array.rb +1 -0
- data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -3
- data/lib/rubocop/cop/team.rb +18 -4
- data/lib/rubocop/formatter/disabled_config_formatter.rb +14 -7
- data/lib/rubocop/runner.rb +55 -13
- data/lib/rubocop/server/core.rb +6 -0
- data/lib/rubocop/version.rb +1 -1
- metadata +3 -3
|
@@ -36,31 +36,31 @@ module RuboCop
|
|
|
36
36
|
RESTRICT_ON_SEND = %i[+ - * / **].freeze
|
|
37
37
|
|
|
38
38
|
# @!method useless_operation?(node)
|
|
39
|
-
def_node_matcher :useless_operation?,
|
|
39
|
+
def_node_matcher :useless_operation?, <<~PATTERN
|
|
40
|
+
(call ${lvar ivar cvar gvar const (send nil? _)} $_ (int $_))
|
|
41
|
+
PATTERN
|
|
40
42
|
|
|
41
43
|
# @!method useless_abbreviated_assignment?(node)
|
|
42
|
-
def_node_matcher :useless_abbreviated_assignment?,
|
|
44
|
+
def_node_matcher :useless_abbreviated_assignment?, <<~PATTERN
|
|
45
|
+
(op-asgn ${lvasgn ivasgn cvasgn gvasgn casgn} $_ (int $_))
|
|
46
|
+
PATTERN
|
|
43
47
|
|
|
44
48
|
def on_send(node)
|
|
45
|
-
return unless useless_operation?(node)
|
|
46
|
-
|
|
47
|
-
variable, operation, number = useless_operation?(node)
|
|
49
|
+
return unless (receiver, operation, number = useless_operation?(node))
|
|
48
50
|
return unless useless?(operation, number)
|
|
49
51
|
|
|
50
52
|
add_offense(node) do |corrector|
|
|
51
|
-
corrector.replace(node,
|
|
53
|
+
corrector.replace(node, receiver.source)
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
alias on_csend on_send
|
|
55
57
|
|
|
56
58
|
def on_op_asgn(node)
|
|
57
|
-
return unless useless_abbreviated_assignment?(node)
|
|
58
|
-
|
|
59
|
-
variable, operation, number = useless_abbreviated_assignment?(node)
|
|
59
|
+
return unless (variable, operation, number = useless_abbreviated_assignment?(node))
|
|
60
60
|
return unless useless?(operation, number)
|
|
61
61
|
|
|
62
62
|
add_offense(node) do |corrector|
|
|
63
|
-
corrector.replace(node, "#{variable} = #{variable}")
|
|
63
|
+
corrector.replace(node, "#{variable.source} = #{variable.source}")
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -107,11 +107,15 @@ module RuboCop
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def find_method_definition(node, method_name)
|
|
110
|
-
node.each_ancestor
|
|
111
|
-
ancestor.each_child_node(:def, :any_block).find do |child|
|
|
110
|
+
node.each_ancestor do |ancestor|
|
|
111
|
+
found = ancestor.each_child_node(:def, :any_block).find do |child|
|
|
112
112
|
method_definition(child, method_name)
|
|
113
113
|
end
|
|
114
|
-
|
|
114
|
+
return found if found
|
|
115
|
+
# A method defined in an outer lexical scope does not define this scope's method,
|
|
116
|
+
# so stop searching once a class/module boundary is crossed without a match.
|
|
117
|
+
return nil if ancestor.type?(:class, :module, :sclass)
|
|
118
|
+
end
|
|
115
119
|
end
|
|
116
120
|
|
|
117
121
|
# `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
|
|
@@ -107,7 +107,10 @@ module RuboCop
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def process_multiple_assignment(masgn_node)
|
|
110
|
-
|
|
110
|
+
# Iterate the top-level destructuring slots so each maps to the right-hand side
|
|
111
|
+
# element at the same position. Using the flattened `assignments` would misalign
|
|
112
|
+
# the index when a slot is itself a nested destructuring (e.g. `(a, b), c = x, y`).
|
|
113
|
+
masgn_node.lhs.children.each_with_index do |lhs_node, index|
|
|
111
114
|
next unless ASSIGNMENT_TYPES.include?(lhs_node.type)
|
|
112
115
|
|
|
113
116
|
if masgn_node.rhs.array_type? && (rhs_node = masgn_node.rhs.children[index])
|
|
@@ -83,7 +83,7 @@ module RuboCop
|
|
|
83
83
|
|
|
84
84
|
def autocorrect_block(corrector, node)
|
|
85
85
|
block_arg = block_arg(node)
|
|
86
|
-
return
|
|
86
|
+
return unless reducible_to_body?(node, block_arg)
|
|
87
87
|
|
|
88
88
|
source = node.body.source
|
|
89
89
|
source.gsub!(/\b#{block_arg}\b/, '0') if block_arg
|
|
@@ -91,6 +91,27 @@ module RuboCop
|
|
|
91
91
|
corrector.replace(node, fix_indentation(source, node.loc.column...node.body.loc.column))
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
+
def reducible_to_body?(node, block_arg)
|
|
95
|
+
# A block with multiple arguments can't be reduced to its body (the extra arguments
|
|
96
|
+
# would become undefined references), and `next`/`break`/`redo` bound to the block
|
|
97
|
+
# become orphaned (a syntax error) once the block is removed.
|
|
98
|
+
return false if node.arguments.size > 1 || orphans_loop_control_keyword?(node)
|
|
99
|
+
|
|
100
|
+
# A lone non-simple argument (destructuring `|(a, b)|` or a splat `|*a|`) can't be
|
|
101
|
+
# substituted either, so reducing to the body would leave it referencing an
|
|
102
|
+
# undefined variable.
|
|
103
|
+
return false if node.arguments.one? && block_arg.nil?
|
|
104
|
+
|
|
105
|
+
!block_reassigns_arg?(node, block_arg)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def orphans_loop_control_keyword?(node)
|
|
109
|
+
node.body&.each_node(:next, :break, :redo)&.any? do |control|
|
|
110
|
+
inner = control.each_ancestor.take_while { |ancestor| !ancestor.equal?(node) }
|
|
111
|
+
inner.none? { |ancestor| ancestor.type?(:any_block, :while, :until, :for) }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
94
115
|
def fix_indentation(source, range)
|
|
95
116
|
# Cleanup indentation in a multiline block
|
|
96
117
|
source_lines = source.split("\n")
|
|
@@ -212,7 +212,7 @@ module RuboCop
|
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
def check_nonmutating(node)
|
|
215
|
-
return unless node.type?(:
|
|
215
|
+
return unless node.type?(:call, :any_block)
|
|
216
216
|
|
|
217
217
|
method_name = node.method_name
|
|
218
218
|
return unless NONMUTATING_METHODS.include?(method_name)
|
|
@@ -265,7 +265,7 @@ module RuboCop
|
|
|
265
265
|
end
|
|
266
266
|
|
|
267
267
|
def autocorrect_nonmutating_send(corrector, node, suggestion)
|
|
268
|
-
send_node = if node.
|
|
268
|
+
send_node = if node.call_type?
|
|
269
269
|
node
|
|
270
270
|
else
|
|
271
271
|
node.send_node
|
|
@@ -58,7 +58,7 @@ module RuboCop
|
|
|
58
58
|
return unless node.method?(:define_method)
|
|
59
59
|
|
|
60
60
|
method_name = node.send_node.first_argument
|
|
61
|
-
return if method_name
|
|
61
|
+
return if method_name&.basic_literal? && allowed?(method_name.value)
|
|
62
62
|
|
|
63
63
|
check_code_length(node)
|
|
64
64
|
end
|
|
@@ -12,9 +12,15 @@ module RuboCop
|
|
|
12
12
|
# nodes count. In contrast to the CyclomaticComplexity cop, this cop
|
|
13
13
|
# considers `else` nodes as adding complexity.
|
|
14
14
|
#
|
|
15
|
+
# A `case`/`in` branch whose pattern is a simple literal (e.g. `in 1`, `in "red"`, `in 1..10`)
|
|
16
|
+
# or a constant/type (e.g. `in Integer`) and has no guard is just as easy to read as a `when`
|
|
17
|
+
# branch, so it is discounted the same way. Branches with structural patterns (e.g. array,
|
|
18
|
+
# hash, or find patterns), bindings, alternatives, or a guard add the full complexity of
|
|
19
|
+
# a decision point.
|
|
20
|
+
#
|
|
15
21
|
# @example
|
|
16
22
|
#
|
|
17
|
-
# def
|
|
23
|
+
# def example_1 # 1
|
|
18
24
|
# if cond # 1
|
|
19
25
|
# case var # 2 (0.8 + 4 * 0.2, rounded)
|
|
20
26
|
# when 1 then func_one
|
|
@@ -26,33 +32,58 @@ module RuboCop
|
|
|
26
32
|
# do_something until a && b # 2
|
|
27
33
|
# end # ===
|
|
28
34
|
# end # 7 complexity points
|
|
35
|
+
#
|
|
36
|
+
# def example_2 # 1
|
|
37
|
+
# case color # 1 (3 * 0.2, rounded)
|
|
38
|
+
# in "red" then func_red
|
|
39
|
+
# in "blue" then func_blue
|
|
40
|
+
# in "green" then func_green
|
|
41
|
+
# end # ===
|
|
42
|
+
# end # 2 complexity points
|
|
29
43
|
class PerceivedComplexity < CyclomaticComplexity
|
|
30
44
|
MSG = 'Perceived complexity for `%<method>s` is too high. [%<complexity>d/%<max>d]'
|
|
31
45
|
|
|
32
|
-
COUNTED_NODES = (
|
|
46
|
+
COUNTED_NODES = (
|
|
47
|
+
CyclomaticComplexity::COUNTED_NODES - %i[when in_pattern] + %i[case case_match]
|
|
48
|
+
).freeze
|
|
33
49
|
|
|
34
50
|
private
|
|
35
51
|
|
|
52
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
36
53
|
def complexity_score_for(node)
|
|
37
54
|
case node.type
|
|
38
55
|
when :case
|
|
39
|
-
# If cond is nil, that means each when has an expression that
|
|
40
|
-
#
|
|
41
|
-
# if/elsif/elsif... so the when nodes count.
|
|
56
|
+
# If cond is nil, that means each when has an expression that evaluates to true or
|
|
57
|
+
# false. It's just an alternative to if/elsif/elsif... so the when nodes count.
|
|
42
58
|
nb_branches = node.when_branches.length + (node.else_branch ? 1 : 0)
|
|
43
59
|
if node.condition.nil?
|
|
44
60
|
nb_branches
|
|
45
61
|
else
|
|
46
|
-
# Otherwise, the case node gets 0.8 complexity points and each
|
|
47
|
-
# when gets 0.2.
|
|
62
|
+
# Otherwise, the case node gets 0.8 complexity points and each when gets 0.2.
|
|
48
63
|
((nb_branches * 0.2) + 0.8).round
|
|
49
64
|
end
|
|
65
|
+
when :case_match
|
|
66
|
+
# Simple `in` branches are discounted like `when`, while structural patterns keep
|
|
67
|
+
# the full complexity of a decision point.
|
|
68
|
+
score = node.in_pattern_branches.sum { |branch| simple_in_pattern?(branch) ? 0.2 : 1 }
|
|
69
|
+
score += 0.2 if node.else_branch
|
|
70
|
+
score.round
|
|
50
71
|
when :if
|
|
51
72
|
node.else? && !node.elsif? ? 2 : 1
|
|
52
73
|
else
|
|
53
74
|
super
|
|
54
75
|
end
|
|
55
76
|
end
|
|
77
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
78
|
+
|
|
79
|
+
def simple_in_pattern?(in_pattern_node)
|
|
80
|
+
# `in_pattern_node.children[1]` is the guard (`if`/`unless`), or `nil`.
|
|
81
|
+
return false unless in_pattern_node.children[1].nil?
|
|
82
|
+
|
|
83
|
+
# A scalar literal, a literal range, or a constant/type is as easy to read as a `when`.
|
|
84
|
+
pattern = in_pattern_node.pattern
|
|
85
|
+
pattern.literal? || pattern.const_type?
|
|
86
|
+
end
|
|
56
87
|
end
|
|
57
88
|
end
|
|
58
89
|
end
|
|
@@ -25,11 +25,11 @@ module RuboCop
|
|
|
25
25
|
|
|
26
26
|
# @api public
|
|
27
27
|
def allowed_methods
|
|
28
|
-
if cop_config_deprecated_values.any?(Regexp)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
@allowed_methods ||= if cop_config_deprecated_values.any?(Regexp)
|
|
29
|
+
cop_config_allowed_methods
|
|
30
|
+
else
|
|
31
|
+
cop_config_allowed_methods + cop_config_deprecated_values
|
|
32
|
+
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def cop_config_allowed_methods
|
|
@@ -27,7 +27,7 @@ module RuboCop
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def matches_allowed_pattern?(line)
|
|
30
|
-
|
|
30
|
+
allowed_pattern_regexps.any? { |pattern| pattern.match?(line) }
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# @deprecated Use matches_allowed_pattern? instead
|
|
@@ -49,6 +49,10 @@ module RuboCop
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
def allowed_pattern_regexps
|
|
53
|
+
@allowed_pattern_regexps ||= allowed_patterns.map { |pattern| Regexp.new(pattern) }
|
|
54
|
+
end
|
|
55
|
+
|
|
52
56
|
def cop_config_patterns_values
|
|
53
57
|
@cop_config_patterns_values ||=
|
|
54
58
|
Array(cop_config.fetch('AllowedPatterns', [])) +
|
|
@@ -5,12 +5,16 @@ module RuboCop
|
|
|
5
5
|
# This module encapsulates the ability to forbid certain patterns in a cop.
|
|
6
6
|
module ForbiddenPattern
|
|
7
7
|
def forbidden_pattern?(name)
|
|
8
|
-
|
|
8
|
+
forbidden_pattern_regexps.any? { |pattern| pattern.match?(name) }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def forbidden_patterns
|
|
12
12
|
cop_config.fetch('ForbiddenPatterns', [])
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
def forbidden_pattern_regexps
|
|
16
|
+
@forbidden_pattern_regexps ||= forbidden_patterns.map { |pattern| Regexp.new(pattern) }
|
|
17
|
+
end
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
end
|
|
@@ -182,10 +182,18 @@ module RuboCop
|
|
|
182
182
|
return ":\"#{value.source}\"" if value.dsym_type?
|
|
183
183
|
return "\"#{value.source}\"" if value.dstr_type?
|
|
184
184
|
return ":#{value.source}" if value.sym_type?
|
|
185
|
+
# The element of a `%w` array can contain characters that are special
|
|
186
|
+
# inside a single-quoted string (e.g. a `'`), so escape them rather than
|
|
187
|
+
# wrapping the raw source.
|
|
188
|
+
return to_single_quoted(value.value) if value.str_type?
|
|
185
189
|
|
|
186
190
|
"'#{value.source}'"
|
|
187
191
|
end
|
|
188
192
|
|
|
193
|
+
def to_single_quoted(string)
|
|
194
|
+
"'#{string.gsub(/['\\]/) { |character| "\\#{character}" }}'"
|
|
195
|
+
end
|
|
196
|
+
|
|
189
197
|
def except_key(node)
|
|
190
198
|
key_arg = node.argument_list.first.source
|
|
191
199
|
body, = extract_body_if_negated(node.body)
|
|
@@ -94,6 +94,10 @@ module RuboCop
|
|
|
94
94
|
|
|
95
95
|
return unless captures.use_transformed_argname?
|
|
96
96
|
|
|
97
|
+
# A splat transforming expression (e.g. `[k, *v]`) can't be used as a
|
|
98
|
+
# standalone block return value, so the rewrite would produce invalid Ruby.
|
|
99
|
+
return if captures.transforming_body_expr.splat_type?
|
|
100
|
+
|
|
97
101
|
message = "Prefer `#{new_method_name}` over `#{match_desc}`."
|
|
98
102
|
add_offense(node, message: message) do |corrector|
|
|
99
103
|
correction = prepare_correction(node)
|
|
@@ -203,9 +203,10 @@ module RuboCop
|
|
|
203
203
|
|
|
204
204
|
def match_acronym?(expected, name)
|
|
205
205
|
expected = expected.to_s
|
|
206
|
-
name = name.to_s
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
name = allowed_acronyms.reduce(name.to_s) do |result, acronym|
|
|
207
|
+
result.gsub(acronym, acronym.capitalize)
|
|
208
|
+
end
|
|
209
|
+
expected == name
|
|
209
210
|
end
|
|
210
211
|
|
|
211
212
|
def to_namespace(path) # rubocop:disable Metrics/AbcSize
|
|
@@ -91,6 +91,8 @@ module RuboCop
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def on_new_investigation
|
|
94
|
+
return unless @flagged_terms_regex
|
|
95
|
+
|
|
94
96
|
investigate_filepath if cop_config['CheckFilepaths']
|
|
95
97
|
investigate_tokens
|
|
96
98
|
end
|
|
@@ -144,7 +146,7 @@ module RuboCop
|
|
|
144
146
|
def preprocess_flagged_terms
|
|
145
147
|
allowed_strings = []
|
|
146
148
|
flagged_term_strings = []
|
|
147
|
-
cop_config['FlaggedTerms'].each do |term, term_definition|
|
|
149
|
+
(cop_config['FlaggedTerms'] || {}).each do |term, term_definition|
|
|
148
150
|
next if term_definition.nil?
|
|
149
151
|
|
|
150
152
|
allowed_strings.concat(process_allowed_regex(term_definition['AllowedRegex']))
|
|
@@ -181,7 +183,11 @@ module RuboCop
|
|
|
181
183
|
end
|
|
182
184
|
|
|
183
185
|
def set_regexes(flagged_term_strings, allowed_strings)
|
|
184
|
-
|
|
186
|
+
# With no flagged terms an empty regex would match everything, so leave the
|
|
187
|
+
# regex nil and let `on_new_investigation` treat the cop as a no-op.
|
|
188
|
+
unless flagged_term_strings.empty?
|
|
189
|
+
@flagged_terms_regex = array_to_ignorecase_regex(flagged_term_strings)
|
|
190
|
+
end
|
|
185
191
|
@allowed_regex = array_to_ignorecase_regex(allowed_strings) unless allowed_strings.empty?
|
|
186
192
|
end
|
|
187
193
|
|
|
@@ -174,6 +174,7 @@ module RuboCop
|
|
|
174
174
|
|
|
175
175
|
method_node, method_name = find_definition(node)
|
|
176
176
|
return unless method_node
|
|
177
|
+
return unless nameable_method?(method_name)
|
|
177
178
|
|
|
178
179
|
body = method_node.body
|
|
179
180
|
return unless body == node || body.children.last == node
|
|
@@ -209,6 +210,7 @@ module RuboCop
|
|
|
209
210
|
|
|
210
211
|
method_node, method_name = find_definition(node)
|
|
211
212
|
return false unless method_node
|
|
213
|
+
return false unless nameable_method?(method_name)
|
|
212
214
|
|
|
213
215
|
defined_memoized?(method_node.body, arg.name) do |defined_ivar, return_ivar, ivar_assign|
|
|
214
216
|
return false if matches?(method_name, ivar_assign)
|
|
@@ -250,6 +252,13 @@ module RuboCop
|
|
|
250
252
|
nil
|
|
251
253
|
end
|
|
252
254
|
|
|
255
|
+
# Operator and other non-word method names (e.g. `[]`, `+`, `<=>`) cannot form a
|
|
256
|
+
# valid instance variable name, so there is no matching ivar to enforce and a
|
|
257
|
+
# suggested correction like `@[]` would be invalid Ruby.
|
|
258
|
+
def nameable_method?(method_name)
|
|
259
|
+
/\A[a-zA-Z_]\w*\z/.match?(method_name.to_s.delete('!?='))
|
|
260
|
+
end
|
|
261
|
+
|
|
253
262
|
def matches?(method_name, ivar_assign)
|
|
254
263
|
return true if ivar_assign.nil? || INITIALIZE_METHODS.include?(method_name)
|
|
255
264
|
|
|
@@ -95,11 +95,14 @@ module RuboCop
|
|
|
95
95
|
|
|
96
96
|
def autocorrect(corrector, node, range, offending_name, preferred_name)
|
|
97
97
|
corrector.replace(range, preferred_name)
|
|
98
|
-
|
|
98
|
+
# Once the exception variable is reassigned, later references point to a
|
|
99
|
+
# different value, so stop correcting after the reassignment - both in the
|
|
100
|
+
# body and in the code following the `begin`/`rescue`.
|
|
101
|
+
return if correct_node(corrector, node.body, offending_name, preferred_name)
|
|
99
102
|
return unless (kwbegin_node = node.parent.each_ancestor(:kwbegin).first)
|
|
100
103
|
|
|
101
104
|
kwbegin_node.right_siblings.each do |child_node|
|
|
102
|
-
correct_node(corrector, child_node, offending_name, preferred_name)
|
|
105
|
+
break if correct_node(corrector, child_node, offending_name, preferred_name)
|
|
103
106
|
end
|
|
104
107
|
end
|
|
105
108
|
|
|
@@ -113,6 +116,8 @@ module RuboCop
|
|
|
113
116
|
end
|
|
114
117
|
end
|
|
115
118
|
|
|
119
|
+
# Returns the reassignment node once the exception variable is reassigned (a truthy
|
|
120
|
+
# signal to stop correcting later references), or `nil` when no reassignment is found.
|
|
116
121
|
# rubocop:disable Metrics/MethodLength
|
|
117
122
|
def correct_node(corrector, node, offending_name, preferred_name)
|
|
118
123
|
return unless node
|
|
@@ -131,9 +136,10 @@ module RuboCop
|
|
|
131
136
|
|
|
132
137
|
if child_node.type?(:masgn, :lvasgn)
|
|
133
138
|
correct_reassignment(corrector, child_node, offending_name, preferred_name)
|
|
134
|
-
|
|
139
|
+
return child_node
|
|
135
140
|
end
|
|
136
141
|
end
|
|
142
|
+
nil
|
|
137
143
|
end
|
|
138
144
|
# rubocop:enable Metrics/MethodLength
|
|
139
145
|
|
|
@@ -37,7 +37,7 @@ module RuboCop
|
|
|
37
37
|
return unless (receiver = node.receiver) && receiver.source == 'IO'
|
|
38
38
|
|
|
39
39
|
argument = node.first_argument
|
|
40
|
-
return if argument
|
|
40
|
+
return if argument&.str_type? && argument.value.strip.start_with?('|')
|
|
41
41
|
|
|
42
42
|
add_offense(node, message: format(MSG, method_name: node.method_name)) do |corrector|
|
|
43
43
|
corrector.replace(receiver, 'File')
|
|
@@ -25,7 +25,7 @@ module RuboCop
|
|
|
25
25
|
# @!method marshal_load(node)
|
|
26
26
|
def_node_matcher :marshal_load, <<~PATTERN
|
|
27
27
|
(send (const {nil? cbase} :Marshal) ${:load :restore}
|
|
28
|
-
!(send (const {nil? cbase} :Marshal) :dump ...))
|
|
28
|
+
!(send (const {nil? cbase} :Marshal) :dump ...) _?)
|
|
29
29
|
PATTERN
|
|
30
30
|
|
|
31
31
|
def on_send(node)
|
|
@@ -203,13 +203,23 @@ module RuboCop
|
|
|
203
203
|
end
|
|
204
204
|
|
|
205
205
|
def range_with_trailing_argument_comment(node)
|
|
206
|
-
comment =
|
|
206
|
+
comment = trailing_argument_comment(node)
|
|
207
207
|
if comment
|
|
208
208
|
add_range(node.source_range, comment.source_range)
|
|
209
209
|
else
|
|
210
210
|
node
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
|
+
|
|
214
|
+
# For a single-line declaration the parser associates the trailing
|
|
215
|
+
# comment with the first argument, not `last_argument`, so look through
|
|
216
|
+
# all arguments for a comment that trails the whole node.
|
|
217
|
+
def trailing_argument_comment(node)
|
|
218
|
+
comments = node.arguments.filter_map do |argument|
|
|
219
|
+
processed_source.ast_with_comments[argument].last
|
|
220
|
+
end
|
|
221
|
+
comments.find { |comment| comment.source_range.begin_pos >= node.source_range.end_pos }
|
|
222
|
+
end
|
|
213
223
|
end
|
|
214
224
|
end
|
|
215
225
|
end
|
|
@@ -71,7 +71,7 @@ module RuboCop
|
|
|
71
71
|
node.each_child_node do |expr|
|
|
72
72
|
if expr.send_type?
|
|
73
73
|
correct_send(expr, corrector)
|
|
74
|
-
elsif expr.
|
|
74
|
+
elsif expr.type?(:return, :next, :break, :yield) || expr.assignment?
|
|
75
75
|
correct_other(expr, corrector)
|
|
76
76
|
end
|
|
77
77
|
end
|
|
@@ -40,8 +40,9 @@ module RuboCop
|
|
|
40
40
|
|
|
41
41
|
node = innermost_braces_node(node)
|
|
42
42
|
return if node.parent && brace_method?(node.parent)
|
|
43
|
+
return if compound_assignment_target?(node)
|
|
43
44
|
|
|
44
|
-
preferred = (value
|
|
45
|
+
preferred = preferred_name(value)
|
|
45
46
|
offense_range = find_offense_range(node)
|
|
46
47
|
|
|
47
48
|
add_offense(offense_range, message: format(MSG, preferred: preferred)) do |corrector|
|
|
@@ -53,6 +54,10 @@ module RuboCop
|
|
|
53
54
|
|
|
54
55
|
private
|
|
55
56
|
|
|
57
|
+
def preferred_name(value)
|
|
58
|
+
value.zero? ? 'first' : 'last'
|
|
59
|
+
end
|
|
60
|
+
|
|
56
61
|
def preferred_value(node, value)
|
|
57
62
|
value = ".#{value}" unless node.loc.dot
|
|
58
63
|
value
|
|
@@ -74,6 +79,12 @@ module RuboCop
|
|
|
74
79
|
def brace_method?(node)
|
|
75
80
|
node.send_type? && (node.method?(:[]) || node.method?(:[]=))
|
|
76
81
|
end
|
|
82
|
+
|
|
83
|
+
# `arr[0] += 1` etc. would autocorrect to `arr.first += 1`, which calls the
|
|
84
|
+
# nonexistent `first=`/`last=` setter and raises `NoMethodError`.
|
|
85
|
+
def compound_assignment_target?(node)
|
|
86
|
+
node.parent&.type?(:op_asgn, :or_asgn, :and_asgn) && node.parent.children.first == node
|
|
87
|
+
end
|
|
77
88
|
end
|
|
78
89
|
end
|
|
79
90
|
end
|
|
@@ -47,7 +47,7 @@ module RuboCop
|
|
|
47
47
|
#
|
|
48
48
|
# # bad
|
|
49
49
|
# array1.any? { |elem| array2.member?(elem) }
|
|
50
|
-
# array1.none? { |elem| array2.
|
|
50
|
+
# array1.none? { |elem| array2.include?(elem) }
|
|
51
51
|
#
|
|
52
52
|
# # good
|
|
53
53
|
# array1.intersect?(array2)
|
|
@@ -121,15 +121,15 @@ module RuboCop
|
|
|
121
121
|
(block
|
|
122
122
|
(call $_receiver ${:any? :none?})
|
|
123
123
|
(args (arg _key))
|
|
124
|
-
(send $_argument :member? (lvar _key))
|
|
124
|
+
(send $_argument {:member? :include?} (lvar _key))
|
|
125
125
|
)
|
|
126
126
|
(numblock
|
|
127
127
|
(call $_receiver ${:any? :none?}) 1
|
|
128
|
-
(send $_argument :member? (lvar :_1))
|
|
128
|
+
(send $_argument {:member? :include?} (lvar :_1))
|
|
129
129
|
)
|
|
130
130
|
(itblock
|
|
131
131
|
(call $_receiver ${:any? :none?}) :it
|
|
132
|
-
(send $_argument :member? (lvar :it))
|
|
132
|
+
(send $_argument {:member? :include?} (lvar :it))
|
|
133
133
|
)
|
|
134
134
|
}
|
|
135
135
|
PATTERN
|
|
@@ -145,6 +145,10 @@ module RuboCop
|
|
|
145
145
|
|
|
146
146
|
dot = dot_node.loc.dot.source
|
|
147
147
|
bang = straight?(method_name) ? '' : '!'
|
|
148
|
+
# `a&.intersection(b)&.none?` returns `nil` when `a` is `nil`, but the negated
|
|
149
|
+
# rewrite `!a&.intersect?(b)` returns `true` there, flipping the result.
|
|
150
|
+
return if bang == '!' && dot == '&.'
|
|
151
|
+
|
|
148
152
|
replacement = "#{bang}#{receiver.source}#{dot}intersect?(#{argument.source})"
|
|
149
153
|
|
|
150
154
|
register_offense(node, replacement)
|
|
@@ -29,6 +29,9 @@ module RuboCop
|
|
|
29
29
|
def on_send(node)
|
|
30
30
|
array, element = single_element(node)
|
|
31
31
|
return unless array
|
|
32
|
+
# `[*foo]` is not a single element: the splat can expand to any number of
|
|
33
|
+
# elements, so `intersect?([*foo])` is not equivalent to `include?(*foo)`.
|
|
34
|
+
return if element.splat_type?
|
|
32
35
|
|
|
33
36
|
add_offense(
|
|
34
37
|
node.source_range.with(begin_pos: node.loc.selector.begin_pos)
|
|
@@ -389,9 +389,23 @@ module RuboCop
|
|
|
389
389
|
|
|
390
390
|
def require_do_end?(node)
|
|
391
391
|
return false if node.braces? || node.multiline?
|
|
392
|
-
return false unless (resbody = node.each_descendant(:resbody).first)
|
|
393
392
|
|
|
394
|
-
|
|
393
|
+
body = node.body
|
|
394
|
+
return false unless body
|
|
395
|
+
# `ensure` and a block-level `rescue` are illegal inside `{ }`; only a
|
|
396
|
+
# bare modifier rescue (`expr rescue expr`) can be written with braces.
|
|
397
|
+
return true if body.ensure_type?
|
|
398
|
+
return false unless body.rescue_type?
|
|
399
|
+
|
|
400
|
+
!modifier_rescue?(body)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def modifier_rescue?(rescue_node)
|
|
404
|
+
return false if rescue_node.body.nil? || rescue_node.else_branch
|
|
405
|
+
return false unless rescue_node.resbody_branches.one?
|
|
406
|
+
|
|
407
|
+
resbody = rescue_node.resbody_branches.first
|
|
408
|
+
resbody.exceptions.empty? && resbody.exception_variable.nil?
|
|
395
409
|
end
|
|
396
410
|
|
|
397
411
|
def braces_required_method?(method_name)
|