rubocop 1.92.1 → 1.92.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/lib/rubocop/config.rb +0 -4
- data/lib/rubocop/config_obsoletion.rb +9 -0
- data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
- data/lib/rubocop/cop/layout/end_alignment.rb +2 -8
- data/lib/rubocop/cop/layout/extra_spacing.rb +17 -6
- data/lib/rubocop/cop/layout/line_length.rb +23 -5
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/single_line_block_chain.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +12 -0
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +1 -1
- data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
- data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
- data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
- data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +27 -8
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +17 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +14 -1
- data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
- data/lib/rubocop/cop/style/and_or.rb +33 -10
- data/lib/rubocop/cop/style/case_equality.rb +14 -3
- data/lib/rubocop/cop/style/class_and_module_children.rb +4 -3
- data/lib/rubocop/cop/style/endless_method.rb +34 -6
- data/lib/rubocop/cop/style/float_division.rb +16 -1
- data/lib/rubocop/cop/style/hash_as_last_array_item.rb +1 -1
- data/lib/rubocop/cop/style/hash_lookup_method.rb +10 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +1 -0
- data/lib/rubocop/cop/style/method_def_parentheses.rb +9 -1
- data/lib/rubocop/cop/style/numeric_predicate.rb +7 -0
- data/lib/rubocop/cop/style/parentheses_around_condition.rb +18 -0
- data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
- data/lib/rubocop/cop/style/raise_args.rb +66 -7
- data/lib/rubocop/cop/style/reduce_to_hash.rb +32 -34
- data/lib/rubocop/cop/style/redundant_begin.rb +24 -2
- data/lib/rubocop/cop/style/redundant_exception.rb +13 -9
- data/lib/rubocop/cop/style/redundant_self.rb +9 -1
- data/lib/rubocop/cop/style/single_line_methods.rb +21 -0
- data/lib/rubocop/cop/style/string_concatenation.rb +12 -1
- data/lib/rubocop/plugin/configuration_integrator.rb +24 -3
- data/lib/rubocop/runner.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e582386de45680cecfa5173359f6ccb24894b2440ef9d6fec5ec5c0d28d0b41
|
|
4
|
+
data.tar.gz: ef3bdbb7e39a1960f5e1feb41df80f7afef2f35148a44693566f725b99715a3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dff3c8ee84b5334d4aabb751f7de89b2cf2b87d65ad7970913132f558909d80da6306f60707f0ba1001d826a22c3491d9ab716c82defb20bb10216654cf75902
|
|
7
|
+
data.tar.gz: 6c6913ed8f1d4f30ee0044df54c2c370aa59b2f1f91de340f33295af795fcd56138b75583c37049c3fd117ffd51241600bd24e0c5e82d41b58304821a8170a25
|
data/lib/rubocop/config.rb
CHANGED
|
@@ -56,10 +56,6 @@ module RuboCop
|
|
|
56
56
|
deprecated_config = @for_cop[deprecated_cop_name]
|
|
57
57
|
next if deprecated_config.empty?
|
|
58
58
|
|
|
59
|
-
warn Rainbow(<<~WARNING).yellow
|
|
60
|
-
Warning: Using `#{deprecated_cop_name}` configuration in #{loaded_path} for `#{cop}`.
|
|
61
|
-
WARNING
|
|
62
|
-
|
|
63
59
|
cop_options.merge!(@for_cop[deprecated_cop_name])
|
|
64
60
|
end
|
|
65
61
|
end
|
|
@@ -76,6 +76,15 @@ module RuboCop
|
|
|
76
76
|
raise ValidationError, messages.join("\n")
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
# Messages for every violated rule, regardless of severity, without raising.
|
|
80
|
+
# Used for configuration the user does not own (a plugin's defaults), where an
|
|
81
|
+
# obsolete key should be reported but must not stop the run. Extracted cops are
|
|
82
|
+
# left out: whether their gem is loaded is only known once every plugin has been
|
|
83
|
+
# integrated, and this is called while plugins are still loading.
|
|
84
|
+
def messages
|
|
85
|
+
rules.grep_v(ExtractedCop).select(&:violated?).map(&:message)
|
|
86
|
+
end
|
|
87
|
+
|
|
79
88
|
def legacy_cop_names
|
|
80
89
|
# Used by DepartmentName#qualified_legacy_cop_name
|
|
81
90
|
cop_rules.map(&:old_name)
|
|
@@ -80,10 +80,6 @@ module RuboCop
|
|
|
80
80
|
include RangeHelp
|
|
81
81
|
extend AutoCorrector
|
|
82
82
|
|
|
83
|
-
def on_class(node)
|
|
84
|
-
check_other_alignment(node)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
83
|
def on_sclass(node)
|
|
88
84
|
if node.parent&.assignment?
|
|
89
85
|
check_asgn_alignment(node.parent, node)
|
|
@@ -91,10 +87,8 @@ module RuboCop
|
|
|
91
87
|
check_other_alignment(node)
|
|
92
88
|
end
|
|
93
89
|
end
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
check_other_alignment(node)
|
|
97
|
-
end
|
|
90
|
+
alias on_class on_sclass
|
|
91
|
+
alias on_module on_sclass
|
|
98
92
|
|
|
99
93
|
def on_if(node)
|
|
100
94
|
check_other_alignment(node) unless node.ternary?
|
|
@@ -30,6 +30,7 @@ module RuboCop
|
|
|
30
30
|
# some_object.method(arg) # this is some comment
|
|
31
31
|
class ExtraSpacing < Base
|
|
32
32
|
extend AutoCorrector
|
|
33
|
+
include LineLengthHelp
|
|
33
34
|
include PrecedingFollowingAlignment
|
|
34
35
|
include RangeHelp
|
|
35
36
|
|
|
@@ -77,9 +78,14 @@ module RuboCop
|
|
|
77
78
|
def check_assignment(token)
|
|
78
79
|
return unless aligned_with_preceding_equals_operator(token) == :no
|
|
79
80
|
|
|
81
|
+
tokens, align_to = equal_sign_alignment(token.line)
|
|
82
|
+
return if exceeds_max_line_length?(tokens, align_to)
|
|
83
|
+
|
|
80
84
|
message = format(MSG_UNALIGNED_ASGN, location: 'preceding')
|
|
81
85
|
add_offense(token.pos, message: message) do |corrector|
|
|
82
|
-
|
|
86
|
+
tokens.each do |asgn_token|
|
|
87
|
+
align_equal_sign(corrector, asgn_token, align_to)
|
|
88
|
+
end
|
|
83
89
|
end
|
|
84
90
|
end
|
|
85
91
|
|
|
@@ -240,14 +246,19 @@ module RuboCop
|
|
|
240
246
|
cop_config['ForceEqualSignAlignment']
|
|
241
247
|
end
|
|
242
248
|
|
|
243
|
-
def
|
|
244
|
-
lines = all_relevant_assignment_lines(
|
|
249
|
+
def equal_sign_alignment(line_number)
|
|
250
|
+
lines = all_relevant_assignment_lines(line_number)
|
|
245
251
|
tokens = assignment_tokens.select { |t| lines.include?(t.line) }
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
[tokens, tokens.map { |t| align_column(t) }.max]
|
|
254
|
+
end
|
|
249
255
|
|
|
250
|
-
|
|
256
|
+
def exceeds_max_line_length?(tokens, align_to)
|
|
257
|
+
max_line_length && tokens.any? do |token|
|
|
258
|
+
padding = align_to - token.pos.last_column
|
|
259
|
+
|
|
260
|
+
padding.positive? && line_length(token.pos.source_line) + padding > max_line_length
|
|
261
|
+
end
|
|
251
262
|
end
|
|
252
263
|
|
|
253
264
|
def align_equal_sign(corrector, token, align_to)
|
|
@@ -125,6 +125,7 @@ module RuboCop
|
|
|
125
125
|
@breakable_strings = {}
|
|
126
126
|
@endless_methods_by_line = {}
|
|
127
127
|
@heredocs = nil
|
|
128
|
+
@heredoc_openers = nil
|
|
128
129
|
|
|
129
130
|
return unless processed_source.raw_source.include?(';')
|
|
130
131
|
|
|
@@ -214,11 +215,24 @@ module RuboCop
|
|
|
214
215
|
range = breakable_node.source_range
|
|
215
216
|
|
|
216
217
|
existing = breakable_range_by_line_index[line_index]
|
|
217
|
-
return if existing
|
|
218
|
+
return if existing || after_heredoc_opener?(range)
|
|
218
219
|
|
|
219
220
|
breakable_range_by_line_index[line_index] = range
|
|
220
221
|
end
|
|
221
222
|
|
|
223
|
+
def after_heredoc_opener?(range)
|
|
224
|
+
heredoc_openers.any? do |opener|
|
|
225
|
+
opener.line == range.line && opener.begin_pos < range.begin_pos
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def heredoc_openers
|
|
230
|
+
ast = processed_source.ast
|
|
231
|
+
return [] if ast.nil?
|
|
232
|
+
|
|
233
|
+
@heredoc_openers ||= ast.each_node(:any_str).select(&:heredoc?).map(&:source_range)
|
|
234
|
+
end
|
|
235
|
+
|
|
222
236
|
def compact_nested_array_break?(node, breakable_node)
|
|
223
237
|
return false if !node.array_type? || !node.square_brackets?
|
|
224
238
|
return false unless breakable_node.equal?(node.children.first)
|
|
@@ -237,21 +251,24 @@ module RuboCop
|
|
|
237
251
|
tokens = processed_source.tokens.select { |t| t.type == :tSEMI }
|
|
238
252
|
tokens.reverse_each do |token|
|
|
239
253
|
range = breakable_range_after_semicolon(token)
|
|
240
|
-
|
|
254
|
+
next if range.nil? || after_heredoc_opener?(range)
|
|
255
|
+
|
|
256
|
+
breakable_range_by_line_index[range.line - 1] = range
|
|
241
257
|
end
|
|
242
258
|
end
|
|
243
259
|
|
|
244
260
|
def check_for_breakable_block(block_node)
|
|
245
261
|
return unless block_node.single_line?
|
|
246
|
-
return if receiver_contains_heredoc?(block_node)
|
|
247
262
|
|
|
248
263
|
line_index = block_node.loc.line - 1
|
|
249
264
|
return if breakable_string_delimiters[line_index]
|
|
250
265
|
|
|
251
266
|
range = breakable_block_range(block_node)
|
|
252
267
|
pos = range.begin_pos + 1
|
|
268
|
+
range = range_between(pos, pos + 1)
|
|
269
|
+
return if after_heredoc_opener?(range)
|
|
253
270
|
|
|
254
|
-
breakable_range_by_line_index[line_index] =
|
|
271
|
+
breakable_range_by_line_index[line_index] = range
|
|
255
272
|
end
|
|
256
273
|
|
|
257
274
|
# rubocop:disable-next Metrics/AbcSize
|
|
@@ -289,7 +306,8 @@ module RuboCop
|
|
|
289
306
|
!node.heredoc? &&
|
|
290
307
|
# TODO: strings inside hashes, kwargs and arrays are currently ignored,
|
|
291
308
|
# but could be considered in the future
|
|
292
|
-
!node.parent&.type?(:pair, :kwoptarg, :array)
|
|
309
|
+
!node.parent&.type?(:pair, :kwoptarg, :array) &&
|
|
310
|
+
!after_heredoc_opener?(node.source_range)
|
|
293
311
|
end
|
|
294
312
|
|
|
295
313
|
def breakable_block_range(block_node)
|
|
@@ -115,7 +115,7 @@ module RuboCop
|
|
|
115
115
|
node.loc.name
|
|
116
116
|
when :sclass
|
|
117
117
|
node.identifier.source_range
|
|
118
|
-
when :masgn
|
|
118
|
+
when :masgn, :op_asgn, :or_asgn, :and_asgn
|
|
119
119
|
node.lhs.source_range
|
|
120
120
|
else
|
|
121
121
|
# It is a wrapper with receiver of object attribute or access modifier.
|
|
@@ -28,7 +28,7 @@ module RuboCop
|
|
|
28
28
|
# These cops rewrite a method chained to the block in the same pass,
|
|
29
29
|
# which must not start a line of its own.
|
|
30
30
|
def self.autocorrect_incompatible_with
|
|
31
|
-
[Style::FloatDivision, Style::MapToHash, Style::NilComparison, Style::RedundantSort]
|
|
31
|
+
[Style::FloatDivision, Style::MapToHash, Style::MapToSet, Style::NilComparison, Style::RedundantSort]
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def on_send(node)
|
|
@@ -245,20 +245,30 @@ module RuboCop
|
|
|
245
245
|
return if with_space.is?(operator.source)
|
|
246
246
|
|
|
247
247
|
"Space around operator `#{operator.source}` detected."
|
|
248
|
-
elsif
|
|
248
|
+
elsif missing_surrounding_space?(with_space)
|
|
249
249
|
"Surrounding space missing for operator `#{operator.source}`."
|
|
250
|
-
elsif excess_leading_space?(type, operator, with_space) ||
|
|
250
|
+
elsif excess_leading_space?(type, operator, with_space, right_operand) ||
|
|
251
251
|
excess_trailing_space?(right_operand.source_range, with_space)
|
|
252
252
|
"Operator `#{operator.source}` should be surrounded " \
|
|
253
253
|
'by a single space.'
|
|
254
254
|
end
|
|
255
255
|
end
|
|
256
256
|
|
|
257
|
-
def
|
|
257
|
+
def missing_surrounding_space?(with_space)
|
|
258
|
+
return false if /\A\s.*\s\z/m.match?(with_space.source)
|
|
259
|
+
|
|
260
|
+
!with_space.source.start_with?(/\s/) || !line_continuation_after?(with_space)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def line_continuation_after?(range)
|
|
264
|
+
processed_source.buffer.source[range.end_pos, 2] == "\\\n"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def excess_leading_space?(type, operator, with_space, right_operand)
|
|
258
268
|
return false unless allow_for_alignment?
|
|
259
269
|
return false unless with_space.source.start_with?(EXCESSIVE_SPACE)
|
|
260
270
|
|
|
261
|
-
return !
|
|
271
|
+
return !aligned?(type, operator, right_operand) unless grouped_alignment_type?(type)
|
|
262
272
|
|
|
263
273
|
token = Token.new(operator, nil, operator.source)
|
|
264
274
|
align_preceding = aligned_with_preceding_equals_operator(token)
|
|
@@ -269,6 +279,23 @@ module RuboCop
|
|
|
269
279
|
aligned_with_subsequent_equals_operator(token) != :yes
|
|
270
280
|
end
|
|
271
281
|
|
|
282
|
+
def aligned?(type, operator, right_operand)
|
|
283
|
+
return true if aligned_with_operator?(operator)
|
|
284
|
+
|
|
285
|
+
type == :pair && aligned_with_another_hash_rocket?(right_operand)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def aligned_with_another_hash_rocket?(pair)
|
|
289
|
+
hash_rocket_columns(pair.parent)[pair.loc.operator.column] > 1
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def hash_rocket_columns(hash_node)
|
|
293
|
+
@hash_rocket_columns ||= {}.compare_by_identity
|
|
294
|
+
@hash_rocket_columns[hash_node] ||= hash_node.pairs.select(&:hash_rocket?).map do |pair|
|
|
295
|
+
pair.loc.operator.column
|
|
296
|
+
end.tally
|
|
297
|
+
end
|
|
298
|
+
|
|
272
299
|
def grouped_alignment_type?(type)
|
|
273
300
|
type == :assignment || (type == :special_asgn && force_equal_sign_alignment?)
|
|
274
301
|
end
|
|
@@ -136,11 +136,23 @@ module RuboCop
|
|
|
136
136
|
|
|
137
137
|
def autocorrect(corrector, range)
|
|
138
138
|
case range.source
|
|
139
|
+
when /\n/ then remove_line_continuation(corrector, range)
|
|
139
140
|
when /\s/ then corrector.remove(range)
|
|
140
141
|
else corrector.insert_before(range, ' ')
|
|
141
142
|
end
|
|
142
143
|
end
|
|
143
144
|
|
|
145
|
+
# When a heredoc body sits between the `\` and the brace, the newline before
|
|
146
|
+
# the brace ends the heredoc, and removing it would break the terminator.
|
|
147
|
+
def remove_line_continuation(corrector, range)
|
|
148
|
+
source = processed_source.buffer.source
|
|
149
|
+
return unless source[range.begin_pos - 1] == '\\'
|
|
150
|
+
|
|
151
|
+
begin_pos = range.begin_pos
|
|
152
|
+
begin_pos -= 1 while source[begin_pos - 1].match?(/[ \t\\]/)
|
|
153
|
+
corrector.remove(range_between(begin_pos, range.end_pos))
|
|
154
|
+
end
|
|
155
|
+
|
|
144
156
|
def style_for_empty_braces
|
|
145
157
|
case cop_config['EnforcedStyleForEmptyBraces']
|
|
146
158
|
when 'space' then :space
|
|
@@ -18,6 +18,7 @@ module RuboCop
|
|
|
18
18
|
# # good
|
|
19
19
|
# 'a'..'z'
|
|
20
20
|
class SpaceInsideRangeLiteral < Base
|
|
21
|
+
include RangeHelp
|
|
21
22
|
extend AutoCorrector
|
|
22
23
|
|
|
23
24
|
MSG = 'Space inside range literal.'
|
|
@@ -33,21 +34,38 @@ module RuboCop
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
36
|
def check(node)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# account for multiline range literals
|
|
41
|
-
expression.sub!(/#{escaped_op}\n\s*/, op)
|
|
42
|
-
|
|
43
|
-
return unless /(\s#{escaped_op})|(#{escaped_op}\s)/.match?(expression)
|
|
37
|
+
operator = node.loc.operator
|
|
38
|
+
spaces = [space_before(node, operator), space_after(node, operator)].compact
|
|
39
|
+
return if spaces.all? { |space| line_break_after_operator?(space, operator) }
|
|
44
40
|
|
|
45
41
|
add_offense(node) do |corrector|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
spaces.each do |space|
|
|
43
|
+
corrector.remove(space)
|
|
44
|
+
end
|
|
49
45
|
end
|
|
50
46
|
end
|
|
47
|
+
|
|
48
|
+
def space_before(node, operator)
|
|
49
|
+
return unless node.begin
|
|
50
|
+
|
|
51
|
+
whitespace_range(node.begin.source_range.end_pos, operator.begin_pos)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def space_after(node, operator)
|
|
55
|
+
return unless node.end
|
|
56
|
+
|
|
57
|
+
whitespace_range(operator.end_pos, node.end.source_range.begin_pos)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def line_break_after_operator?(space, operator)
|
|
61
|
+
space.begin_pos == operator.end_pos && space.source.start_with?("\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def whitespace_range(begin_pos, end_pos)
|
|
65
|
+
range = range_between(begin_pos, end_pos)
|
|
66
|
+
|
|
67
|
+
range if range.source.match?(/\A\s+\z/)
|
|
68
|
+
end
|
|
51
69
|
end
|
|
52
70
|
end
|
|
53
71
|
end
|
|
@@ -41,26 +41,22 @@ module RuboCop
|
|
|
41
41
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
45
44
|
def check(node)
|
|
46
45
|
return if node.parent&.regexp_type?
|
|
47
46
|
return unless /(?<!\\)#\{.*\}/.match?(node.source)
|
|
48
47
|
return if heredoc?(node)
|
|
49
|
-
return unless node
|
|
48
|
+
return unless single_quoted?(node)
|
|
50
49
|
return unless valid_syntax?(node)
|
|
51
50
|
|
|
52
51
|
add_offense(node) { |corrector| autocorrect(corrector, node) }
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
else
|
|
59
|
-
['"', '"']
|
|
60
|
-
end
|
|
54
|
+
def single_quoted?(node)
|
|
55
|
+
node.loc.begin&.is?("'") && node.loc.end
|
|
56
|
+
end
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
corrector.replace(node
|
|
58
|
+
def autocorrect(corrector, node)
|
|
59
|
+
corrector.replace(node, interpolated_source(node))
|
|
64
60
|
end
|
|
65
61
|
|
|
66
62
|
def heredoc?(node)
|
|
@@ -68,15 +64,22 @@ module RuboCop
|
|
|
68
64
|
end
|
|
69
65
|
|
|
70
66
|
def valid_syntax?(node)
|
|
71
|
-
|
|
72
|
-
node.source.sub(/\A'/, '%{').sub(/'\z/, '}')
|
|
73
|
-
else
|
|
74
|
-
node.source.gsub(/\A'|'\z/, '"')
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
processed_source = parse(double_quoted_string)
|
|
67
|
+
processed_source = parse(interpolated_source(node))
|
|
78
68
|
processed_source.valid_syntax? && processed_source.ast.dstr_type?
|
|
79
69
|
end
|
|
70
|
+
|
|
71
|
+
def interpolated_source(node)
|
|
72
|
+
content = node.source[1...-1]
|
|
73
|
+
return %("#{content}") unless content.include?('"')
|
|
74
|
+
return "%{#{content}}" unless continued_string_part?(node)
|
|
75
|
+
|
|
76
|
+
%("#{content.gsub(/\\.|"/) { |m| m == '"' ? '\"' : m }}")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A percent literal right after another string part is parsed as the `%` operator.
|
|
80
|
+
def continued_string_part?(node)
|
|
81
|
+
node.parent&.dstr_type? && node.left_sibling
|
|
82
|
+
end
|
|
80
83
|
end
|
|
81
84
|
end
|
|
82
85
|
end
|
|
@@ -40,6 +40,7 @@ module RuboCop
|
|
|
40
40
|
# NestedPercentLiteral violation to be emitted.
|
|
41
41
|
PERCENT_LITERAL_TYPES = PreferredDelimiters::PERCENT_LITERAL_TYPES
|
|
42
42
|
REGEXES = PERCENT_LITERAL_TYPES.map { |percent_literal| /\A#{percent_literal}\W/ }.freeze
|
|
43
|
+
FORMAT_SEQUENCE = /\A#{RuboCop::Cop::Utils::FormatString::SEQUENCE}\z/.freeze
|
|
43
44
|
|
|
44
45
|
def on_array(node)
|
|
45
46
|
process(node, *PERCENT_LITERAL_TYPES)
|
|
@@ -54,6 +55,8 @@ module RuboCop
|
|
|
54
55
|
def contains_percent_literals?(node)
|
|
55
56
|
node.each_child_node.any? do |child|
|
|
56
57
|
literal = child.children.first.to_s.scrub
|
|
58
|
+
next false if literal.match?(FORMAT_SEQUENCE)
|
|
59
|
+
|
|
57
60
|
REGEXES.any? { |regex| literal.match?(regex) }
|
|
58
61
|
end
|
|
59
62
|
end
|
|
@@ -57,7 +57,7 @@ module RuboCop
|
|
|
57
57
|
node = node.children.first while node&.begin_type? && node.children.one?
|
|
58
58
|
return false unless node
|
|
59
59
|
|
|
60
|
-
keyword_operator?(node) || modifier_expression?(node)
|
|
60
|
+
keyword_operator?(node) || modifier_expression?(node) || command_call_operand?(node)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def keyword_operator?(node)
|
|
@@ -69,6 +69,27 @@ module RuboCop
|
|
|
69
69
|
node.type?(:if, :while, :until) && node.modifier_form?
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
def command_call_operand?(node)
|
|
73
|
+
if node.assignment? || (node.call_type? && node.assignment_method?)
|
|
74
|
+
command_call_value?(node.children.last)
|
|
75
|
+
elsif node.send_type? && node.prefix_bang?
|
|
76
|
+
command_call_value?(node.receiver)
|
|
77
|
+
else
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def command_call_value?(node)
|
|
83
|
+
command_call?(node) || command_call_operand?(node)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def command_call?(node)
|
|
87
|
+
node = node.send_node if node.any_block_type?
|
|
88
|
+
return false unless node.type?(:call, :super, :yield)
|
|
89
|
+
|
|
90
|
+
node.arguments? && !node.parenthesized? && !node.operator_method?
|
|
91
|
+
end
|
|
92
|
+
|
|
72
93
|
def chained_calls?(node)
|
|
73
94
|
first_argument = node.first_argument
|
|
74
95
|
first_argument.call_type? && (node.children.last&.children&.count || 0) > 1
|
|
@@ -9,6 +9,11 @@ module RuboCop
|
|
|
9
9
|
# example, mistranslating an array of literals to percent string notation)
|
|
10
10
|
# rather than meant to be part of the resulting strings.
|
|
11
11
|
#
|
|
12
|
+
# When a quoted string contains whitespace, `%w` splits it into separate values,
|
|
13
|
+
# so removing the quotes would not restore the intended string.
|
|
14
|
+
# Such an array is corrected to an array literal instead, as long as its content
|
|
15
|
+
# reads as one with only string elements.
|
|
16
|
+
#
|
|
12
17
|
# @safety
|
|
13
18
|
# The cop is unsafe because the correction changes the values in the array
|
|
14
19
|
# and that might have been done purposely.
|
|
@@ -26,6 +31,12 @@ module RuboCop
|
|
|
26
31
|
#
|
|
27
32
|
# # good
|
|
28
33
|
# %w(foo bar)
|
|
34
|
+
#
|
|
35
|
+
# # bad - the quoted strings are split at whitespace
|
|
36
|
+
# %w('foo bar', 'baz')
|
|
37
|
+
#
|
|
38
|
+
# # good
|
|
39
|
+
# ['foo bar', 'baz']
|
|
29
40
|
class PercentStringArray < Base
|
|
30
41
|
include PercentLiteral
|
|
31
42
|
extend AutoCorrector
|
|
@@ -36,27 +47,69 @@ module RuboCop
|
|
|
36
47
|
|
|
37
48
|
MSG = "Within `%w`/`%W`, quotes and ',' are unnecessary and may be " \
|
|
38
49
|
'unwanted in the resulting strings.'
|
|
50
|
+
MSG_SPLIT_STRING = 'Within `%w`/`%W`, quoted strings are split at whitespace; ' \
|
|
51
|
+
'use an array literal instead.'
|
|
39
52
|
|
|
40
53
|
def on_array(node)
|
|
41
54
|
process(node, '%w', '%W')
|
|
42
55
|
end
|
|
43
56
|
|
|
44
57
|
def on_percent_literal(node)
|
|
45
|
-
|
|
58
|
+
if split_quoted_string?(node)
|
|
59
|
+
register_split_quoted_string(node)
|
|
60
|
+
elsif contains_quotes_or_commas?(node)
|
|
61
|
+
register_quotes_or_commas(node)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
node.each_value do |value|
|
|
49
|
-
range = value.source_range
|
|
65
|
+
private
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
def split_quoted_string?(node)
|
|
68
|
+
sources = node.values.map { |value| value.source.delete_suffix(',') }
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
sources.each_with_index.any? do |source, index|
|
|
71
|
+
next false unless (quote = unclosed_quote(source))
|
|
72
|
+
|
|
73
|
+
sources.drop(index + 1).any? { |rest| closing_quote?(rest, quote) }
|
|
56
74
|
end
|
|
57
75
|
end
|
|
58
76
|
|
|
59
|
-
|
|
77
|
+
def unclosed_quote(source)
|
|
78
|
+
quote = source[0]
|
|
79
|
+
return unless LEADING_QUOTE.match?(quote)
|
|
80
|
+
|
|
81
|
+
quote if source.length > 1 && !source.end_with?(quote)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def closing_quote?(source, quote)
|
|
85
|
+
source.end_with?(quote) && (source.length == 1 || !source.start_with?(quote))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def register_split_quoted_string(node)
|
|
89
|
+
add_offense(node, message: MSG_SPLIT_STRING) do |corrector|
|
|
90
|
+
next unless (array_literal = array_literal_source(node))
|
|
91
|
+
|
|
92
|
+
corrector.replace(node, array_literal)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def array_literal_source(node)
|
|
97
|
+
content = range_between(node.loc.begin.end_pos, node.loc.end.begin_pos).source
|
|
98
|
+
array_literal = "[#{content}]"
|
|
99
|
+
|
|
100
|
+
array_literal if string_array?(parse(array_literal))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def string_array?(processed_source)
|
|
104
|
+
return false unless processed_source.valid_syntax? && processed_source.comments.empty?
|
|
105
|
+
return false unless (array = processed_source.ast).array_type?
|
|
106
|
+
|
|
107
|
+
array.children.all? { |child| quoted_string?(child) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def quoted_string?(node)
|
|
111
|
+
node.type?(:str, :dstr) && node.loc?(:begin)
|
|
112
|
+
end
|
|
60
113
|
|
|
61
114
|
def contains_quotes_or_commas?(node)
|
|
62
115
|
node.values.any? do |value|
|
|
@@ -68,6 +121,19 @@ module RuboCop
|
|
|
68
121
|
QUOTES_AND_COMMAS.any? { |pat| literal.match?(pat) }
|
|
69
122
|
end
|
|
70
123
|
end
|
|
124
|
+
|
|
125
|
+
def register_quotes_or_commas(node)
|
|
126
|
+
add_offense(node) do |corrector|
|
|
127
|
+
node.each_value do |value|
|
|
128
|
+
range = value.source_range
|
|
129
|
+
|
|
130
|
+
match = range.source.match(TRAILING_QUOTE)
|
|
131
|
+
corrector.remove_trailing(range, match[0].length) if match
|
|
132
|
+
|
|
133
|
+
corrector.remove_leading(range, 1) if LEADING_QUOTE.match?(range.source)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
71
137
|
end
|
|
72
138
|
end
|
|
73
139
|
end
|