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
|
@@ -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
|
|
@@ -5,7 +5,7 @@ module RuboCop
|
|
|
5
5
|
module Style
|
|
6
6
|
# Looks for uses of Perl-style regexp match
|
|
7
7
|
# backreferences and their English versions like
|
|
8
|
-
# $1, $2, $&,
|
|
8
|
+
# $1, $2, $&, $MATCH, $PREMATCH, etc.
|
|
9
9
|
#
|
|
10
10
|
# @example
|
|
11
11
|
# # bad
|
|
@@ -69,6 +69,10 @@ module RuboCop
|
|
|
69
69
|
# @return [String, nil]
|
|
70
70
|
def preferred_expression_to(node)
|
|
71
71
|
first = node.to_a.first
|
|
72
|
+
# NOTE: `$+` / `$LAST_PAREN_MATCH` is deliberately not converted. It
|
|
73
|
+
# refers to the last group that actually matched, which has no concise
|
|
74
|
+
# `Regexp.last_match` equivalent (`Regexp.last_match(-1)` is the last
|
|
75
|
+
# group in the pattern, which may be `nil`).
|
|
72
76
|
case first
|
|
73
77
|
when ::Integer
|
|
74
78
|
"Regexp.last_match(#{first})"
|
|
@@ -78,8 +82,6 @@ module RuboCop
|
|
|
78
82
|
'Regexp.last_match.pre_match'
|
|
79
83
|
when :$', :$POSTMATCH
|
|
80
84
|
'Regexp.last_match.post_match'
|
|
81
|
-
when :$+, :$LAST_PAREN_MATCH
|
|
82
|
-
'Regexp.last_match(-1)'
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
87
|
|
|
@@ -26,7 +26,9 @@ module RuboCop
|
|
|
26
26
|
def on_send(node)
|
|
27
27
|
return unless (first_argument = node.first_argument)
|
|
28
28
|
return unless (index = first_argument.source.index(CURRENT_DIRECTORY_PREFIX))
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
content = leading_path_content(first_argument)
|
|
31
|
+
return unless (redundant_length = redundant_path_length(content))
|
|
30
32
|
|
|
31
33
|
begin_pos = first_argument.source_range.begin.begin_pos + index
|
|
32
34
|
end_pos = begin_pos + redundant_length
|
|
@@ -39,6 +41,16 @@ module RuboCop
|
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
44
|
+
# The literal text at the start of the path, which is the whole string
|
|
45
|
+
# for a plain string and the leading literal segment for an interpolated
|
|
46
|
+
# one (`nil` when it starts with interpolation).
|
|
47
|
+
def leading_path_content(node)
|
|
48
|
+
return node.str_content if node.str_type?
|
|
49
|
+
return unless node.dstr_type? && (first = node.children.first)&.str_type?
|
|
50
|
+
|
|
51
|
+
first.str_content
|
|
52
|
+
end
|
|
53
|
+
|
|
42
54
|
def redundant_path_length(path)
|
|
43
55
|
return unless (match = path&.match(REDUNDANT_CURRENT_DIRECTORY_PREFIX))
|
|
44
56
|
|
|
@@ -38,6 +38,10 @@ module RuboCop
|
|
|
38
38
|
|
|
39
39
|
def fix_exploded(node)
|
|
40
40
|
exploded?(node) do |command, message|
|
|
41
|
+
# `raise RuntimeError, nil` uses the class name as the message, so
|
|
42
|
+
# rewriting it to `raise nil.to_s` (an empty message) would change it.
|
|
43
|
+
next if message.nil_type?
|
|
44
|
+
|
|
41
45
|
add_offense(node, message: MSG_1) do |corrector|
|
|
42
46
|
corrector.replace(node, replaced_exploded(node, command, message))
|
|
43
47
|
end
|
|
@@ -56,6 +60,8 @@ module RuboCop
|
|
|
56
60
|
|
|
57
61
|
def fix_compact(node)
|
|
58
62
|
compact?(node) do |new_call, message|
|
|
63
|
+
next if message.nil_type?
|
|
64
|
+
|
|
59
65
|
add_offense(node, message: MSG_2) do |corrector|
|
|
60
66
|
corrector.replace(new_call, replaced_compact(message))
|
|
61
67
|
end
|
|
@@ -62,7 +62,7 @@ module RuboCop
|
|
|
62
62
|
def_node_matcher :select_predicate?, <<~PATTERN
|
|
63
63
|
(call
|
|
64
64
|
{
|
|
65
|
-
(
|
|
65
|
+
(any_block $(call _ {:select :filter :find_all}) ...)
|
|
66
66
|
$(call _ {:select :filter :find_all} block_pass_type?)
|
|
67
67
|
}
|
|
68
68
|
${:#{RESTRICT_ON_SEND.join(' :')}})
|
|
@@ -89,6 +89,8 @@ module RuboCop
|
|
|
89
89
|
|
|
90
90
|
def on_send(node)
|
|
91
91
|
format_without_additional_args?(node) do |value|
|
|
92
|
+
next if string_with_format_sequence?(value)
|
|
93
|
+
|
|
92
94
|
replacement = escape_control_chars(value.source)
|
|
93
95
|
|
|
94
96
|
add_offense(node, message: message(node, replacement)) do |corrector|
|
|
@@ -102,12 +104,31 @@ module RuboCop
|
|
|
102
104
|
|
|
103
105
|
private
|
|
104
106
|
|
|
107
|
+
# A single-argument `format` whose string still contains a format sequence is
|
|
108
|
+
# not redundant: `format('%s')` raises at runtime, and `format('%%')` returns
|
|
109
|
+
# `'%'`, so replacing it with the literal would change behavior.
|
|
110
|
+
def string_with_format_sequence?(node)
|
|
111
|
+
string = static_string_value(node)
|
|
112
|
+
return false unless string
|
|
113
|
+
|
|
114
|
+
RuboCop::Cop::Utils::FormatString.new(string).format_sequences.any?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def static_string_value(node)
|
|
118
|
+
if node.str_type?
|
|
119
|
+
node.value
|
|
120
|
+
elsif node.dstr_type?
|
|
121
|
+
node.children.select(&:str_type?).map(&:value).join
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
105
125
|
def message(node, prefer)
|
|
106
126
|
format(MSG, prefer: prefer, method_name: node.method_name)
|
|
107
127
|
end
|
|
108
128
|
|
|
109
129
|
def detect_unnecessary_fields(node)
|
|
110
130
|
return unless node.first_argument&.str_type?
|
|
131
|
+
return if node.first_argument.heredoc?
|
|
111
132
|
|
|
112
133
|
string = node.first_argument.value
|
|
113
134
|
arguments = node.arguments[1..]
|
|
@@ -245,6 +266,14 @@ module RuboCop
|
|
|
245
266
|
def argument_value(argument)
|
|
246
267
|
argument = argument.children.first if argument.begin_type?
|
|
247
268
|
|
|
269
|
+
# `nil` formats to an empty string (`format('%s', nil) == ''`), not the
|
|
270
|
+
# literal `'nil'` that `argument.source` would return.
|
|
271
|
+
return if argument.nil_type?
|
|
272
|
+
|
|
273
|
+
typed_argument_value(argument)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def typed_argument_value(argument)
|
|
248
277
|
if argument.dsym_type?
|
|
249
278
|
dsym_value(argument)
|
|
250
279
|
elsif argument.hash_type?
|
|
@@ -48,8 +48,13 @@ module RuboCop
|
|
|
48
48
|
def need_heredoc_delimiter_quotes?(node)
|
|
49
49
|
heredoc_delimiter = node.source.delete(heredoc_type(node))
|
|
50
50
|
return true unless heredoc_delimiter.start_with?("'", '"')
|
|
51
|
+
return true if node.loc.heredoc_end.source.strip.match?(/\W/)
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
# A double-quoted delimiter interpolates exactly like an unquoted one,
|
|
54
|
+
# so its quotes are always redundant. A single-quoted delimiter is
|
|
55
|
+
# required when the body contains interpolation or escapes that would
|
|
56
|
+
# otherwise be evaluated.
|
|
57
|
+
heredoc_delimiter.start_with?("'") &&
|
|
53
58
|
node.loc.heredoc_body.source.match?(STRING_INTERPOLATION_OR_ESCAPED_CHARACTER_PATTERN)
|
|
54
59
|
end
|
|
55
60
|
end
|
|
@@ -165,11 +165,11 @@ module RuboCop
|
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
def inspect_end_of_ruby_code_line_continuation
|
|
168
|
-
|
|
168
|
+
last_line_number = processed_source.ast.last_line
|
|
169
|
+
last_line = processed_source.lines[last_line_number - 1]
|
|
169
170
|
return unless code_ends_with_continuation?(last_line)
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
line_continuation_range = range_between(last_column - 1, last_column)
|
|
172
|
+
line_continuation_range = trailing_line_continuation_range(last_line_number)
|
|
173
173
|
|
|
174
174
|
add_offense(line_continuation_range) do |corrector|
|
|
175
175
|
corrector.remove_trailing(line_continuation_range, 1)
|
|
@@ -182,6 +182,14 @@ module RuboCop
|
|
|
182
182
|
last_line.end_with?(LINE_CONTINUATION)
|
|
183
183
|
end
|
|
184
184
|
|
|
185
|
+
# The backslash is the last character of the line; locate it by the line's
|
|
186
|
+
# position in the buffer rather than treating the column as an absolute offset
|
|
187
|
+
# (which corrupts an earlier line in multi-line files).
|
|
188
|
+
def trailing_line_continuation_range(line_number)
|
|
189
|
+
line_range = processed_source.buffer.line_range(line_number)
|
|
190
|
+
range_between(line_range.end_pos - 1, line_range.end_pos)
|
|
191
|
+
end
|
|
192
|
+
|
|
185
193
|
def inside_string_literal?(range, token)
|
|
186
194
|
ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
|
|
187
195
|
end
|
|
@@ -65,7 +65,7 @@ module RuboCop
|
|
|
65
65
|
# different versions of Ruby so that e.g. /\i/ != /i/
|
|
66
66
|
return true if /[[:alnum:]]/.match?(char)
|
|
67
67
|
return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char)
|
|
68
|
-
return true if requires_escape_to_avoid_interpolation?(node
|
|
68
|
+
return true if requires_escape_to_avoid_interpolation?(node, index, char)
|
|
69
69
|
|
|
70
70
|
if within_character_class
|
|
71
71
|
ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) &&
|
|
@@ -97,10 +97,14 @@ module RuboCop
|
|
|
97
97
|
delimiters.include?(char)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
def requires_escape_to_avoid_interpolation?(
|
|
100
|
+
def requires_escape_to_avoid_interpolation?(node, index, escaped_char)
|
|
101
101
|
# Preserve escapes after '#' that would otherwise trigger interpolation:
|
|
102
|
-
# '#@ivar', '#@@cvar', and '#$gvar'.
|
|
103
|
-
|
|
102
|
+
# '#@ivar', '#@@cvar', and '#$gvar'. `index` is relative to the regexp
|
|
103
|
+
# contents, so index into those rather than `node.source` (which also
|
|
104
|
+
# includes the opening delimiter, e.g. `%r{`).
|
|
105
|
+
return false unless index.positive? && INTERPOLATION_SIGILS.include?(escaped_char)
|
|
106
|
+
|
|
107
|
+
contents_range(node).source[index - 1] == '#'
|
|
104
108
|
end
|
|
105
109
|
|
|
106
110
|
def each_escape(node)
|
|
@@ -100,6 +100,15 @@ module RuboCop
|
|
|
100
100
|
add_lhs_to_local_variables_scopes(node.rhs, node.lhs)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
# Register the exception variable of `rescue => e` so that `self.e` in the
|
|
104
|
+
# body is not treated as redundant (it disambiguates the local variable).
|
|
105
|
+
def on_resbody(node)
|
|
106
|
+
exception_variable = node.exception_variable
|
|
107
|
+
return unless exception_variable&.lvasgn_type?
|
|
108
|
+
|
|
109
|
+
@local_variables_scopes[node] << exception_variable.name
|
|
110
|
+
end
|
|
111
|
+
|
|
103
112
|
def on_in_pattern(node)
|
|
104
113
|
add_match_var_scopes(node)
|
|
105
114
|
end
|
|
@@ -100,12 +100,31 @@ module RuboCop
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def range(redundant_keyword_init)
|
|
103
|
-
if redundant_keyword_init.parent.
|
|
104
|
-
|
|
103
|
+
if redundant_keyword_init.parent.pairs.all? { |pair| keyword_init?(pair) }
|
|
104
|
+
# The hash holds only `keyword_init` pairs, so it is emptied; anchor the
|
|
105
|
+
# removal before the hash to also drop the comma that precedes it.
|
|
106
|
+
range_emptying_hash(redundant_keyword_init)
|
|
107
|
+
else
|
|
108
|
+
# Other pairs remain, so just drop this pair and one adjacent comma.
|
|
109
|
+
range_within_hash(redundant_keyword_init)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def range_within_hash(pair)
|
|
114
|
+
if (left = pair.left_sibling)
|
|
115
|
+
left.source_range.end.join(pair.source_range.end)
|
|
116
|
+
elsif (right = pair.right_sibling)
|
|
117
|
+
pair.source_range.begin.join(right.source_range.begin)
|
|
118
|
+
else
|
|
119
|
+
pair.source_range
|
|
120
|
+
end
|
|
121
|
+
end
|
|
105
122
|
|
|
106
|
-
|
|
123
|
+
def range_emptying_hash(pair)
|
|
124
|
+
if (preceding = pair.parent.left_sibling).is_a?(AST::Node)
|
|
125
|
+
preceding.source_range.end.join(pair.source_range.end)
|
|
107
126
|
else
|
|
108
|
-
|
|
127
|
+
pair.source_range
|
|
109
128
|
end
|
|
110
129
|
end
|
|
111
130
|
end
|
|
@@ -128,7 +128,7 @@ module RuboCop
|
|
|
128
128
|
|
|
129
129
|
add_offense(range) do |corrector|
|
|
130
130
|
if after_expression
|
|
131
|
-
corrector
|
|
131
|
+
replace_semicolon_with_line_break(corrector, range)
|
|
132
132
|
else
|
|
133
133
|
# Prevents becoming one range instance with subsequent line when endless range
|
|
134
134
|
# without parentheses.
|
|
@@ -148,6 +148,21 @@ module RuboCop
|
|
|
148
148
|
end
|
|
149
149
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
150
150
|
|
|
151
|
+
def replace_semicolon_with_line_break(corrector, range)
|
|
152
|
+
# Replacing the semicolon with a newline would move the rest of the
|
|
153
|
+
# line into the body of a heredoc opened earlier on that line.
|
|
154
|
+
return if heredoc_opened_before_semicolon?(range)
|
|
155
|
+
|
|
156
|
+
corrector.replace(range, "\n")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def heredoc_opened_before_semicolon?(semicolon_range)
|
|
160
|
+
processed_source.ast.each_descendant(:any_str).select(&:heredoc?).any? do |heredoc|
|
|
161
|
+
heredoc.first_line == semicolon_range.line &&
|
|
162
|
+
heredoc.source_range.end_pos <= semicolon_range.begin_pos
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
151
166
|
def expressions_per_line(exprs)
|
|
152
167
|
# create a map matching lines to the number of expressions on them
|
|
153
168
|
exprs_lines = exprs.map(&:last_line)
|
|
@@ -155,10 +170,10 @@ module RuboCop
|
|
|
155
170
|
end
|
|
156
171
|
|
|
157
172
|
def find_semicolon_positions(line)
|
|
158
|
-
# Scan for all the
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
yield
|
|
173
|
+
# Scan for all the semicolon tokens on the line. Iterating tokens rather
|
|
174
|
+
# than the raw source skips `;` characters inside string/regexp literals.
|
|
175
|
+
processed_source.tokens.each do |token|
|
|
176
|
+
yield token.column if token.line == line && token.semicolon?
|
|
162
177
|
end
|
|
163
178
|
end
|
|
164
179
|
|
|
@@ -44,11 +44,12 @@ module RuboCop
|
|
|
44
44
|
add_offense(node) do |corrector|
|
|
45
45
|
corrector.insert_after(do_line(node), "\n")
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
if (heredoc = trailing_heredoc(node.body))
|
|
48
|
+
# The heredoc body extends past the `end` on the source, so the
|
|
49
|
+
# `end` has to be moved after it rather than before, which would
|
|
50
|
+
# otherwise move it into the heredoc body and break the syntax.
|
|
50
51
|
corrector.remove(node.loc.end)
|
|
51
|
-
corrector.insert_after(
|
|
52
|
+
corrector.insert_after(heredoc.loc.heredoc_end, "\nend")
|
|
52
53
|
else
|
|
53
54
|
corrector.insert_before(node.loc.end, "\n")
|
|
54
55
|
end
|
|
@@ -60,6 +61,18 @@ module RuboCop
|
|
|
60
61
|
|
|
61
62
|
private
|
|
62
63
|
|
|
64
|
+
# Returns the heredoc opened on the block's line whose body extends the
|
|
65
|
+
# furthest down, whether it is the block body itself or nested within it.
|
|
66
|
+
def trailing_heredoc(node_body)
|
|
67
|
+
return unless node_body
|
|
68
|
+
|
|
69
|
+
heredocs = [node_body, *node_body.each_descendant].select do |node|
|
|
70
|
+
node.respond_to?(:heredoc?) && node.heredoc?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
heredocs.max_by { |heredoc| heredoc.loc.heredoc_end.line }
|
|
74
|
+
end
|
|
75
|
+
|
|
63
76
|
def do_line(node)
|
|
64
77
|
if node.type?(:numblock, :itblock) ||
|
|
65
78
|
node.arguments.children.empty? || node.send_node.lambda_literal?
|
|
@@ -41,7 +41,9 @@ module RuboCop
|
|
|
41
41
|
return unless struct_constructor?(node.parent_class)
|
|
42
42
|
|
|
43
43
|
add_offense(node.parent_class) do |corrector|
|
|
44
|
-
corrector.remove(
|
|
44
|
+
corrector.remove(
|
|
45
|
+
range_with_surrounding_space(node.loc.keyword, side: :right, newlines: false)
|
|
46
|
+
)
|
|
45
47
|
corrector.replace(node.loc.operator, '=')
|
|
46
48
|
|
|
47
49
|
correct_parent(node.parent_class, corrector)
|