rubocop 1.8.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +34 -4
- data/lib/rubocop.rb +5 -0
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
- data/lib/rubocop/config.rb +2 -2
- data/lib/rubocop/config_loader.rb +7 -14
- data/lib/rubocop/config_store.rb +12 -1
- data/lib/rubocop/cop/base.rb +1 -1
- data/lib/rubocop/cop/generator.rb +1 -3
- data/lib/rubocop/cop/internal_affairs.rb +5 -1
- data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
- data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
- data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
- data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
- data/lib/rubocop/cop/layout/class_structure.rb +7 -2
- data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
- data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
- data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
- data/lib/rubocop/cop/lint/symbol_conversion.rb +102 -0
- data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
- data/lib/rubocop/cop/mixin/comments_help.rb +0 -1
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
- data/lib/rubocop/cop/naming/variable_number.rb +1 -1
- data/lib/rubocop/cop/severity.rb +3 -3
- data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
- data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
- data/lib/rubocop/cop/style/float_division.rb +3 -0
- data/lib/rubocop/cop/style/format_string_token.rb +18 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +14 -7
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +96 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +1 -0
- data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
- data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +26 -2
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
- data/lib/rubocop/magic_comment.rb +30 -1
- data/lib/rubocop/options.rb +1 -1
- data/lib/rubocop/rspec/expect_offense.rb +5 -2
- data/lib/rubocop/runner.rb +1 -0
- data/lib/rubocop/version.rb +2 -2
- metadata +12 -3
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module InternalAffairs
|
6
|
+
# This cop checks for redundant `subject(:cop) { described_class.new }`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# RSpec.describe RuboCop::Cop::Department::Foo do
|
11
|
+
# subject(:cop) { described_class.new(config) }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# RSpec.describe RuboCop::Cop::Department::Foo, :config do
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class RedundantDescribedClassAsSubject < Base
|
19
|
+
include RangeHelp
|
20
|
+
extend AutoCorrector
|
21
|
+
|
22
|
+
MSG = 'Remove the redundant `subject`%<additional_message>s.'
|
23
|
+
|
24
|
+
def_node_matcher :described_class_subject?, <<~PATTERN
|
25
|
+
(block
|
26
|
+
(send nil? :subject
|
27
|
+
(sym :cop))
|
28
|
+
(args)
|
29
|
+
(send
|
30
|
+
(send nil? :described_class) :new
|
31
|
+
$...))
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_block(node)
|
35
|
+
return unless (described_class_arguments = described_class_subject?(node))
|
36
|
+
return if described_class_arguments.count >= 2
|
37
|
+
|
38
|
+
describe = find_describe_method_node(node)
|
39
|
+
|
40
|
+
unless (exist_config = describe.last_argument.source == ':config')
|
41
|
+
additional_message = ' and specify `:config` in `describe`'
|
42
|
+
end
|
43
|
+
|
44
|
+
message = format(MSG, additional_message: additional_message)
|
45
|
+
|
46
|
+
add_offense(node, message: message) do |corrector|
|
47
|
+
corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
|
48
|
+
|
49
|
+
corrector.insert_after(describe.last_argument, ', :config') unless exist_config
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def find_describe_method_node(block_node)
|
56
|
+
block_node.ancestors.find { |node| node.block_type? && node.method?(:describe) }.send_node
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module InternalAffairs
|
6
|
+
# This cop checks that `let` is `RuboCop::Config.new` with no arguments.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# RSpec.describe RuboCop::Cop::Department::Foo, :config do
|
11
|
+
# let(:config) { RuboCop::Config.new }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# RSpec.describe RuboCop::Cop::Department::Foo, :config do
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# RSpec.describe RuboCop::Cop::Department::Foo, :config do
|
19
|
+
# let(:config) { RuboCop::Config.new(argument) }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class RedundantLetRuboCopConfigNew < Base
|
23
|
+
include RangeHelp
|
24
|
+
extend AutoCorrector
|
25
|
+
|
26
|
+
MSG = 'Remove `let` that is `RuboCop::Config.new` with no arguments%<additional_message>s.'
|
27
|
+
|
28
|
+
def_node_matcher :let_rubocop_config_new?, <<~PATTERN
|
29
|
+
(block
|
30
|
+
(send nil? :let
|
31
|
+
(sym :config))
|
32
|
+
(args)
|
33
|
+
(send
|
34
|
+
(const
|
35
|
+
(const nil? :RuboCop) :Config) :new))
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_block(node)
|
39
|
+
return unless let_rubocop_config_new?(node)
|
40
|
+
|
41
|
+
describe = find_describe_method_node(node)
|
42
|
+
|
43
|
+
unless (exist_config = describe.last_argument.source == ':config')
|
44
|
+
additional_message = ' and specify `:config` in `describe`'
|
45
|
+
end
|
46
|
+
|
47
|
+
message = format(MSG, additional_message: additional_message)
|
48
|
+
|
49
|
+
add_offense(node, message: message) do |corrector|
|
50
|
+
corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
|
51
|
+
|
52
|
+
corrector.insert_after(describe.last_argument, ', :config') unless exist_config
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def find_describe_method_node(block_node)
|
59
|
+
block_node.ancestors.find { |node| node.block_type? && node.method?(:describe) }.send_node
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -213,7 +213,12 @@ module RuboCop
|
|
213
213
|
name = node.method_name.to_s
|
214
214
|
category, = categories.find { |_, names| names.include?(name) }
|
215
215
|
key = category || name
|
216
|
-
visibility_key =
|
216
|
+
visibility_key =
|
217
|
+
if node.def_modifier?
|
218
|
+
"#{name}_methods"
|
219
|
+
else
|
220
|
+
"#{node_visibility(node)}_#{key}"
|
221
|
+
end
|
217
222
|
expected_order.include?(visibility_key) ? visibility_key : key
|
218
223
|
end
|
219
224
|
|
@@ -264,7 +269,7 @@ module RuboCop
|
|
264
269
|
|
265
270
|
def source_range_with_comment(node)
|
266
271
|
begin_pos, end_pos =
|
267
|
-
if node.def_type?
|
272
|
+
if node.def_type? && !node.method?(:initialize) || node.send_type? && node.def_modifier?
|
268
273
|
start_node = find_visibility_start(node) || node
|
269
274
|
end_node = find_visibility_end(node) || node
|
270
275
|
[begin_pos_with_comment(start_node),
|
@@ -25,12 +25,18 @@ module RuboCop
|
|
25
25
|
# '10'.to_i
|
26
26
|
# '10.2'.to_f
|
27
27
|
# '10'.to_c
|
28
|
+
# ['1', '2', '3'].map(&:to_i)
|
29
|
+
# foo.try(:to_f)
|
30
|
+
# bar.send(:to_c)
|
28
31
|
#
|
29
32
|
# # good
|
30
33
|
#
|
31
34
|
# Integer('10', 10)
|
32
35
|
# Float('10.2')
|
33
36
|
# Complex('10')
|
37
|
+
# ['1', '2', '3'].map { |i| Integer(i, 10) }
|
38
|
+
# foo.try { |i| Float(i) }
|
39
|
+
# bar.send { |i| Complex(i) }
|
34
40
|
#
|
35
41
|
# @example IgnoredMethods: [minutes]
|
36
42
|
#
|
@@ -52,22 +58,33 @@ module RuboCop
|
|
52
58
|
}.freeze
|
53
59
|
MSG = 'Replace unsafe number conversion with number '\
|
54
60
|
'class parsing, instead of using '\
|
55
|
-
'%<
|
61
|
+
'%<current>s, use stricter '\
|
56
62
|
'%<corrected_method>s.'
|
57
|
-
|
63
|
+
METHODS = CONVERSION_METHOD_CLASS_MAPPING.keys.map(&:inspect).join(' ')
|
58
64
|
|
59
65
|
def_node_matcher :to_method, <<~PATTERN
|
60
|
-
(send $_ ${
|
66
|
+
(send $_ ${#{METHODS}})
|
67
|
+
PATTERN
|
68
|
+
|
69
|
+
def_node_matcher :to_method_symbol, <<~PATTERN
|
70
|
+
{(send _ $_ ${(sym ${#{METHODS}})} ...)
|
71
|
+
(send _ $_ ${(block_pass (sym ${#{METHODS}}))} ...)}
|
61
72
|
PATTERN
|
62
73
|
|
63
74
|
def on_send(node)
|
75
|
+
handle_conversion_method(node)
|
76
|
+
handle_as_symbol(node)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def handle_conversion_method(node)
|
64
82
|
to_method(node) do |receiver, to_method|
|
65
83
|
next if receiver.nil? || ignore_receiver?(receiver)
|
66
84
|
|
67
85
|
message = format(
|
68
86
|
MSG,
|
69
|
-
|
70
|
-
to_method: to_method,
|
87
|
+
current: "#{receiver.source}.#{to_method}",
|
71
88
|
corrected_method: correct_method(node, receiver)
|
72
89
|
)
|
73
90
|
add_offense(node, message: message) do |corrector|
|
@@ -76,13 +93,31 @@ module RuboCop
|
|
76
93
|
end
|
77
94
|
end
|
78
95
|
|
79
|
-
|
96
|
+
def handle_as_symbol(node)
|
97
|
+
to_method_symbol(node) do |receiver, sym_node, to_method|
|
98
|
+
next if receiver.nil?
|
99
|
+
|
100
|
+
message = format(
|
101
|
+
MSG,
|
102
|
+
current: sym_node.source,
|
103
|
+
corrected_method: correct_sym_method(to_method)
|
104
|
+
)
|
105
|
+
add_offense(node, message: message) do |corrector|
|
106
|
+
corrector.replace(sym_node, correct_sym_method(to_method))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
80
110
|
|
81
111
|
def correct_method(node, receiver)
|
82
112
|
format(CONVERSION_METHOD_CLASS_MAPPING[node.method_name],
|
83
113
|
number_object: receiver.source)
|
84
114
|
end
|
85
115
|
|
116
|
+
def correct_sym_method(to_method)
|
117
|
+
body = format(CONVERSION_METHOD_CLASS_MAPPING[to_method], number_object: 'i')
|
118
|
+
"{ |i| #{body} }"
|
119
|
+
end
|
120
|
+
|
86
121
|
def ignore_receiver?(receiver)
|
87
122
|
if receiver.send_type? && ignored_method?(receiver.method_name)
|
88
123
|
true
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks for uses of numbered parameter assignment.
|
7
|
+
# It emulates the following warning in Ruby 2.7:
|
8
|
+
#
|
9
|
+
# % ruby -ve '_1 = :value'
|
10
|
+
# ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]
|
11
|
+
# -e:1: warning: `_1' is reserved for numbered parameter; consider another name
|
12
|
+
#
|
13
|
+
# Assiging to numbered parameter (from `_1` to `_9`) cause an error in Ruby 3.0.
|
14
|
+
#
|
15
|
+
# % ruby -ve '_1 = :value'
|
16
|
+
# ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19]
|
17
|
+
# -e:1: _1 is reserved for numbered parameter
|
18
|
+
#
|
19
|
+
# NOTE: The parametered parameters are from `_1` to `_9`. This cop checks `_0`, and over `_10`
|
20
|
+
# as well to prevent confusion.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# _1 = :value
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# non_numbered_parameter_name = :value
|
29
|
+
#
|
30
|
+
class NumberedParameterAssignment < Base
|
31
|
+
NUM_PARAM_MSG = '`_%<number>s` is reserved for numbered parameter; consider another name.'
|
32
|
+
LVAR_MSG = '`_%<number>s` is similar to numbered parameter; consider another name.'
|
33
|
+
NUMBERED_PARAMETER_RANGE = (1..9).freeze
|
34
|
+
|
35
|
+
def on_lvasgn(node)
|
36
|
+
lhs, _rhs = *node
|
37
|
+
return unless /\A_(\d+)\z/ =~ lhs
|
38
|
+
|
39
|
+
number = Regexp.last_match(1).to_i
|
40
|
+
template = NUMBERED_PARAMETER_RANGE.include?(number) ? NUM_PARAM_MSG : LVAR_MSG
|
41
|
+
|
42
|
+
add_offense(node, message: format(template, number: number))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks for unintended or-assignment to a constant.
|
7
|
+
#
|
8
|
+
# Constants should always be assigned in the same location. And its value
|
9
|
+
# should always be the same. If constants are assigned in multiple
|
10
|
+
# locations, the result may vary depending on the order of `require`.
|
11
|
+
#
|
12
|
+
# Also, if you already have such an implementation, auto-correction may
|
13
|
+
# change the result.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# CONST ||= 1
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# CONST = 1
|
22
|
+
#
|
23
|
+
class OrAssignmentToConstant < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
|
26
|
+
MSG = 'Avoid using or-assignment with constants.'
|
27
|
+
|
28
|
+
def on_or_asgn(node)
|
29
|
+
lhs, _rhs = *node
|
30
|
+
return unless lhs&.casgn_type?
|
31
|
+
|
32
|
+
add_offense(node.loc.operator) do |corrector|
|
33
|
+
corrector.replace(node.loc.operator, '=')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks for uses of literal strings converted to
|
7
|
+
# a symbol where a literal symbol could be used instead.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# 'string'.to_sym
|
12
|
+
# :symbol.to_sym
|
13
|
+
# 'underscored_string'.to_sym
|
14
|
+
# :'underscored_symbol'
|
15
|
+
# 'hyphenated-string'.to_sym
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# :string
|
19
|
+
# :symbol
|
20
|
+
# :underscored_string
|
21
|
+
# :underscored_symbol
|
22
|
+
# :'hyphenated-string'
|
23
|
+
#
|
24
|
+
class SymbolConversion < Base
|
25
|
+
extend AutoCorrector
|
26
|
+
|
27
|
+
MSG = 'Unnecessary symbol conversion; use `%<correction>s` instead.'
|
28
|
+
RESTRICT_ON_SEND = %i[to_sym intern].freeze
|
29
|
+
|
30
|
+
def on_send(node)
|
31
|
+
return unless node.receiver.str_type? || node.receiver.sym_type?
|
32
|
+
|
33
|
+
register_offense(node, correction: node.receiver.value.to_sym.inspect)
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_sym(node)
|
37
|
+
return if properly_quoted?(node.source, node.value.inspect)
|
38
|
+
|
39
|
+
# `alias` arguments are symbols but since a symbol that requires
|
40
|
+
# being quoted is not a valid method identifier, it can be ignored
|
41
|
+
return if in_alias?(node)
|
42
|
+
|
43
|
+
# The `%I[]` and `%i[]` macros are parsed as normal arrays of symbols
|
44
|
+
# so they need to be ignored.
|
45
|
+
return if in_percent_literal_array?(node)
|
46
|
+
|
47
|
+
# Symbol hash keys have a different format and need to be handled separately
|
48
|
+
return correct_hash_key(node) if hash_key?(node)
|
49
|
+
|
50
|
+
register_offense(node, correction: node.value.inspect)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def register_offense(node, correction:, message: format(MSG, correction: correction))
|
56
|
+
add_offense(node, message: message) do |corrector|
|
57
|
+
corrector.replace(node, correction)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def properly_quoted?(source, value)
|
62
|
+
return true unless source.match?(/['"]/)
|
63
|
+
|
64
|
+
source == value ||
|
65
|
+
# `Symbol#inspect` uses double quotes, but allow single-quoted
|
66
|
+
# symbols to work as well.
|
67
|
+
source.tr("'", '"') == value
|
68
|
+
end
|
69
|
+
|
70
|
+
def in_alias?(node)
|
71
|
+
node.parent&.alias_type?
|
72
|
+
end
|
73
|
+
|
74
|
+
def in_percent_literal_array?(node)
|
75
|
+
node.parent&.array_type? && node.parent&.percent_literal?
|
76
|
+
end
|
77
|
+
|
78
|
+
def hash_key?(node)
|
79
|
+
node.parent&.pair_type? && node == node.parent.child_nodes.first
|
80
|
+
end
|
81
|
+
|
82
|
+
def correct_hash_key(node)
|
83
|
+
# Although some operators can be converted to symbols normally
|
84
|
+
# (ie. `:==`), these are not accepted as hash keys and will
|
85
|
+
# raise a syntax error (eg. `{ ==: ... }`). Therefore, if the
|
86
|
+
# symbol does not start with an alpha-numeric or underscore, it
|
87
|
+
# will be ignored.
|
88
|
+
return unless node.value.to_s.match?(/\A[a-z0-9_]/i)
|
89
|
+
|
90
|
+
correction = node.value.inspect.delete(':')
|
91
|
+
return if properly_quoted?(node.source, correction)
|
92
|
+
|
93
|
+
register_offense(
|
94
|
+
node,
|
95
|
+
correction: correction,
|
96
|
+
message: format(MSG, correction: "#{correction}:")
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks for "triple quotes" (strings delimted by any odd number
|
7
|
+
# of quotes greater than 1).
|
8
|
+
#
|
9
|
+
# Ruby allows multiple strings to be implicitly concatenated by just
|
10
|
+
# being adjacent in a statement (ie. `"foo""bar" == "foobar"`). This sometimes
|
11
|
+
# gives the impression that there is something special about triple quotes, but
|
12
|
+
# in fact it is just extra unnecessary quotes and produces the same string. Each
|
13
|
+
# pair of quotes produces an additional concatenated empty string, so the result
|
14
|
+
# is still only the "actual" string within the delimiters.
|
15
|
+
#
|
16
|
+
# NOTE: Although this cop is called triple quotes, the same behavior is present
|
17
|
+
# for strings delimited by 5, 7, etc. quotation marks.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# # bad
|
21
|
+
# """
|
22
|
+
# A string
|
23
|
+
# """
|
24
|
+
#
|
25
|
+
# # bad
|
26
|
+
# '''
|
27
|
+
# A string
|
28
|
+
# '''
|
29
|
+
#
|
30
|
+
# # good
|
31
|
+
# "
|
32
|
+
# A string
|
33
|
+
# "
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# <<STRING
|
37
|
+
# A string
|
38
|
+
# STRING
|
39
|
+
#
|
40
|
+
# # good (but not the same spacing as the bad case)
|
41
|
+
# 'A string'
|
42
|
+
class TripleQuotes < Base
|
43
|
+
extend AutoCorrector
|
44
|
+
|
45
|
+
MSG = 'Delimiting a string with multiple quotes has no effect, use a single quote instead.'
|
46
|
+
|
47
|
+
def on_dstr(node)
|
48
|
+
return if (empty_str_nodes = empty_str_nodes(node)).none?
|
49
|
+
|
50
|
+
opening_quotes = node.source.scan(/(?<=\A)['"]*/)[0]
|
51
|
+
return if opening_quotes.size < 3
|
52
|
+
|
53
|
+
# If the node is composed of only empty `str` nodes, keep one
|
54
|
+
empty_str_nodes.shift if empty_str_nodes.size == node.child_nodes.size
|
55
|
+
|
56
|
+
add_offense(node) do |corrector|
|
57
|
+
empty_str_nodes.each do |str|
|
58
|
+
corrector.remove(str)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def empty_str_nodes(node)
|
66
|
+
node.each_child_node(:str).select { |str| str.value == '' }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|