rubocop 1.87.0 → 1.88.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/default.yml +78 -72
- data/config/obsoletion.yml +21 -1
- data/lib/rubocop/cli/command/auto_generate_config.rb +6 -0
- data/lib/rubocop/cop/base.rb +17 -2
- data/lib/rubocop/cop/bundler/gem_comment.rb +5 -3
- 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/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/condition_position.rb +13 -3
- 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/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 +0 -1
- 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 +3 -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/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/useless_assignment.rb +10 -5
- 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/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/hash_subset.rb +8 -0
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +4 -0
- 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/array_first_last.rb +12 -1
- data/lib/rubocop/cop/style/array_intersect.rb +4 -0
- 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/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/dig_chain.rb +5 -0
- data/lib/rubocop/cop/style/dir_empty.rb +4 -0
- 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_slice.rb +16 -0
- data/lib/rubocop/cop/style/hash_syntax.rb +2 -0
- data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -1
- 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/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/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 +6 -2
- data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/min_max_comparison.rb +3 -0
- 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_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_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/ternary_parentheses.rb +11 -0
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +7 -8
- 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/formatter/disabled_config_formatter.rb +14 -7
- data/lib/rubocop/runner.rb +5 -3
- data/lib/rubocop/server/core.rb +6 -0
- data/lib/rubocop/version.rb +1 -1
- metadata +3 -3
|
@@ -70,7 +70,7 @@ module RuboCop
|
|
|
70
70
|
|
|
71
71
|
def allowed_var?(node)
|
|
72
72
|
env_key_node = node.children.last
|
|
73
|
-
env_key_node.str_type? && cop_config['
|
|
73
|
+
env_key_node.str_type? && cop_config['AllowedVariables'].include?(env_key_node.value)
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def used_as_flag?(node)
|
|
@@ -78,10 +78,12 @@ module RuboCop
|
|
|
78
78
|
|
|
79
79
|
def acceptable?(node)
|
|
80
80
|
# Using a hardcoded null device is acceptable when inside an array or
|
|
81
|
-
# inside a hash to ensure behavior doesn't change.
|
|
81
|
+
# inside a hash to ensure behavior doesn't change. A `str` that is part of
|
|
82
|
+
# an interpolated or concatenated string (`dstr`) is not a standalone null
|
|
83
|
+
# device either, and replacing it would corrupt the surrounding string.
|
|
82
84
|
return false unless node.parent
|
|
83
85
|
|
|
84
|
-
node.parent.type?(:array, :pair)
|
|
86
|
+
node.parent.type?(:array, :pair, :dstr)
|
|
85
87
|
end
|
|
86
88
|
end
|
|
87
89
|
end
|
|
@@ -104,30 +104,33 @@ module RuboCop
|
|
|
104
104
|
|
|
105
105
|
def replacement(mode, filename, content, write_node)
|
|
106
106
|
replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
|
|
107
|
-
|
|
108
|
-
return replacement
|
|
107
|
+
heredocs = removed_heredocs(filename, content, write_node)
|
|
108
|
+
return replacement if heredocs.empty?
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
#{replacement}
|
|
112
|
-
#{heredoc_range(heredoc).source}
|
|
113
|
-
REPLACEMENT
|
|
110
|
+
[replacement, *heredocs.map { |heredoc| heredoc_range(heredoc).source }].join("\n")
|
|
114
111
|
end
|
|
115
112
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
# Heredocs opened in the arguments keep working in the replacement, but their
|
|
114
|
+
# bodies are lost when they lie within the replaced range, so they need to be
|
|
115
|
+
# restored after the replacement.
|
|
116
|
+
def removed_heredocs(filename, content, write_node)
|
|
117
|
+
[filename, content].flat_map { |argument| find_heredocs(argument) }
|
|
118
|
+
.select { |heredoc| removed?(heredoc, write_node) }
|
|
119
|
+
.sort_by { |heredoc| heredoc.loc.heredoc_body.begin_pos }
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def heredoc_range(heredoc)
|
|
123
123
|
range_between(heredoc.loc.heredoc_body.begin_pos, heredoc.loc.heredoc_end.end_pos)
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
def find_heredocs(node)
|
|
127
|
+
[node, *node.each_descendant(:any_str)].select do |child|
|
|
128
|
+
child.respond_to?(:heredoc?) && child.heredoc?
|
|
129
|
+
end
|
|
130
|
+
end
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
def removed?(heredoc, write_node)
|
|
133
|
+
heredoc.loc.heredoc_end.end_pos <= write_node.source_range.end_pos
|
|
131
134
|
end
|
|
132
135
|
end
|
|
133
136
|
end
|
|
@@ -144,10 +144,22 @@ module RuboCop
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def format_single_parameter(arg)
|
|
147
|
+
# `format(fmt, *args)` is equivalent to `fmt % args`, so unwrap the splat
|
|
148
|
+
# and render the argument it splats.
|
|
149
|
+
return format_single_parameter(arg.children.first) if arg.splat_type?
|
|
150
|
+
|
|
147
151
|
source = arg.source
|
|
148
152
|
return "{ #{source} }" if arg.hash_type?
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
requires_parentheses?(arg) ? "(#{source})" : source
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# An argument that binds looser than `%` (a ternary, range, assignment, or
|
|
158
|
+
# operator call) must be parenthesized to keep its meaning.
|
|
159
|
+
def requires_parentheses?(arg)
|
|
160
|
+
return true if arg.assignment? || arg.type?(:if, :and, :or, :range)
|
|
161
|
+
|
|
162
|
+
arg.send_type? && arg.operator_method? && !arg.parenthesized?
|
|
151
163
|
end
|
|
152
164
|
end
|
|
153
165
|
end
|
|
@@ -19,6 +19,22 @@ module RuboCop
|
|
|
19
19
|
# This cop is unsafe because it cannot be guaranteed that the receiver
|
|
20
20
|
# is a `Hash` or responds to the replacement method.
|
|
21
21
|
#
|
|
22
|
+
# Additionally, the replacement may change the order of the resulting
|
|
23
|
+
# hash: `Hash#slice` returns entries in the order the keys are given,
|
|
24
|
+
# whereas `select`, `filter`, and `reject` preserve the entry order of
|
|
25
|
+
# the receiver.
|
|
26
|
+
#
|
|
27
|
+
# For example:
|
|
28
|
+
#
|
|
29
|
+
# [source,ruby]
|
|
30
|
+
# ----
|
|
31
|
+
# hash = {foo: 1, bar: 2, baz: 3}
|
|
32
|
+
# keys = %i[baz foo]
|
|
33
|
+
#
|
|
34
|
+
# hash.select { |k, _v| keys.include?(k) } # => {foo: 1, baz: 3}
|
|
35
|
+
# hash.slice(*keys) # => {baz: 3, foo: 1}
|
|
36
|
+
# ----
|
|
37
|
+
#
|
|
22
38
|
# @example
|
|
23
39
|
#
|
|
24
40
|
# # bad
|
|
@@ -263,6 +263,8 @@ module RuboCop
|
|
|
263
263
|
|
|
264
264
|
hash_node = pair_node.parent
|
|
265
265
|
return unless hash_node.parent&.return_type? && !hash_node.braces?
|
|
266
|
+
# This runs once per pair, but the hash must only be wrapped once.
|
|
267
|
+
return unless pair_node.equal?(hash_node.pairs.first)
|
|
266
268
|
|
|
267
269
|
corrector.wrap(hash_node, '{', '}')
|
|
268
270
|
end
|
|
@@ -86,7 +86,7 @@ module RuboCop
|
|
|
86
86
|
MSG_USE_NORMAL = 'Modifier form of `%<keyword>s` makes the line too long.'
|
|
87
87
|
|
|
88
88
|
def self.autocorrect_incompatible_with
|
|
89
|
-
[Style::SoleNestedConditional]
|
|
89
|
+
[Style::Next, Style::SoleNestedConditional]
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
@@ -82,7 +82,7 @@ module RuboCop
|
|
|
82
82
|
|
|
83
83
|
then_code, else_code = else_code, then_code if node.unless?
|
|
84
84
|
|
|
85
|
-
"#{node
|
|
85
|
+
"#{ternary_condition(node)} ? #{then_code} : #{else_code}"
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def correct_elsif(node)
|
|
@@ -103,6 +103,14 @@ module RuboCop
|
|
|
103
103
|
"#{method.source}(#{arguments.source})"
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
+
# An assignment used as the condition must be parenthesized, otherwise the
|
|
107
|
+
# assignment would capture the whole ternary (`a = b ? c : d` instead of
|
|
108
|
+
# `(a = b) ? c : d`), changing what gets assigned.
|
|
109
|
+
def ternary_condition(node)
|
|
110
|
+
condition = node.condition
|
|
111
|
+
condition.assignment? ? "(#{condition.source})" : condition.source
|
|
112
|
+
end
|
|
113
|
+
|
|
106
114
|
def build_else_branch(second_condition)
|
|
107
115
|
result = <<~RUBY
|
|
108
116
|
elsif #{second_condition.condition.source}
|
|
@@ -26,7 +26,7 @@ module RuboCop
|
|
|
26
26
|
def on_new_investigation
|
|
27
27
|
processed_source.comments.each do |comment|
|
|
28
28
|
next if comment_line?(processed_source[comment.loc.line - 1]) ||
|
|
29
|
-
comment.text.match?(/\A# rubocop:(enable|disable)/)
|
|
29
|
+
comment.text.match?(/\A# rubocop:(enable|disable|todo)/)
|
|
30
30
|
|
|
31
31
|
add_offense(comment)
|
|
32
32
|
end
|
|
@@ -37,6 +37,10 @@ module RuboCop
|
|
|
37
37
|
return unless (ancestor = node.parent&.parent)
|
|
38
38
|
|
|
39
39
|
merge_kwargs?(ancestor) do |merge_node, hash_node, other_hash_node|
|
|
40
|
+
# A block-pass argument (e.g. `merge(other, &block)`) has no keyword
|
|
41
|
+
# equivalent, so spreading it would produce invalid Ruby (`**&block`).
|
|
42
|
+
next if other_hash_node.any?(&:block_pass_type?)
|
|
43
|
+
|
|
40
44
|
add_offense(merge_node) do |corrector|
|
|
41
45
|
autocorrect(corrector, node, hash_node, other_hash_node)
|
|
42
46
|
end
|
|
@@ -62,11 +62,15 @@ module RuboCop
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def append_newline_to_last_kwoptarg(arguments, corrector)
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
# The newline only needs restoring when the moved keyword argument was
|
|
66
|
+
# the last parameter, so removing it also consumes the line break before
|
|
67
|
+
# the body. When a `kwoptarg` already trails the list, the body stays
|
|
68
|
+
# separated and inserting a newline would leave a spurious blank line.
|
|
69
|
+
return unless arguments.last.kwarg_type?
|
|
70
|
+
return if arguments.parent.block_type?
|
|
67
71
|
|
|
68
72
|
last_kwoptarg = arguments.reverse.find(&:kwoptarg_type?)
|
|
69
|
-
corrector.insert_after(last_kwoptarg, "\n")
|
|
73
|
+
corrector.insert_after(last_kwoptarg, "\n")
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
def remove_kwargs(kwarg_nodes, corrector)
|
|
@@ -118,7 +118,13 @@ module RuboCop
|
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def lambda_arg_string(args)
|
|
121
|
-
|
|
121
|
+
# Block-local (shadow) arguments are separated from regular arguments by a
|
|
122
|
+
# `;`; joining everything with `,` would turn them into extra parameters
|
|
123
|
+
# and change the lambda's arity.
|
|
124
|
+
regular, shadow = args.children.partition { |arg| !arg.shadowarg_type? }
|
|
125
|
+
arg_string = regular.map(&:source).join(', ')
|
|
126
|
+
arg_string += "; #{shadow.map(&:source).join(', ')}" unless shadow.empty?
|
|
127
|
+
arg_string
|
|
122
128
|
end
|
|
123
129
|
end
|
|
124
130
|
end
|
|
@@ -6,6 +6,17 @@ module RuboCop
|
|
|
6
6
|
# Prefer `select` or `reject` over `map { ... }.compact`.
|
|
7
7
|
# This cop also handles `filter_map { ... }`, similar to `map { ... }.compact`.
|
|
8
8
|
#
|
|
9
|
+
# @safety
|
|
10
|
+
# This cop is unsafe because `compact` also removes `nil` elements that
|
|
11
|
+
# were already present in the receiver, whereas `select`/`reject` keep
|
|
12
|
+
# them. The result therefore differs when the collection contains `nil`:
|
|
13
|
+
#
|
|
14
|
+
# [source,ruby]
|
|
15
|
+
# ----
|
|
16
|
+
# [nil, 1].map { |e| e if e }.compact # => [1]
|
|
17
|
+
# [nil, 1].select { |e| e } # => [nil, 1]
|
|
18
|
+
# ----
|
|
19
|
+
#
|
|
9
20
|
# @example
|
|
10
21
|
#
|
|
11
22
|
# # bad
|
|
@@ -65,7 +65,7 @@ module RuboCop
|
|
|
65
65
|
|
|
66
66
|
# @!method suitable_argument_node?(node)
|
|
67
67
|
def_node_matcher :suitable_argument_node?, <<-PATTERN
|
|
68
|
-
!{splat forwarded-restarg forwarded-args (hash (forwarded-kwrestarg))
|
|
68
|
+
!{splat forwarded-restarg forwarded-args (hash (forwarded-kwrestarg)) block-pass}
|
|
69
69
|
PATTERN
|
|
70
70
|
|
|
71
71
|
# @!method each_block_with_push?(node)
|
|
@@ -88,8 +88,12 @@ module RuboCop
|
|
|
88
88
|
#
|
|
89
89
|
def parenthesized_it_method_in_block?(node)
|
|
90
90
|
return false unless node.method?(:it)
|
|
91
|
-
return false unless (block_node = node.each_ancestor(:
|
|
92
|
-
|
|
91
|
+
return false unless (block_node = node.each_ancestor(:any_block).first)
|
|
92
|
+
# Inside a numbered/`it` block, a bare `it` is a parse error (it conflicts
|
|
93
|
+
# with the implicit parameter), so `it()` must keep its parentheses.
|
|
94
|
+
if block_node.block_type? && !block_node.arguments.empty_and_without_delimiters?
|
|
95
|
+
return false
|
|
96
|
+
end
|
|
93
97
|
|
|
94
98
|
!node.receiver && node.arguments.empty? && !node.block_literal?
|
|
95
99
|
end
|
|
@@ -166,7 +166,7 @@ module RuboCop
|
|
|
166
166
|
|
|
167
167
|
def anonymous_arguments?(node)
|
|
168
168
|
return true if node.arguments.any? do |arg|
|
|
169
|
-
arg.type?(:
|
|
169
|
+
arg.forward_arg_type? || (arg.type?(:restarg, :kwrestarg) && arg.name.nil?)
|
|
170
170
|
end
|
|
171
171
|
return false unless (last_argument = node.last_argument)
|
|
172
172
|
|
|
@@ -55,8 +55,11 @@ module RuboCop
|
|
|
55
55
|
lhs, operator, rhs = comparison_condition(node.condition)
|
|
56
56
|
return unless operator
|
|
57
57
|
|
|
58
|
+
# For `unless`, the branches run opposite to an `if`, so swap them to
|
|
59
|
+
# keep the `max`/`min` decision correct.
|
|
58
60
|
if_branch = node.if_branch
|
|
59
61
|
else_branch = node.else_branch
|
|
62
|
+
if_branch, else_branch = else_branch, if_branch if node.unless?
|
|
60
63
|
preferred_method = preferred_method(operator, lhs, rhs, if_branch, else_branch)
|
|
61
64
|
return unless preferred_method
|
|
62
65
|
|
|
@@ -65,10 +65,16 @@ module RuboCop
|
|
|
65
65
|
if style == :keyword
|
|
66
66
|
rhs.begin_type?
|
|
67
67
|
else
|
|
68
|
-
|
|
68
|
+
# A `begin` block with `rescue`/`ensure` cannot be expressed with
|
|
69
|
+
# parentheses, so wrapping it in `(` and `)` is not possible.
|
|
70
|
+
rhs.kwbegin_type? && !contains_rescue_or_ensure?(rhs)
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
|
|
74
|
+
def contains_rescue_or_ensure?(node)
|
|
75
|
+
node.each_child_node(:rescue, :ensure).any?
|
|
76
|
+
end
|
|
77
|
+
|
|
72
78
|
def keyword_autocorrect(node, corrector)
|
|
73
79
|
node_buf = node.source_range.source_buffer
|
|
74
80
|
corrector.replace(node.loc.begin, keyword_begin_str(node, node_buf))
|
|
@@ -51,10 +51,12 @@ module RuboCop
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
arguments_range = range_with_surrounding_space(arguments_range(node), side: :left)
|
|
54
|
-
# If the method name isn't on the same line as def
|
|
54
|
+
# If the method name isn't on the same line as `def`, pull the name and
|
|
55
|
+
# the opening parenthesis up next to `def` so the collapsed signature
|
|
56
|
+
# stays on a single line and remains valid Ruby.
|
|
55
57
|
if arguments_range.first_line != opening_line(node)
|
|
56
|
-
|
|
57
|
-
corrector.
|
|
58
|
+
prefix_range = range_between(node.loc.keyword.end_pos, begin_of_arguments.begin_pos)
|
|
59
|
+
corrector.replace(prefix_range, " #{prefix_range.source.strip}")
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
corrector.remove(arguments_range)
|
|
@@ -85,7 +87,12 @@ module RuboCop
|
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
def definition_width(node)
|
|
88
|
-
|
|
90
|
+
# Measure the collapsed single-line width the autocorrect would
|
|
91
|
+
# produce, not the multi-line source length, so a signature that
|
|
92
|
+
# would fit on one line is not skipped.
|
|
93
|
+
signature = node.source_range.begin.join(node.arguments.source_range.end).source
|
|
94
|
+
|
|
95
|
+
signature.gsub(/\s+/, ' ').length
|
|
89
96
|
end
|
|
90
97
|
end
|
|
91
98
|
end
|
|
@@ -6,6 +6,14 @@ module RuboCop
|
|
|
6
6
|
# Checks whether some constant value isn't a
|
|
7
7
|
# mutable literal (e.g. array or hash).
|
|
8
8
|
#
|
|
9
|
+
# When the `Recursive` option is enabled, mutable literals nested inside
|
|
10
|
+
# arrays and hashes are also frozen, so an offense on the outermost
|
|
11
|
+
# unfrozen literal will autocorrect every nested mutable literal as well.
|
|
12
|
+
# When the outer literal already has `.freeze` appended, the cop descends
|
|
13
|
+
# into it and reports each outermost unfrozen literal underneath. The
|
|
14
|
+
# option is disabled by default to preserve existing behavior; opt in to
|
|
15
|
+
# get strict nested freezing.
|
|
16
|
+
#
|
|
9
17
|
# Strict mode can be used to freeze all constants, rather than
|
|
10
18
|
# just literals.
|
|
11
19
|
# Strict mode is considered an experimental feature. It has not been
|
|
@@ -49,6 +57,17 @@ module RuboCop
|
|
|
49
57
|
# CONST = Something.new
|
|
50
58
|
#
|
|
51
59
|
#
|
|
60
|
+
# @example Recursive: false (default)
|
|
61
|
+
# # good - only the outer container needs to be frozen
|
|
62
|
+
# CONST = [{ a: [], b: 'foo' }].freeze
|
|
63
|
+
#
|
|
64
|
+
# @example Recursive: true
|
|
65
|
+
# # bad - nested mutable literals must be frozen too
|
|
66
|
+
# CONST = [{ a: [], b: 'foo' }].freeze
|
|
67
|
+
#
|
|
68
|
+
# # good
|
|
69
|
+
# CONST = [{ a: [].freeze, b: 'foo'.freeze }.freeze].freeze
|
|
70
|
+
#
|
|
52
71
|
# @example EnforcedStyle: strict
|
|
53
72
|
# # bad
|
|
54
73
|
# CONST = Something.new
|
|
@@ -138,10 +157,30 @@ module RuboCop
|
|
|
138
157
|
private
|
|
139
158
|
|
|
140
159
|
def on_assignment(value)
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
nodes = mutable_nodes(value) do |node|
|
|
161
|
+
if style == :strict
|
|
162
|
+
strict_check(node)
|
|
163
|
+
else
|
|
164
|
+
literal_check(node)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
nodes.each do |node|
|
|
169
|
+
add_offense(node) { |corrector| autocorrect(corrector, node) }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def mutable_nodes(value, &block)
|
|
174
|
+
if recursive? && explicitly_frozen_literal?(value)
|
|
175
|
+
literal_children(value.receiver).flat_map { |c| mutable_nodes(c, &block) }
|
|
143
176
|
else
|
|
144
|
-
|
|
177
|
+
node_offending = yield(value)
|
|
178
|
+
|
|
179
|
+
if node_offending
|
|
180
|
+
[value]
|
|
181
|
+
else
|
|
182
|
+
[]
|
|
183
|
+
end
|
|
145
184
|
end
|
|
146
185
|
end
|
|
147
186
|
|
|
@@ -151,18 +190,20 @@ module RuboCop
|
|
|
151
190
|
return if frozen_string_literal?(value)
|
|
152
191
|
return if shareable_constant_value?(value)
|
|
153
192
|
|
|
154
|
-
|
|
193
|
+
true
|
|
155
194
|
end
|
|
156
195
|
|
|
157
|
-
def
|
|
158
|
-
|
|
159
|
-
return unless mutable_literal?(value) ||
|
|
160
|
-
(target_ruby_version <= 2.7 && range_enclosed_in_parentheses)
|
|
161
|
-
|
|
196
|
+
def literal_check(value)
|
|
197
|
+
return unless mutable_or_unfrozen_range?(value)
|
|
162
198
|
return if frozen_string_literal?(value)
|
|
163
199
|
return if shareable_constant_value?(value)
|
|
164
200
|
|
|
165
|
-
|
|
201
|
+
true
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def mutable_or_unfrozen_range?(value)
|
|
205
|
+
mutable_literal?(value) ||
|
|
206
|
+
(target_ruby_version <= 2.7 && range_enclosed_in_parentheses?(value))
|
|
166
207
|
end
|
|
167
208
|
|
|
168
209
|
def autocorrect(corrector, node)
|
|
@@ -171,13 +212,66 @@ module RuboCop
|
|
|
171
212
|
splat_value = splat_value(node)
|
|
172
213
|
if splat_value
|
|
173
214
|
correct_splat_expansion(corrector, expr, splat_value)
|
|
174
|
-
|
|
215
|
+
corrector.insert_after(expr, '.freeze')
|
|
216
|
+
return
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if node.array_type? && !node.bracketed?
|
|
175
220
|
corrector.wrap(expr, '[', ']')
|
|
176
221
|
elsif requires_parentheses?(node)
|
|
177
222
|
corrector.wrap(expr, '(', ')')
|
|
178
223
|
end
|
|
179
224
|
|
|
180
225
|
corrector.insert_after(expr, '.freeze')
|
|
226
|
+
|
|
227
|
+
freeze_nested_literals(corrector, node) if recursive?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Recursively freezes every nested mutable literal inside an array or
|
|
231
|
+
# hash literal. Already-frozen subtrees are not re-frozen, but their
|
|
232
|
+
# children are still inspected for unfrozen literals deeper down.
|
|
233
|
+
def freeze_nested_literals(corrector, node)
|
|
234
|
+
literal_children(node).each do |child|
|
|
235
|
+
if explicitly_frozen_literal?(child)
|
|
236
|
+
freeze_nested_literals(corrector, child.receiver)
|
|
237
|
+
elsif freezable_nested_literal?(child)
|
|
238
|
+
autocorrect(corrector, child)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def freezable_nested_literal?(node)
|
|
244
|
+
return false if frozen_string_literal?(node)
|
|
245
|
+
return false if shareable_constant_value?(node)
|
|
246
|
+
|
|
247
|
+
mutable_literal?(node)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Returns the child literals of an array or hash node that may
|
|
251
|
+
# themselves need freezing. For hashes, both keys and values are
|
|
252
|
+
# included. Percent-literal arrays (e.g. `%w(a b)`) are skipped because
|
|
253
|
+
# `.freeze` cannot be appended to their contents.
|
|
254
|
+
def literal_children(node)
|
|
255
|
+
case node.type
|
|
256
|
+
when :array
|
|
257
|
+
return [] if node.percent_literal?
|
|
258
|
+
|
|
259
|
+
node.children
|
|
260
|
+
when :hash
|
|
261
|
+
node.children.flat_map { |child| child.pair_type? ? child.children : [] }
|
|
262
|
+
else
|
|
263
|
+
[]
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def explicitly_frozen_literal?(node)
|
|
268
|
+
return false unless node.send_type? && node.method?(:freeze)
|
|
269
|
+
|
|
270
|
+
node.receiver && mutable_literal?(node.receiver)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def recursive?
|
|
274
|
+
cop_config.fetch('Recursive', false)
|
|
181
275
|
end
|
|
182
276
|
|
|
183
277
|
def mutable_literal?(value)
|
|
@@ -6,6 +6,11 @@ module RuboCop
|
|
|
6
6
|
# Checks for lambdas and procs that always return nil,
|
|
7
7
|
# which can be replaced with an empty lambda or proc instead.
|
|
8
8
|
#
|
|
9
|
+
# NOTE: A `proc` that returns nil via an explicit `return` is allowed,
|
|
10
|
+
# because in a `proc` `return` exits the enclosing method, so removing it
|
|
11
|
+
# would change behavior. A lambda is still reported, since there `return`
|
|
12
|
+
# only exits the lambda itself.
|
|
13
|
+
#
|
|
9
14
|
# @example
|
|
10
15
|
# # bad
|
|
11
16
|
# -> { nil }
|
|
@@ -46,6 +51,9 @@ module RuboCop
|
|
|
46
51
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
47
52
|
return unless node.lambda_or_proc?
|
|
48
53
|
return unless nil_return?(node.body)
|
|
54
|
+
# `return` inside a non-lambda proc returns from the enclosing method,
|
|
55
|
+
# so dropping it changes behavior; only a lambda can omit it safely.
|
|
56
|
+
return if node.body.return_type? && !node.lambda?
|
|
49
57
|
|
|
50
58
|
message = format(MSG, type: node.lambda? ? 'lambda' : 'proc')
|
|
51
59
|
add_offense(node, message: message) do |corrector|
|
|
@@ -92,7 +92,7 @@ module RuboCop
|
|
|
92
92
|
return unless numeric
|
|
93
93
|
|
|
94
94
|
return if allowed_method_name?(node.method_name) ||
|
|
95
|
-
node.each_ancestor(:send, :
|
|
95
|
+
node.each_ancestor(:send, :any_block).any? do |ancestor|
|
|
96
96
|
allowed_method_name?(ancestor.method_name)
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -38,7 +38,7 @@ module RuboCop
|
|
|
38
38
|
rhs_elements = Array(rhs).compact # edge case for one constant
|
|
39
39
|
|
|
40
40
|
return if allowed_lhs?(node.assignments) || allowed_rhs?(rhs) ||
|
|
41
|
-
allowed_masign?(node.assignments, rhs_elements)
|
|
41
|
+
allowed_masign?(node.assignments, rhs_elements) || contains_heredoc?(rhs)
|
|
42
42
|
|
|
43
43
|
range = node.source_range.begin.join(rhs.source_range.end)
|
|
44
44
|
|
|
@@ -77,6 +77,13 @@ module RuboCop
|
|
|
77
77
|
!node.array_type? || elements.any?(&:splat_type?)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
# Autocorrection splits the assignment into single assignments on
|
|
81
|
+
# consecutive lines, which would put following assignments into the
|
|
82
|
+
# heredoc body unless the heredoc bodies were moved along.
|
|
83
|
+
def contains_heredoc?(node)
|
|
84
|
+
node.each_descendant(:any_str).any?(&:heredoc?)
|
|
85
|
+
end
|
|
86
|
+
|
|
80
87
|
def assignment_corrector(node, rhs, order)
|
|
81
88
|
if node.parent&.rescue_type?
|
|
82
89
|
_assignment, modifier = *node.parent
|
|
@@ -226,14 +233,23 @@ module RuboCop
|
|
|
226
233
|
def source(node, loc)
|
|
227
234
|
# __FILE__ is treated as a StrNode but has no begin
|
|
228
235
|
if node.str_type? && loc.respond_to?(:begin) && loc.begin.nil?
|
|
229
|
-
|
|
236
|
+
# `%w` elements have no per-element delimiter, so the value must be
|
|
237
|
+
# quoted and escaped to stay valid (e.g. `%w(it's)` -> `'it\'s'`).
|
|
238
|
+
quote(node.value)
|
|
230
239
|
elsif node.sym_type? && !node.loc?(:begin)
|
|
231
|
-
|
|
240
|
+
# `%i` elements have no per-element delimiter, so a symbol that needs
|
|
241
|
+
# quoting must be emitted as `:"..."` (e.g. `%i(foo-bar)` -> `:"foo-bar"`),
|
|
242
|
+
# otherwise `:foo-bar` would parse as `:foo.-(bar)`.
|
|
243
|
+
node.value.inspect
|
|
232
244
|
else
|
|
233
245
|
node.source
|
|
234
246
|
end
|
|
235
247
|
end
|
|
236
248
|
|
|
249
|
+
def quote(string)
|
|
250
|
+
"'#{string.gsub(/[\\']/) { |char| "\\#{char}" }}'"
|
|
251
|
+
end
|
|
252
|
+
|
|
237
253
|
def extract_sources(node)
|
|
238
254
|
node.children.map(&:source)
|
|
239
255
|
end
|