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
|
@@ -57,11 +57,15 @@ module RuboCop
|
|
|
57
57
|
private
|
|
58
58
|
|
|
59
59
|
def parentheses_required?(node, arguments_range)
|
|
60
|
-
return true if node.single_line? && !node.endless?
|
|
61
|
-
|
|
62
60
|
end_pos = arguments_range.end.end_pos
|
|
63
61
|
token_after_argument = range_between(end_pos, end_pos + 1).source
|
|
64
62
|
|
|
63
|
+
# A `;` after the parentheses (e.g. `def foo(); end`) already separates the
|
|
64
|
+
# signature from the body, so the parentheses can be removed (`def foo; end`).
|
|
65
|
+
return false if token_after_argument == ';'
|
|
66
|
+
|
|
67
|
+
return true if node.single_line? && !node.endless?
|
|
68
|
+
|
|
65
69
|
token_after_argument == '='
|
|
66
70
|
end
|
|
67
71
|
end
|
|
@@ -35,6 +35,10 @@ module RuboCop
|
|
|
35
35
|
PATTERN
|
|
36
36
|
|
|
37
37
|
def on_send(node)
|
|
38
|
+
# A trailing block (e.g. `Dir.each_child(path).none? { ... }`) changes
|
|
39
|
+
# the meaning and is not equivalent to `Dir.empty?`.
|
|
40
|
+
return if node.block_literal?
|
|
41
|
+
|
|
38
42
|
offensive?(node) do |const_node, arg_node|
|
|
39
43
|
replacement = "#{bang(node)}#{const_node.source}.empty?(#{arg_node.source})"
|
|
40
44
|
add_offense(node, message: format(MSG, replacement: replacement)) do |corrector|
|
|
@@ -140,7 +140,11 @@ module RuboCop
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def method_allowed?(node)
|
|
143
|
-
|
|
143
|
+
# For a modifier form like `module_function def foo; end`, `node` is the
|
|
144
|
+
# `module_function`/`ruby2_keywords` send; the real method name is on its
|
|
145
|
+
# `def`/`defs` argument, not the modifier itself.
|
|
146
|
+
method_name = node.send_type? ? node.first_argument.method_name : node.method_name
|
|
147
|
+
allowed_methods.include?(method_name)
|
|
144
148
|
end
|
|
145
149
|
|
|
146
150
|
def allowed_methods
|
|
@@ -40,7 +40,7 @@ module RuboCop
|
|
|
40
40
|
extend AutoCorrector
|
|
41
41
|
|
|
42
42
|
MSG = 'Do not use empty `case` condition, instead use an `if` expression.'
|
|
43
|
-
NOT_SUPPORTED_PARENT_TYPES = %i[return break next send csend].freeze
|
|
43
|
+
NOT_SUPPORTED_PARENT_TYPES = %i[return break next send csend yield super].freeze
|
|
44
44
|
|
|
45
45
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
46
46
|
def on_case(case_node)
|
|
@@ -90,7 +90,17 @@ module RuboCop
|
|
|
90
90
|
range = range_between(conditions.first.source_range.begin_pos,
|
|
91
91
|
conditions.last.source_range.end_pos)
|
|
92
92
|
|
|
93
|
-
corrector.replace(range, conditions.map(
|
|
93
|
+
corrector.replace(range, conditions.map { |c| parenthesize_condition(c) }.join(' || '))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# A condition that binds looser than `||` (e.g. a ternary, range, or
|
|
98
|
+
# assignment) must be parenthesized so the joined `||` keeps its meaning.
|
|
99
|
+
def parenthesize_condition(condition)
|
|
100
|
+
if condition.assignment? || condition.type?(:if, :and, :or, :range)
|
|
101
|
+
"(#{condition.source})"
|
|
102
|
+
else
|
|
103
|
+
condition.source
|
|
94
104
|
end
|
|
95
105
|
end
|
|
96
106
|
|
|
@@ -101,12 +101,19 @@ module RuboCop
|
|
|
101
101
|
|
|
102
102
|
def autocorrect_class_new(corrector, node, class_new_node)
|
|
103
103
|
indent = ' ' * node.loc.column
|
|
104
|
-
class_name = node
|
|
104
|
+
class_name = constant_name(node)
|
|
105
105
|
parent_class_name = class_new_node.first_argument.source
|
|
106
106
|
|
|
107
107
|
corrector.replace(node, "class #{class_name} < #{parent_class_name}\n#{indent}end")
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
# Preserve any namespace on the assigned constant (e.g. `Foo::Bar`),
|
|
111
|
+
# which `node.name` drops.
|
|
112
|
+
def constant_name(node)
|
|
113
|
+
namespace = node.namespace
|
|
114
|
+
namespace ? "#{namespace.source}::#{node.name}" : node.name
|
|
115
|
+
end
|
|
116
|
+
|
|
110
117
|
def autocorrect_class_definition(corrector, node)
|
|
111
118
|
class_name = node.identifier.source
|
|
112
119
|
parent_class_name = node.parent_class.source
|
|
@@ -42,6 +42,10 @@ module RuboCop
|
|
|
42
42
|
MSG = 'Use an empty string literal instead of heredoc.'
|
|
43
43
|
|
|
44
44
|
def on_heredoc(node)
|
|
45
|
+
# A backtick heredoc (`<<~`CMD``) executes a command, so it cannot be
|
|
46
|
+
# replaced with an empty string literal.
|
|
47
|
+
return if node.xstr_type?
|
|
48
|
+
|
|
45
49
|
heredoc_body = node.loc.heredoc_body
|
|
46
50
|
|
|
47
51
|
return unless heredoc_body.source.empty?
|
|
@@ -44,13 +44,18 @@ module RuboCop
|
|
|
44
44
|
def_node_matcher :str_node, '(send (const {nil? cbase} :String) :new)'
|
|
45
45
|
|
|
46
46
|
# @!method array_with_block(node)
|
|
47
|
-
def_node_matcher :array_with_block,
|
|
47
|
+
def_node_matcher :array_with_block, <<~PATTERN
|
|
48
|
+
{
|
|
49
|
+
(block (send (const {nil? cbase} :Array) :new) args _)
|
|
50
|
+
({numblock itblock} (send (const {nil? cbase} :Array) :new) ...)
|
|
51
|
+
}
|
|
52
|
+
PATTERN
|
|
48
53
|
|
|
49
54
|
# @!method hash_with_block(node)
|
|
50
55
|
def_node_matcher :hash_with_block, <<~PATTERN
|
|
51
56
|
{
|
|
52
57
|
(block (send (const {nil? cbase} :Hash) :new) args _)
|
|
53
|
-
(numblock (send (const {nil? cbase} :Hash) :new) ...)
|
|
58
|
+
({numblock itblock} (send (const {nil? cbase} :Hash) :new) ...)
|
|
54
59
|
}
|
|
55
60
|
PATTERN
|
|
56
61
|
|
|
@@ -45,36 +45,46 @@ module RuboCop
|
|
|
45
45
|
MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
|
|
46
46
|
MSG_TERNARY = 'Do not return empty strings in string interpolation.'
|
|
47
47
|
|
|
48
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
49
48
|
def on_interpolation(node)
|
|
50
49
|
node.each_child_node(:if) do |child_node|
|
|
51
50
|
if style == :trailing_conditional
|
|
52
|
-
|
|
53
|
-
ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
if empty_else_outcome?(child_node)
|
|
57
|
-
ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
|
|
58
|
-
end
|
|
51
|
+
trailing_conditional_correction(child_node)
|
|
59
52
|
elsif style == :ternary
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
ternary_component = if child_node.unless?
|
|
63
|
-
"'' : #{child_node.if_branch.source}"
|
|
64
|
-
else
|
|
65
|
-
"#{child_node.if_branch.source} : ''"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
|
|
69
|
-
corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
|
|
70
|
-
end
|
|
53
|
+
ternary_correction(node, child_node)
|
|
71
54
|
end
|
|
72
55
|
end
|
|
73
56
|
end
|
|
74
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
75
57
|
|
|
76
58
|
private
|
|
77
59
|
|
|
60
|
+
def trailing_conditional_correction(child_node)
|
|
61
|
+
# A modifier `if`/`unless` is already a trailing conditional and has
|
|
62
|
+
# no `else` branch, so the ternary-to-trailing rewrite does not apply.
|
|
63
|
+
return if child_node.modifier_form?
|
|
64
|
+
|
|
65
|
+
if empty_if_outcome?(child_node)
|
|
66
|
+
ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return unless empty_else_outcome?(child_node)
|
|
70
|
+
|
|
71
|
+
ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def ternary_correction(node, child_node)
|
|
75
|
+
return unless child_node.modifier_form?
|
|
76
|
+
|
|
77
|
+
ternary_component = if child_node.unless?
|
|
78
|
+
"'' : #{child_node.if_branch.source}"
|
|
79
|
+
else
|
|
80
|
+
"#{child_node.if_branch.source} : ''"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
|
|
84
|
+
corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
78
88
|
def empty_if_outcome?(node)
|
|
79
89
|
empty_branch_outcome?(node.if_branch)
|
|
80
90
|
end
|
|
@@ -45,6 +45,10 @@ module RuboCop
|
|
|
45
45
|
def on_send(node)
|
|
46
46
|
return unless env_home?(node)
|
|
47
47
|
return if node.arguments.count == 2 && !node.arguments[1].nil_type?
|
|
48
|
+
# `ENV.fetch('HOME') { default }` supplies a fallback, just like
|
|
49
|
+
# `ENV.fetch('HOME', default)`. `Dir.home` ignores the block, so
|
|
50
|
+
# converting would silently drop it.
|
|
51
|
+
return if node.block_node
|
|
48
52
|
|
|
49
53
|
add_offense(node) do |corrector|
|
|
50
54
|
corrector.replace(node, 'Dir.home')
|
|
@@ -34,7 +34,7 @@ module RuboCop
|
|
|
34
34
|
even_odd_candidate?(node) do |base_number, method, arg|
|
|
35
35
|
replacement_method = replacement_method(arg, method)
|
|
36
36
|
add_offense(node, message: format(MSG, method: replacement_method)) do |corrector|
|
|
37
|
-
correction = "#{base_number
|
|
37
|
+
correction = "#{receiver_source(base_number)}.#{replacement_method}?"
|
|
38
38
|
corrector.replace(node, correction)
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -42,6 +42,16 @@ module RuboCop
|
|
|
42
42
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
|
+
def receiver_source(node)
|
|
46
|
+
# A binary or unary operator receiver (e.g. `a * b`, `-a`) binds looser
|
|
47
|
+
# than the appended method call, so it must be wrapped in parentheses.
|
|
48
|
+
if node.send_type? && node.operator_method? && !node.method?(:[])
|
|
49
|
+
"(#{node.source})"
|
|
50
|
+
else
|
|
51
|
+
node.source
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
45
55
|
def replacement_method(arg, method)
|
|
46
56
|
case arg
|
|
47
57
|
when 0
|
|
@@ -43,7 +43,8 @@ module RuboCop
|
|
|
43
43
|
return unless (parsed_regexp = parse_regexp(regexp))
|
|
44
44
|
return unless exact_match_pattern?(parsed_regexp)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
string = escape_single_quotes(parsed_regexp[1].text)
|
|
47
|
+
prefer = "#{receiver.source} #{new_method(node)} '#{string}'"
|
|
47
48
|
|
|
48
49
|
add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
|
|
49
50
|
corrector.replace(node, prefer)
|
|
@@ -53,6 +54,12 @@ module RuboCop
|
|
|
53
54
|
|
|
54
55
|
private
|
|
55
56
|
|
|
57
|
+
# Escape characters that are special inside a single-quoted string so the
|
|
58
|
+
# generated literal (e.g. for `/\Afoo'bar\z/`) stays valid Ruby.
|
|
59
|
+
def escape_single_quotes(text)
|
|
60
|
+
text.gsub(/['\\]/) { |char| "\\#{char}" }
|
|
61
|
+
end
|
|
62
|
+
|
|
56
63
|
def exact_match_pattern?(parsed_regexp)
|
|
57
64
|
tokens = parsed_regexp.map(&:token)
|
|
58
65
|
return false unless tokens[0] == :bos && tokens[1] == :literal && tokens[2] == :eos
|
|
@@ -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
|
|
@@ -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
|
|
@@ -121,16 +121,24 @@ module RuboCop
|
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def multi_argument(node)
|
|
124
|
+
# A splat argument can expand to any number of elements, so the pairs
|
|
125
|
+
# can't be built statically and there is no literal hash to suggest.
|
|
126
|
+
return if node.arguments.any?(&:splat_type?)
|
|
127
|
+
|
|
124
128
|
if node.arguments.count.odd?
|
|
125
129
|
add_offense(node, message: MSG_LITERAL_MULTI_ARG)
|
|
126
130
|
else
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
correct_multi_argument(node)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
def correct_multi_argument(node)
|
|
136
|
+
add_offense(node, message: MSG_LITERAL_MULTI_ARG) do |corrector|
|
|
137
|
+
corrector.replace(node, args_to_hash(node.arguments))
|
|
138
|
+
|
|
139
|
+
parent = node.parent
|
|
140
|
+
if parent&.send_type? && !parent.method?(:to_h) && !parent.parenthesized?
|
|
141
|
+
add_parentheses(parent, corrector)
|
|
134
142
|
end
|
|
135
143
|
end
|
|
136
144
|
end
|
|
@@ -74,7 +74,7 @@ module RuboCop
|
|
|
74
74
|
|
|
75
75
|
def offense_for_brackets?(node)
|
|
76
76
|
style == :brackets && node.receiver && node.method?(:fetch) && node.arguments.one? &&
|
|
77
|
-
!node.block_literal?
|
|
77
|
+
!node.block_literal? && !node.csend_type?
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def offense_for_fetch?(node)
|
|
@@ -84,11 +84,7 @@ module RuboCop
|
|
|
84
84
|
def correct_fetch_to_brackets(corrector, node)
|
|
85
85
|
key = node.first_argument.source
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
corrector.replace(node, "(#{node.receiver.source}[#{key}])")
|
|
89
|
-
else
|
|
90
|
-
corrector.replace(node.loc.dot.join(node.source_range.end), "[#{key}]")
|
|
91
|
-
end
|
|
87
|
+
corrector.replace(node.loc.dot.join(node.source_range.end), "[#{key}]")
|
|
92
88
|
end
|
|
93
89
|
|
|
94
90
|
def correct_brackets_to_fetch(corrector, node)
|
|
@@ -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
|
|
@@ -216,23 +216,34 @@ module RuboCop
|
|
|
216
216
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
217
217
|
|
|
218
218
|
def correct_assignment(corrector, node, expression, insert_position)
|
|
219
|
+
indentation = indentation_of(node.parent)
|
|
220
|
+
|
|
219
221
|
if insert_position == :after_condition
|
|
220
222
|
assignment = node.parent.source_range.with(end_pos: node.source_range.begin_pos)
|
|
221
223
|
corrector.remove(assignment)
|
|
222
|
-
corrector.insert_after(node, "\n#{assignment.source}#{expression.source}")
|
|
224
|
+
corrector.insert_after(node, "\n#{indentation}#{assignment.source}#{expression.source}")
|
|
223
225
|
else
|
|
224
|
-
corrector.insert_before(node.parent, "#{expression.source}\n")
|
|
226
|
+
corrector.insert_before(node.parent, "#{expression.source}\n#{indentation}")
|
|
225
227
|
end
|
|
226
228
|
end
|
|
227
229
|
|
|
228
230
|
def correct_no_assignment(corrector, node, expression, insert_position)
|
|
231
|
+
indentation = indentation_of(node)
|
|
232
|
+
|
|
229
233
|
if insert_position == :after_condition
|
|
230
|
-
corrector.insert_after(node, "\n#{expression.source}")
|
|
234
|
+
corrector.insert_after(node, "\n#{indentation}#{expression.source}")
|
|
231
235
|
else
|
|
232
|
-
corrector.insert_before(node, "#{expression.source}\n")
|
|
236
|
+
corrector.insert_before(node, "#{expression.source}\n#{indentation}")
|
|
233
237
|
end
|
|
234
238
|
end
|
|
235
239
|
|
|
240
|
+
# The leading indentation of the line the conditional starts on, so a
|
|
241
|
+
# hoisted expression keeps the surrounding nesting instead of landing
|
|
242
|
+
# at column zero.
|
|
243
|
+
def indentation_of(node)
|
|
244
|
+
' ' * node.source_range.column
|
|
245
|
+
end
|
|
246
|
+
|
|
236
247
|
def last_child_of_parent?(node)
|
|
237
248
|
return true unless (parent = node.parent)
|
|
238
249
|
|
|
@@ -91,7 +91,7 @@ module RuboCop
|
|
|
91
91
|
|
|
92
92
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
93
93
|
def on_if(node)
|
|
94
|
-
return if endless_method?(node.body) || node.
|
|
94
|
+
return if endless_method?(node.body) || node.each_ancestor(:dstr).any?
|
|
95
95
|
|
|
96
96
|
condition = node.condition
|
|
97
97
|
return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
|
|
@@ -119,7 +119,7 @@ module RuboCop
|
|
|
119
119
|
if condition.defined_type?
|
|
120
120
|
[condition]
|
|
121
121
|
else
|
|
122
|
-
condition.each_descendant
|
|
122
|
+
condition.each_descendant(:defined?)
|
|
123
123
|
end
|
|
124
124
|
end
|
|
125
125
|
|
|
@@ -137,7 +137,7 @@ module RuboCop
|
|
|
137
137
|
if condition.any_match_pattern_type?
|
|
138
138
|
[condition]
|
|
139
139
|
else
|
|
140
|
-
condition.each_descendant
|
|
140
|
+
condition.each_descendant(:any_match_pattern)
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
143
|
|
|
@@ -350,7 +350,7 @@ module RuboCop
|
|
|
350
350
|
end
|
|
351
351
|
|
|
352
352
|
def comment_on_node_line(node)
|
|
353
|
-
processed_source.
|
|
353
|
+
processed_source.comment_at_line(node.first_line)
|
|
354
354
|
end
|
|
355
355
|
|
|
356
356
|
def remove_comment(corrector, _node, comment)
|
|
@@ -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
|
|
@@ -75,7 +75,9 @@ module RuboCop
|
|
|
75
75
|
def invertible?(node) # rubocop:disable Metrics/CyclomaticComplexity
|
|
76
76
|
case node&.type
|
|
77
77
|
when :begin
|
|
78
|
-
|
|
78
|
+
# A multi-statement `begin` evaluates to its last expression, so it
|
|
79
|
+
# cannot be inverted by negating a single child.
|
|
80
|
+
node.children.one? && invertible?(node.children.first)
|
|
79
81
|
when :send
|
|
80
82
|
return false if inheritance_check?(node)
|
|
81
83
|
|
|
@@ -124,12 +126,17 @@ module RuboCop
|
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
def preferred_logical_condition(node)
|
|
127
|
-
preferred_lhs =
|
|
128
|
-
preferred_rhs =
|
|
129
|
+
preferred_lhs = preferred_operand(node, node.lhs)
|
|
130
|
+
preferred_rhs = preferred_operand(node, node.rhs)
|
|
129
131
|
|
|
130
132
|
"#{preferred_lhs} #{node.inverse_operator} #{preferred_rhs}"
|
|
131
133
|
end
|
|
132
134
|
|
|
135
|
+
def preferred_operand(node, operand)
|
|
136
|
+
preferred = preferred_condition(operand)
|
|
137
|
+
parenthesize_inverted_operand?(node, operand) ? "(#{preferred})" : preferred
|
|
138
|
+
end
|
|
139
|
+
|
|
133
140
|
def autocorrect(corrector, node)
|
|
134
141
|
case node.type
|
|
135
142
|
when :begin
|
|
@@ -138,11 +145,24 @@ module RuboCop
|
|
|
138
145
|
autocorrect_send_node(corrector, node)
|
|
139
146
|
when :or, :and
|
|
140
147
|
corrector.replace(node.loc.operator, node.inverse_operator)
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
autocorrect_operand(corrector, node, node.lhs)
|
|
149
|
+
autocorrect_operand(corrector, node, node.rhs)
|
|
143
150
|
end
|
|
144
151
|
end
|
|
145
152
|
|
|
153
|
+
def autocorrect_operand(corrector, node, operand)
|
|
154
|
+
autocorrect(corrector, operand)
|
|
155
|
+
corrector.wrap(operand, '(', ')') if parenthesize_inverted_operand?(node, operand)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# When an `and` is nested in an `or`, inverting both operators turns the
|
|
159
|
+
# `and` into an `or` that now binds looser than its parent, so it must be
|
|
160
|
+
# parenthesized to keep the original grouping (`a && b || c` inverts to
|
|
161
|
+
# `(!a || !b) && !c`, not `!a || !b && !c`).
|
|
162
|
+
def parenthesize_inverted_operand?(node, operand)
|
|
163
|
+
node.or_type? && operand.and_type?
|
|
164
|
+
end
|
|
165
|
+
|
|
146
166
|
def autocorrect_send_node(corrector, node)
|
|
147
167
|
if node.method?(:!)
|
|
148
168
|
corrector.remove(node.loc.selector)
|
|
@@ -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
|
|
@@ -27,6 +27,9 @@ module RuboCop
|
|
|
27
27
|
|
|
28
28
|
def on_send(node)
|
|
29
29
|
return unless node.receiver
|
|
30
|
+
# Rewriting the call rebuilds it as a single expression, which would drop
|
|
31
|
+
# any comments inside the argument list, so leave it to be fixed manually.
|
|
32
|
+
return if comments_in_node?(node)
|
|
30
33
|
|
|
31
34
|
if offense?(node)
|
|
32
35
|
prefer = prefer(node)
|
|
@@ -48,6 +51,14 @@ module RuboCop
|
|
|
48
51
|
|
|
49
52
|
private
|
|
50
53
|
|
|
54
|
+
def comments_in_node?(node)
|
|
55
|
+
range = node.source_range
|
|
56
|
+
processed_source.comments.any? do |comment|
|
|
57
|
+
range.begin_pos <= comment.source_range.begin_pos &&
|
|
58
|
+
comment.source_range.end_pos <= range.end_pos
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
51
62
|
def offense?(node)
|
|
52
63
|
(explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
|
|
53
64
|
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)
|