rubocop 1.88.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 +5 -1
- data/lib/rubocop/cop/base.rb +13 -8
- data/lib/rubocop/cop/bundler/gem_comment.rb +3 -1
- 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/layout/block_alignment.rb +17 -0
- 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/assignment_in_condition.rb +13 -1
- data/lib/rubocop/cop/lint/debugger.rb +13 -1
- data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +7 -0
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -1
- data/lib/rubocop/cop/lint/to_json.rb +8 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +32 -8
- data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
- data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
- data/lib/rubocop/cop/lint/void.rb +2 -2
- 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/arguments_forwarding.rb +1 -1
- data/lib/rubocop/cop/style/array_intersect.rb +4 -4
- data/lib/rubocop/cop/style/collection_compact.rb +1 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +4 -0
- data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
- 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/file_null.rb +4 -2
- 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_syntax.rb +2 -0
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
- data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -4
- 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/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 +11 -2
- 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 +28 -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 +4 -4
- 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/team.rb +18 -4
- data/lib/rubocop/runner.rb +55 -13
- data/lib/rubocop/version.rb +1 -1
- metadata +2 -2
|
@@ -50,9 +50,11 @@ module RuboCop
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
def register_offense(node)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
range = offense_range(node)
|
|
54
|
+
return if processed_source.contains_comment?(range)
|
|
55
|
+
|
|
56
|
+
add_offense(range) do |corrector|
|
|
57
|
+
corrector.remove(range)
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
60
|
|
|
@@ -88,8 +90,12 @@ module RuboCop
|
|
|
88
90
|
#
|
|
89
91
|
def parenthesized_it_method_in_block?(node)
|
|
90
92
|
return false unless node.method?(:it)
|
|
91
|
-
return false unless (block_node = node.each_ancestor(:
|
|
92
|
-
|
|
93
|
+
return false unless (block_node = node.each_ancestor(:any_block).first)
|
|
94
|
+
# Inside a numbered/`it` block, a bare `it` is a parse error (it conflicts
|
|
95
|
+
# with the implicit parameter), so `it()` must keep its parentheses.
|
|
96
|
+
if block_node.block_type? && !block_node.arguments.empty_and_without_delimiters?
|
|
97
|
+
return false
|
|
98
|
+
end
|
|
93
99
|
|
|
94
100
|
!node.receiver && node.arguments.empty? && !node.block_literal?
|
|
95
101
|
end
|
|
@@ -102,6 +102,10 @@ module RuboCop
|
|
|
102
102
|
MSG_PRESENT = 'Use def without parentheses.'
|
|
103
103
|
MSG_MISSING = 'Use def with parentheses when there are parameters.'
|
|
104
104
|
|
|
105
|
+
def self.autocorrect_incompatible_with
|
|
106
|
+
[Style::ArgumentsForwarding]
|
|
107
|
+
end
|
|
108
|
+
|
|
105
109
|
def on_def(node)
|
|
106
110
|
args = node.arguments
|
|
107
111
|
|
|
@@ -166,7 +170,7 @@ module RuboCop
|
|
|
166
170
|
|
|
167
171
|
def anonymous_arguments?(node)
|
|
168
172
|
return true if node.arguments.any? do |arg|
|
|
169
|
-
arg.type?(:
|
|
173
|
+
arg.forward_arg_type? || (arg.type?(:restarg, :kwrestarg) && arg.name.nil?)
|
|
170
174
|
end
|
|
171
175
|
return false unless (last_argument = node.last_argument)
|
|
172
176
|
|
|
@@ -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,16 +65,19 @@ module RuboCop
|
|
|
65
65
|
private
|
|
66
66
|
|
|
67
67
|
def implements_respond_to_missing?(node)
|
|
68
|
-
|
|
68
|
+
scope = enclosing_scope(node)
|
|
69
|
+
search_root = scope || node.parent
|
|
70
|
+
return false unless search_root
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
child = descendant.children.first
|
|
74
|
-
return true if child.respond_to?(:method?) && child.method?(:respond_to_missing?)
|
|
72
|
+
search_root.each_descendant(node.type).any? do |descendant|
|
|
73
|
+
descendant.method?(:respond_to_missing?) && enclosing_scope(descendant).equal?(scope)
|
|
75
74
|
end
|
|
75
|
+
end
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
# The class/module/`class << self` body that lexically contains `node`,
|
|
78
|
+
# or `nil` when `node` is defined at the top level.
|
|
79
|
+
def enclosing_scope(node)
|
|
80
|
+
node.each_ancestor(:class, :module, :sclass).first
|
|
78
81
|
end
|
|
79
82
|
end
|
|
80
83
|
end
|
|
@@ -110,9 +110,11 @@ module RuboCop
|
|
|
110
110
|
def_node_matcher :private_directive?, '(send nil? :private ...)'
|
|
111
111
|
|
|
112
112
|
def on_module(node)
|
|
113
|
-
return unless node.body
|
|
113
|
+
return unless node.body
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
body_nodes = node.body.begin_type? ? node.body.children : [node.body]
|
|
116
|
+
|
|
117
|
+
each_wrong_style(body_nodes) do |child_node|
|
|
116
118
|
add_offense(child_node) do |corrector|
|
|
117
119
|
next if style == :forbidden
|
|
118
120
|
|
|
@@ -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,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
|
|
|
@@ -233,14 +233,23 @@ module RuboCop
|
|
|
233
233
|
def source(node, loc)
|
|
234
234
|
# __FILE__ is treated as a StrNode but has no begin
|
|
235
235
|
if node.str_type? && loc.respond_to?(:begin) && loc.begin.nil?
|
|
236
|
-
|
|
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)
|
|
237
239
|
elsif node.sym_type? && !node.loc?(:begin)
|
|
238
|
-
|
|
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
|
|
239
244
|
else
|
|
240
245
|
node.source
|
|
241
246
|
end
|
|
242
247
|
end
|
|
243
248
|
|
|
249
|
+
def quote(string)
|
|
250
|
+
"'#{string.gsub(/[\\']/) { |char| "\\#{char}" }}'"
|
|
251
|
+
end
|
|
252
|
+
|
|
244
253
|
def extract_sources(node)
|
|
245
254
|
node.children.map(&:source)
|
|
246
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,6 +104,24 @@ 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
|
|
@@ -246,6 +266,14 @@ module RuboCop
|
|
|
246
266
|
def argument_value(argument)
|
|
247
267
|
argument = argument.children.first if argument.begin_type?
|
|
248
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)
|
|
249
277
|
if argument.dsym_type?
|
|
250
278
|
dsym_value(argument)
|
|
251
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
|
|
@@ -170,10 +170,10 @@ module RuboCop
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def find_semicolon_positions(line)
|
|
173
|
-
# Scan for all the
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
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?
|
|
177
177
|
end
|
|
178
178
|
end
|
|
179
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)
|
|
@@ -113,6 +113,10 @@ module RuboCop
|
|
|
113
113
|
def offense?(node)
|
|
114
114
|
condition = node.condition
|
|
115
115
|
|
|
116
|
+
# A modifier `if`/`unless` requires the parentheses, e.g. `(a if b) ? x : y`,
|
|
117
|
+
# so removing them would change the meaning. Don't flag it.
|
|
118
|
+
return false if parenthesized_modifier_condition?(condition)
|
|
119
|
+
|
|
116
120
|
if safe_assignment?(condition)
|
|
117
121
|
!safe_assignment_allowed?
|
|
118
122
|
else
|
|
@@ -174,6 +178,13 @@ module RuboCop
|
|
|
174
178
|
condition.children.any? { |child| below_ternary_precedence?(child) }
|
|
175
179
|
end
|
|
176
180
|
|
|
181
|
+
def parenthesized_modifier_condition?(condition)
|
|
182
|
+
return false unless condition.begin_type?
|
|
183
|
+
|
|
184
|
+
inner = condition.children.first
|
|
185
|
+
inner&.if_type? && inner.modifier_form?
|
|
186
|
+
end
|
|
187
|
+
|
|
177
188
|
def unparenthesized_method_call?(child)
|
|
178
189
|
/^[a-z]/i.match?(method_name(child)) && !child.parenthesized?
|
|
179
190
|
end
|