rubocop 1.75.5 → 1.76.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/README.md +20 -14
- data/config/default.yml +55 -7
- data/config/obsoletion.yml +6 -3
- data/lib/rubocop/cop/autocorrect_logic.rb +18 -10
- data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -1
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +50 -6
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +1 -0
- data/lib/rubocop/cop/layout/class_structure.rb +35 -0
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +7 -3
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/space_before_brackets.rb +6 -32
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -0
- data/lib/rubocop/cop/lint/ambiguous_range.rb +5 -0
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +1 -1
- data/lib/rubocop/cop/lint/duplicate_methods.rb +84 -2
- data/lib/rubocop/cop/lint/empty_interpolation.rb +3 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +27 -0
- data/lib/rubocop/cop/lint/identity_comparison.rb +19 -15
- data/lib/rubocop/cop/lint/literal_as_condition.rb +16 -24
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +4 -4
- data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +5 -0
- data/lib/rubocop/cop/lint/useless_access_modifier.rb +21 -4
- data/lib/rubocop/cop/lint/useless_assignment.rb +2 -0
- data/lib/rubocop/cop/lint/useless_default_value_argument.rb +90 -0
- data/lib/rubocop/cop/lint/useless_or.rb +98 -0
- data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
- data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
- data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
- data/lib/rubocop/cop/naming/predicate_method.rb +245 -0
- data/lib/rubocop/cop/naming/{predicate_name.rb → predicate_prefix.rb} +2 -2
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -10
- data/lib/rubocop/cop/style/command_literal.rb +1 -1
- data/lib/rubocop/cop/style/comparable_between.rb +3 -0
- data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
- data/lib/rubocop/cop/style/data_inheritance.rb +7 -0
- data/lib/rubocop/cop/style/def_with_parentheses.rb +18 -5
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +100 -0
- data/lib/rubocop/cop/style/if_unless_modifier.rb +22 -4
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -7
- data/lib/rubocop/cop/style/it_block_parameter.rb +33 -14
- data/lib/rubocop/cop/style/map_to_hash.rb +11 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/min_max_comparison.rb +13 -5
- data/lib/rubocop/cop/style/multiline_if_modifier.rb +2 -0
- data/lib/rubocop/cop/style/percent_q_literals.rb +1 -1
- data/lib/rubocop/cop/style/redundant_array_flatten.rb +50 -0
- data/lib/rubocop/cop/style/redundant_format.rb +6 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +23 -5
- data/lib/rubocop/cop/style/redundant_self.rb +5 -5
- data/lib/rubocop/cop/style/regexp_literal.rb +1 -1
- data/lib/rubocop/cop/style/safe_navigation.rb +24 -11
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +4 -2
- data/lib/rubocop/cop/style/string_concatenation.rb +1 -2
- data/lib/rubocop/cop/style/struct_inheritance.rb +8 -1
- data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
- data/lib/rubocop/cop/team.rb +1 -1
- data/lib/rubocop/cop/variable_force/assignment.rb +7 -3
- data/lib/rubocop/formatter/disabled_config_formatter.rb +1 -0
- data/lib/rubocop/formatter/html_formatter.rb +1 -1
- data/lib/rubocop/rspec/expect_offense.rb +9 -3
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +6 -1
- metadata +13 -11
@@ -0,0 +1,245 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Naming
|
6
|
+
# Checks that predicate methods end with `?` and non-predicate methods do not.
|
7
|
+
#
|
8
|
+
# The names of predicate methods (methods that return a boolean value) should end
|
9
|
+
# in a question mark. Methods that don't return a boolean, shouldn't
|
10
|
+
# end in a question mark.
|
11
|
+
#
|
12
|
+
# The cop assesses a predicate method as one that returns boolean values. Likewise,
|
13
|
+
# a method that only returns literal values is assessed as non-predicate. The cop does
|
14
|
+
# not make an assessment if the return type is unknown (method calls, variables, etc.).
|
15
|
+
#
|
16
|
+
# NOTE: Operator methods (`def ==`, etc.) are ignored.
|
17
|
+
#
|
18
|
+
# By default, the cop runs in `conservative` mode, which allows a method to be named
|
19
|
+
# with a question mark as long as at least one return value is boolean. In `aggressive`
|
20
|
+
# mode, methods with a question mark will register an offense if any known non-boolean
|
21
|
+
# return values are detected.
|
22
|
+
#
|
23
|
+
# The cop also has `AllowedMethods` configuration in order to prevent the cop from
|
24
|
+
# registering an offense from a method name that does not confirm to the naming
|
25
|
+
# guidelines. By default, `call` is allowed. The cop also has `AllowedPatterns`
|
26
|
+
# configuration to allow method names by regular expression.
|
27
|
+
#
|
28
|
+
# The cop can furthermore be configured to allow all bang methods (method names
|
29
|
+
# ending with `!`), with `AllowBangMethods: true` (default false).
|
30
|
+
#
|
31
|
+
# @example Mode: conservative (default)
|
32
|
+
# # bad
|
33
|
+
# def foo
|
34
|
+
# bar == baz
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# def foo?
|
39
|
+
# bar == baz
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # bad
|
43
|
+
# def foo?
|
44
|
+
# 5
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # good
|
48
|
+
# def foo
|
49
|
+
# 5
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# # good - operator method
|
53
|
+
# def ==(other)
|
54
|
+
# hash == other.hash
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # good - at least one return value is boolean
|
58
|
+
# def foo?
|
59
|
+
# return unless bar?
|
60
|
+
# true
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# # ok - return type is not known
|
64
|
+
# def foo?
|
65
|
+
# bar
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# # ok - return type is not known
|
69
|
+
# def foo
|
70
|
+
# bar?
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @example Mode: aggressive
|
74
|
+
# # bad - the method returns nil in some cases
|
75
|
+
# def foo?
|
76
|
+
# return unless bar?
|
77
|
+
# true
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @example AllowBangMethods: false (default)
|
81
|
+
# # bad
|
82
|
+
# def save!
|
83
|
+
# true
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# @example AllowBangMethods: true
|
87
|
+
# # good
|
88
|
+
# def save!
|
89
|
+
# true
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
class PredicateMethod < Base
|
93
|
+
include AllowedMethods
|
94
|
+
include AllowedPattern
|
95
|
+
|
96
|
+
MSG_PREDICATE = 'Predicate method names should end with `?`.'
|
97
|
+
MSG_NON_PREDICATE = 'Non-predicate method names should not end with `?`.'
|
98
|
+
|
99
|
+
def on_def(node)
|
100
|
+
return if allowed?(node)
|
101
|
+
|
102
|
+
return_values = return_values(node.body)
|
103
|
+
return if acceptable?(return_values)
|
104
|
+
|
105
|
+
if node.predicate_method? && potential_non_predicate?(return_values)
|
106
|
+
add_offense(node.loc.name, message: MSG_NON_PREDICATE)
|
107
|
+
elsif !node.predicate_method? && all_return_values_boolean?(return_values)
|
108
|
+
add_offense(node.loc.name, message: MSG_PREDICATE)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
alias on_defs on_def
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def allowed?(node)
|
116
|
+
allowed_method?(node.method_name) ||
|
117
|
+
matches_allowed_pattern?(node.method_name) ||
|
118
|
+
allowed_bang_method?(node) ||
|
119
|
+
node.operator_method? ||
|
120
|
+
node.body.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
def acceptable?(return_values)
|
124
|
+
# In `conservative` mode, if the method returns `super`, `zsuper`, or a
|
125
|
+
# non-comparison method call, the method name is acceptable.
|
126
|
+
return false unless conservative?
|
127
|
+
|
128
|
+
return_values.any? do |value|
|
129
|
+
value.type?(:super, :zsuper) || non_comparison_call?(value)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def non_comparison_call?(value)
|
134
|
+
value.call_type? && !value.comparison_method?
|
135
|
+
end
|
136
|
+
|
137
|
+
def return_values(node)
|
138
|
+
# Collect all the (implicit and explicit) return values of a node
|
139
|
+
return_values = Set.new(node.begin_type? ? [] : [extract_return_value(node)])
|
140
|
+
|
141
|
+
node.each_descendant(:return) do |return_node|
|
142
|
+
return_values << extract_return_value(return_node)
|
143
|
+
end
|
144
|
+
|
145
|
+
last_value = last_value(node)
|
146
|
+
return_values << last_value if last_value
|
147
|
+
|
148
|
+
process_return_values(return_values)
|
149
|
+
end
|
150
|
+
|
151
|
+
def all_return_values_boolean?(return_values)
|
152
|
+
values = return_values.reject { |value| value.type?(:super, :zsuper) }
|
153
|
+
return false if values.empty?
|
154
|
+
|
155
|
+
values.all? { |value| boolean_return?(value) }
|
156
|
+
end
|
157
|
+
|
158
|
+
def boolean_return?(value)
|
159
|
+
value.boolean_type? || (value.call_type? && value.comparison_method?)
|
160
|
+
end
|
161
|
+
|
162
|
+
def potential_non_predicate?(return_values)
|
163
|
+
# Assumes a method to be non-predicate if all return values are non-boolean literals.
|
164
|
+
#
|
165
|
+
# In `Mode: conservative`, if any of the return values is a boolean,
|
166
|
+
# the method name is acceptable.
|
167
|
+
# In `Mode: aggressive`, all return values must be booleans for a predicate
|
168
|
+
# method, or else an offense will be registered.
|
169
|
+
return false if conservative? && return_values.any? { |value| boolean_return?(value) }
|
170
|
+
|
171
|
+
return_values.any? do |value|
|
172
|
+
value.literal? && !value.boolean_type?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def extract_return_value(node)
|
177
|
+
return node unless node.return_type?
|
178
|
+
|
179
|
+
# `return` without a value is a `nil` return.
|
180
|
+
return s(:nil) if node.arguments.empty?
|
181
|
+
|
182
|
+
# When there's a multiple return, it cannot be a predicate
|
183
|
+
# so just return an `array` sexp for simplicity.
|
184
|
+
return s(:array) unless node.arguments.one?
|
185
|
+
|
186
|
+
node.first_argument
|
187
|
+
end
|
188
|
+
|
189
|
+
def last_value(node)
|
190
|
+
value = node.begin_type? ? node.children.last : node
|
191
|
+
value&.return_type? ? extract_return_value(value) : value
|
192
|
+
end
|
193
|
+
|
194
|
+
def process_return_values(return_values)
|
195
|
+
return_values.flat_map do |value|
|
196
|
+
if value.conditional?
|
197
|
+
process_return_values(extract_conditional_branches(value))
|
198
|
+
elsif and_or?(value)
|
199
|
+
process_return_values(extract_and_or_clauses(value))
|
200
|
+
else
|
201
|
+
value
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def and_or?(node)
|
207
|
+
node.type?(:and, :or)
|
208
|
+
end
|
209
|
+
|
210
|
+
def extract_and_or_clauses(node)
|
211
|
+
# Recursively traverse an `and` or `or` node to collect all clauses within
|
212
|
+
return node unless and_or?(node)
|
213
|
+
|
214
|
+
[extract_and_or_clauses(node.lhs), extract_and_or_clauses(node.rhs)].flatten
|
215
|
+
end
|
216
|
+
|
217
|
+
def extract_conditional_branches(node)
|
218
|
+
return node unless node.conditional?
|
219
|
+
|
220
|
+
if node.type?(:while, :until)
|
221
|
+
# If there is no body, act as implicit `nil`.
|
222
|
+
node.body ? [last_value(node.body)] : [s(:nil)]
|
223
|
+
else
|
224
|
+
# Branches with no value act as an implicit `nil`.
|
225
|
+
node.branches.filter_map { |branch| branch ? last_value(branch) : s(:nil) }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def conservative?
|
230
|
+
cop_config.fetch('Mode', :conservative).to_sym == :conservative
|
231
|
+
end
|
232
|
+
|
233
|
+
def allowed_bang_method?(node)
|
234
|
+
return false unless allow_bang_methods?
|
235
|
+
|
236
|
+
node.bang_method?
|
237
|
+
end
|
238
|
+
|
239
|
+
def allow_bang_methods?
|
240
|
+
cop_config.fetch('AllowBangMethods', false)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -100,7 +100,7 @@ module RuboCop
|
|
100
100
|
# # good
|
101
101
|
# def_node_matcher(:even?) { |value| }
|
102
102
|
#
|
103
|
-
class
|
103
|
+
class PredicatePrefix < Base
|
104
104
|
include AllowedMethods
|
105
105
|
|
106
106
|
# @!method dynamic_method_define(node)
|
@@ -143,7 +143,7 @@ module RuboCop
|
|
143
143
|
next if predicate_prefixes.include?(forbidden_prefix)
|
144
144
|
|
145
145
|
raise ValidationError, <<~MSG.chomp
|
146
|
-
The `Naming/
|
146
|
+
The `Naming/PredicatePrefix` cop is misconfigured. Prefix #{forbidden_prefix} must be included in NamePrefix because it is included in ForbiddenPrefixes.
|
147
147
|
MSG
|
148
148
|
end
|
149
149
|
end
|
@@ -195,15 +195,27 @@ module RuboCop
|
|
195
195
|
def autocorrect(corrector, node)
|
196
196
|
case style
|
197
197
|
when :group
|
198
|
-
|
199
|
-
return unless def_nodes.any?
|
200
|
-
|
201
|
-
replace_defs(corrector, node, def_nodes)
|
198
|
+
autocorrect_group_style(corrector, node)
|
202
199
|
when :inline
|
200
|
+
autocorrect_inline_style(corrector, node)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def autocorrect_group_style(corrector, node)
|
205
|
+
def_nodes = find_corresponding_def_nodes(node)
|
206
|
+
return unless def_nodes.any?
|
207
|
+
|
208
|
+
replace_defs(corrector, node, def_nodes)
|
209
|
+
end
|
210
|
+
|
211
|
+
def autocorrect_inline_style(corrector, node)
|
212
|
+
if node.parent&.begin_type?
|
213
|
+
remove_modifier_node_within_begin(corrector, node, node.parent)
|
214
|
+
else
|
203
215
|
remove_nodes(corrector, node)
|
204
|
-
|
205
|
-
|
206
|
-
|
216
|
+
end
|
217
|
+
select_grouped_def_nodes(node).each do |grouped_def_node|
|
218
|
+
insert_inline_modifier(corrector, grouped_def_node, node.method_name)
|
207
219
|
end
|
208
220
|
end
|
209
221
|
|
@@ -224,9 +236,13 @@ module RuboCop
|
|
224
236
|
end
|
225
237
|
|
226
238
|
def offense?(node)
|
227
|
-
|
228
|
-
|
229
|
-
|
239
|
+
if group_style?
|
240
|
+
return false if node.parent ? node.parent.if_type? : access_modifier_with_symbol?(node)
|
241
|
+
|
242
|
+
access_modifier_is_inlined?(node) && !right_siblings_same_inline_method?(node)
|
243
|
+
else
|
244
|
+
access_modifier_is_not_inlined?(node) && select_grouped_def_nodes(node).any?
|
245
|
+
end
|
230
246
|
end
|
231
247
|
|
232
248
|
def correctable_group_offense?(node)
|
@@ -331,6 +347,12 @@ module RuboCop
|
|
331
347
|
end
|
332
348
|
end
|
333
349
|
|
350
|
+
def remove_modifier_node_within_begin(corrector, modifier_node, begin_node)
|
351
|
+
def_node = begin_node.children[1]
|
352
|
+
range = modifier_node.source_range.begin.join(def_node.source_range.begin)
|
353
|
+
corrector.remove(range)
|
354
|
+
end
|
355
|
+
|
334
356
|
def def_source(node, def_nodes)
|
335
357
|
[
|
336
358
|
*processed_source.ast_with_comments[node].map(&:text),
|
@@ -9,6 +9,9 @@ module RuboCop
|
|
9
9
|
# although the difference generally isn't observable. If you require maximum
|
10
10
|
# performance, consider using logical comparison.
|
11
11
|
#
|
12
|
+
# @safety
|
13
|
+
# This cop is unsafe because the receiver may not respond to `between?`.
|
14
|
+
#
|
12
15
|
# @example
|
13
16
|
#
|
14
17
|
# # bad
|
@@ -451,7 +451,9 @@ module RuboCop
|
|
451
451
|
corrector.remove_preceding(condition.loc.else, condition.loc.else.column - column)
|
452
452
|
end
|
453
453
|
|
454
|
-
return unless condition.loc.end && !same_line?(
|
454
|
+
return unless condition.loc.end && !same_line?(
|
455
|
+
condition.branches.last.parent.else_branch, condition.loc.end
|
456
|
+
)
|
455
457
|
|
456
458
|
corrector.remove_preceding(condition.loc.end, condition.loc.end.column - column)
|
457
459
|
end
|
@@ -4,6 +4,7 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Style
|
6
6
|
# Checks for inheritance from `Data.define` to avoid creating the anonymous parent class.
|
7
|
+
# Inheriting from `Data.define` adds a superfluous level in inheritance tree.
|
7
8
|
#
|
8
9
|
# @safety
|
9
10
|
# Autocorrection is unsafe because it will change the inheritance
|
@@ -17,12 +18,18 @@ module RuboCop
|
|
17
18
|
# end
|
18
19
|
# end
|
19
20
|
#
|
21
|
+
# Person.ancestors
|
22
|
+
# # => [Person, #<Class:0x000000010b4e14a0>, Data, (...)]
|
23
|
+
#
|
20
24
|
# # good
|
21
25
|
# Person = Data.define(:first_name, :last_name) do
|
22
26
|
# def age
|
23
27
|
# 42
|
24
28
|
# end
|
25
29
|
# end
|
30
|
+
#
|
31
|
+
# Person.ancestors
|
32
|
+
# # => [Person, Data, (...)]
|
26
33
|
class DataInheritance < Base
|
27
34
|
include RangeHelp
|
28
35
|
extend AutoCorrector
|
@@ -25,8 +25,9 @@ module RuboCop
|
|
25
25
|
# # good
|
26
26
|
# def foo = do_something
|
27
27
|
#
|
28
|
-
# # good
|
28
|
+
# # good - without parentheses it's a syntax error
|
29
29
|
# def foo() do_something end
|
30
|
+
# def foo()=do_something
|
30
31
|
#
|
31
32
|
# # bad
|
32
33
|
# def Baz.foo()
|
@@ -38,19 +39,31 @@ module RuboCop
|
|
38
39
|
# do_something
|
39
40
|
# end
|
40
41
|
class DefWithParentheses < Base
|
42
|
+
include RangeHelp
|
41
43
|
extend AutoCorrector
|
42
44
|
|
43
45
|
MSG = "Omit the parentheses in defs when the method doesn't accept any arguments."
|
44
46
|
|
45
47
|
def on_def(node)
|
46
|
-
return
|
47
|
-
return
|
48
|
+
return unless !node.arguments? && (arguments_range = node.arguments.source_range)
|
49
|
+
return if parentheses_required?(node, arguments_range)
|
48
50
|
|
49
|
-
add_offense(
|
50
|
-
corrector.remove(
|
51
|
+
add_offense(arguments_range) do |corrector|
|
52
|
+
corrector.remove(arguments_range)
|
51
53
|
end
|
52
54
|
end
|
53
55
|
alias on_defs on_def
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def parentheses_required?(node, arguments_range)
|
60
|
+
return true if node.single_line? && !node.endless?
|
61
|
+
|
62
|
+
end_pos = arguments_range.end.end_pos
|
63
|
+
token_after_argument = range_between(end_pos, end_pos + 1).source
|
64
|
+
|
65
|
+
token_after_argument == '='
|
66
|
+
end
|
54
67
|
end
|
55
68
|
end
|
56
69
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for empty strings being assigned inside string interpolation.
|
7
|
+
#
|
8
|
+
# Empty strings are a meaningless outcome inside of string interpolation, so we remove them.
|
9
|
+
# Alternatively, when configured to do so, we prioritise using empty strings.
|
10
|
+
#
|
11
|
+
# While this cop would also apply to variables that are only going to be used as strings,
|
12
|
+
# RuboCop can't detect that, so we only check inside of string interpolation.
|
13
|
+
#
|
14
|
+
# @example EnforcedStyle: trailing_conditional (default)
|
15
|
+
# # bad
|
16
|
+
# "#{condition ? 'foo' : ''}"
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# "#{'foo' if condition}"
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# "#{condition ? '' : 'foo'}"
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# "#{'foo' unless condition}"
|
26
|
+
#
|
27
|
+
# @example EnforcedStyle: ternary
|
28
|
+
# # bad
|
29
|
+
# "#{'foo' if condition}"
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# "#{condition ? 'foo' : ''}"
|
33
|
+
#
|
34
|
+
# # bad
|
35
|
+
# "#{'foo' unless condition}"
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# "#{condition ? '' : 'foo'}"
|
39
|
+
#
|
40
|
+
class EmptyStringInsideInterpolation < Base
|
41
|
+
include ConfigurableEnforcedStyle
|
42
|
+
include Interpolation
|
43
|
+
extend AutoCorrector
|
44
|
+
|
45
|
+
MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
|
46
|
+
MSG_TERNARY = 'Do not return empty strings in string interpolation.'
|
47
|
+
|
48
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
49
|
+
def on_interpolation(node)
|
50
|
+
node.each_child_node(:if) do |child_node|
|
51
|
+
if style == :trailing_conditional
|
52
|
+
if empty_if_outcome?(child_node)
|
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
|
59
|
+
elsif style == :ternary
|
60
|
+
next unless child_node.modifier_form?
|
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
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def empty_if_outcome?(node)
|
79
|
+
empty_branch_outcome?(node.if_branch)
|
80
|
+
end
|
81
|
+
|
82
|
+
def empty_else_outcome?(node)
|
83
|
+
empty_branch_outcome?(node.else_branch)
|
84
|
+
end
|
85
|
+
|
86
|
+
def empty_branch_outcome?(branch)
|
87
|
+
return false unless branch
|
88
|
+
|
89
|
+
branch.nil_type? || (branch.str_type? && branch.value.empty?)
|
90
|
+
end
|
91
|
+
|
92
|
+
def ternary_style_autocorrect(node, outcome, condition)
|
93
|
+
add_offense(node, message: MSG_TERNARY) do |corrector|
|
94
|
+
corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -23,6 +23,17 @@ module RuboCop
|
|
23
23
|
# end
|
24
24
|
# ----
|
25
25
|
#
|
26
|
+
# The code `def method_name = body if condition` is considered a bad case by
|
27
|
+
# `Style/AmbiguousEndlessMethodDefinition` cop. So, to respect the user's intention to use
|
28
|
+
# an endless method definition in the `if` body, the following code is allowed:
|
29
|
+
#
|
30
|
+
# [source,ruby]
|
31
|
+
# ----
|
32
|
+
# if condition
|
33
|
+
# def method_name = body
|
34
|
+
# end
|
35
|
+
# ----
|
36
|
+
#
|
26
37
|
# NOTE: It is allowed when `defined?` argument has an undefined value,
|
27
38
|
# because using the modifier form causes the following incompatibility:
|
28
39
|
#
|
@@ -77,10 +88,14 @@ module RuboCop
|
|
77
88
|
[Style::SoleNestedConditional]
|
78
89
|
end
|
79
90
|
|
91
|
+
# rubocop:disable Metrics/AbcSize
|
80
92
|
def on_if(node)
|
93
|
+
return if endless_method?(node.body)
|
94
|
+
|
81
95
|
condition = node.condition
|
82
96
|
return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
|
83
97
|
pattern_matching_nodes(condition).any?
|
98
|
+
|
84
99
|
return unless (msg = message(node))
|
85
100
|
|
86
101
|
add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
|
@@ -90,9 +105,14 @@ module RuboCop
|
|
90
105
|
ignore_node(node)
|
91
106
|
end
|
92
107
|
end
|
108
|
+
# rubocop:enable Metrics/AbcSize
|
93
109
|
|
94
110
|
private
|
95
111
|
|
112
|
+
def endless_method?(body)
|
113
|
+
body&.any_def_type? && body.endless?
|
114
|
+
end
|
115
|
+
|
96
116
|
def defined_nodes(condition)
|
97
117
|
if condition.defined_type?
|
98
118
|
[condition]
|
@@ -112,12 +132,10 @@ module RuboCop
|
|
112
132
|
end
|
113
133
|
|
114
134
|
def pattern_matching_nodes(condition)
|
115
|
-
if condition.
|
135
|
+
if condition.any_match_pattern_type?
|
116
136
|
[condition]
|
117
137
|
else
|
118
|
-
condition.each_descendant.select
|
119
|
-
node.type?(:match_pattern, :match_pattern_p)
|
120
|
-
end
|
138
|
+
condition.each_descendant.select(&:any_match_pattern_type?)
|
121
139
|
end
|
122
140
|
end
|
123
141
|
|
@@ -28,19 +28,16 @@ module RuboCop
|
|
28
28
|
|
29
29
|
MSG = 'Avoid modifier `%<keyword>s` after another conditional.'
|
30
30
|
|
31
|
+
# rubocop:disable Metrics/AbcSize
|
31
32
|
def on_if(node)
|
32
33
|
return unless node.modifier_form? && node.body.if_type?
|
33
34
|
|
34
35
|
add_offense(node.loc.keyword, message: format(MSG, keyword: node.keyword)) do |corrector|
|
35
|
-
keyword
|
36
|
-
|
37
|
-
corrector.replace(node, <<~RUBY.chop)
|
38
|
-
#{keyword} #{node.condition.source}
|
39
|
-
#{node.if_branch.source}
|
40
|
-
end
|
41
|
-
RUBY
|
36
|
+
corrector.wrap(node.if_branch, "#{node.keyword} #{node.condition.source}\n", "\nend")
|
37
|
+
corrector.remove(node.if_branch.source_range.end.join(node.condition.source_range.end))
|
42
38
|
end
|
43
39
|
end
|
40
|
+
# rubocop:enable Metrics/AbcSize
|
44
41
|
end
|
45
42
|
end
|
46
43
|
end
|