rubocop-performance 1.7.0 → 1.9.1
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 +8 -0
- data/config/default.yml +49 -7
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
- data/lib/rubocop/cop/performance/ancestors_include.rb +16 -12
- data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +77 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +17 -14
- data/lib/rubocop/cop/performance/bind_call.rb +9 -18
- data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
- data/lib/rubocop/cop/performance/caller.rb +14 -15
- data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
- data/lib/rubocop/cop/performance/casecmp.rb +13 -20
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +24 -28
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
- data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
- data/lib/rubocop/cop/performance/count.rb +14 -16
- data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
- data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
- data/lib/rubocop/cop/performance/detect.rb +65 -32
- data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
- data/lib/rubocop/cop/performance/end_with.rb +9 -13
- data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
- data/lib/rubocop/cop/performance/flat_map.rb +21 -22
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
- data/lib/rubocop/cop/performance/io_readlines.rb +27 -42
- data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
- data/lib/rubocop/cop/performance/open_struct.rb +3 -2
- data/lib/rubocop/cop/performance/range_include.rb +8 -6
- data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
- data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
- data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +6 -16
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +10 -18
- data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
- data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
- data/lib/rubocop/cop/performance/reverse_first.rb +5 -10
- data/lib/rubocop/cop/performance/size.rb +7 -6
- data/lib/rubocop/cop/performance/sort_reverse.rb +6 -15
- data/lib/rubocop/cop/performance/squeeze.rb +8 -11
- data/lib/rubocop/cop/performance/start_with.rb +9 -13
- data/lib/rubocop/cop/performance/string_include.rb +13 -14
- data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
- data/lib/rubocop/cop/performance/sum.rb +236 -0
- data/lib/rubocop/cop/performance/times_map.rb +12 -18
- data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
- data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
- data/lib/rubocop/cop/performance_cops.rb +6 -0
- data/lib/rubocop/performance/version.rb +6 -1
- metadata +25 -13
@@ -18,11 +18,11 @@ module RuboCop
|
|
18
18
|
# caller(1..1).first
|
19
19
|
# caller_locations(2..2).first
|
20
20
|
# caller_locations(1..1).first
|
21
|
-
class Caller <
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
class Caller < Base
|
22
|
+
extend AutoCorrector
|
23
|
+
|
24
|
+
MSG = 'Use `%<preferred_method>s` instead of `%<current_method>s`.'
|
25
|
+
RESTRICT_ON_SEND = %i[first []].freeze
|
26
26
|
|
27
27
|
def_node_matcher :slow_caller?, <<~PATTERN
|
28
28
|
{
|
@@ -41,25 +41,24 @@ module RuboCop
|
|
41
41
|
def on_send(node)
|
42
42
|
return unless caller_with_scope_method?(node)
|
43
43
|
|
44
|
-
add_offense(node)
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def message(node)
|
50
44
|
method_name = node.receiver.method_name
|
51
45
|
caller_arg = node.receiver.first_argument
|
52
46
|
n = caller_arg ? int_value(caller_arg) : 1
|
53
|
-
|
54
47
|
if node.method?(:[])
|
55
48
|
m = int_value(node.first_argument)
|
56
49
|
n += m
|
57
|
-
|
58
|
-
|
59
|
-
|
50
|
+
end
|
51
|
+
|
52
|
+
preferred_method = "#{method_name}(#{n}..#{n}).first"
|
53
|
+
|
54
|
+
message = format(MSG, preferred_method: preferred_method, current_method: node.source)
|
55
|
+
add_offense(node, message: message) do |corrector|
|
56
|
+
corrector.replace(node, preferred_method)
|
60
57
|
end
|
61
58
|
end
|
62
59
|
|
60
|
+
private
|
61
|
+
|
63
62
|
def int_value(node)
|
64
63
|
node.children[0]
|
65
64
|
end
|
@@ -53,9 +53,10 @@ module RuboCop
|
|
53
53
|
# when 5
|
54
54
|
# baz
|
55
55
|
# end
|
56
|
-
class CaseWhenSplat <
|
56
|
+
class CaseWhenSplat < Base
|
57
57
|
include Alignment
|
58
58
|
include RangeHelp
|
59
|
+
extend AutoCorrector
|
59
60
|
|
60
61
|
MSG = 'Reordering `when` conditions with a splat to the end ' \
|
61
62
|
'of the `when` branches can improve performance.'
|
@@ -66,24 +67,30 @@ module RuboCop
|
|
66
67
|
when_conditions = case_node.when_branches.flat_map(&:conditions)
|
67
68
|
|
68
69
|
splat_offenses(when_conditions).reverse_each do |condition|
|
69
|
-
|
70
|
+
next if ignored_node?(condition.parent)
|
71
|
+
|
72
|
+
ignore_node(condition.parent)
|
70
73
|
variable, = *condition
|
71
74
|
message = variable.array_type? ? ARRAY_MSG : MSG
|
72
|
-
add_offense(condition
|
75
|
+
add_offense(range(condition), message: message) do |corrector|
|
76
|
+
autocorrect(corrector, condition.parent)
|
77
|
+
end
|
73
78
|
end
|
74
79
|
end
|
75
80
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
private
|
82
|
+
|
83
|
+
def autocorrect(corrector, when_node)
|
84
|
+
if needs_reorder?(when_node)
|
85
|
+
reorder_condition(corrector, when_node)
|
86
|
+
else
|
87
|
+
inline_fix_branch(corrector, when_node)
|
83
88
|
end
|
84
89
|
end
|
85
90
|
|
86
|
-
|
91
|
+
def range(node)
|
92
|
+
node.parent.loc.keyword.join(node.source_range)
|
93
|
+
end
|
87
94
|
|
88
95
|
def replacement(conditions)
|
89
96
|
reordered = conditions.partition(&:splat_type?).reverse
|
@@ -19,8 +19,11 @@ module RuboCop
|
|
19
19
|
# # good
|
20
20
|
# str.casecmp('ABC').zero?
|
21
21
|
# 'abc'.casecmp(str).zero?
|
22
|
-
class Casecmp <
|
22
|
+
class Casecmp < Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
23
25
|
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
26
|
+
RESTRICT_ON_SEND = %i[== eql? !=].freeze
|
24
27
|
CASE_METHODS = %i[downcase upcase].freeze
|
25
28
|
|
26
29
|
def_node_matcher :downcase_eq, <<~PATTERN
|
@@ -48,21 +51,13 @@ module RuboCop
|
|
48
51
|
return unless downcase_eq(node) || eq_downcase(node)
|
49
52
|
return unless (parts = take_method_apart(node))
|
50
53
|
|
51
|
-
|
54
|
+
_receiver, method, arg, variable = parts
|
52
55
|
good_method = build_good_method(arg, variable)
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def autocorrect(node)
|
61
|
-
return unless (parts = take_method_apart(node))
|
62
|
-
|
63
|
-
receiver, method, arg, variable = parts
|
64
|
-
|
65
|
-
correction(node, receiver, method, arg, variable)
|
57
|
+
message = format(MSG, good: good_method, bad: node.source)
|
58
|
+
add_offense(node, message: message) do |corrector|
|
59
|
+
correction(corrector, node, method, arg, variable)
|
60
|
+
end
|
66
61
|
end
|
67
62
|
|
68
63
|
private
|
@@ -84,14 +79,12 @@ module RuboCop
|
|
84
79
|
[receiver, method, arg, variable]
|
85
80
|
end
|
86
81
|
|
87
|
-
def correction(
|
88
|
-
|
89
|
-
corrector.insert_before(node.loc.expression, '!') if method == :!=
|
82
|
+
def correction(corrector, node, method, arg, variable)
|
83
|
+
corrector.insert_before(node.loc.expression, '!') if method == :!=
|
90
84
|
|
91
|
-
|
85
|
+
replacement = build_good_method(arg, variable)
|
92
86
|
|
93
|
-
|
94
|
-
end
|
87
|
+
corrector.replace(node.loc.expression, replacement)
|
95
88
|
end
|
96
89
|
|
97
90
|
def build_good_method(arg, variable)
|
@@ -20,7 +20,7 @@ module RuboCop
|
|
20
20
|
# array.flatten!
|
21
21
|
# array.map! { |x| x.downcase }
|
22
22
|
# array
|
23
|
-
class ChainArrayAllocation <
|
23
|
+
class ChainArrayAllocation < Base
|
24
24
|
include RangeHelp
|
25
25
|
|
26
26
|
# These methods return a new array but only sometimes. They must be
|
@@ -29,47 +29,43 @@ module RuboCop
|
|
29
29
|
# [1,2].first # => 1
|
30
30
|
# [1,2].first(1) # => [1]
|
31
31
|
#
|
32
|
-
RETURN_NEW_ARRAY_WHEN_ARGS =
|
32
|
+
RETURN_NEW_ARRAY_WHEN_ARGS = %i[first last pop sample shift].to_set.freeze
|
33
33
|
|
34
34
|
# These methods return a new array only when called without a block.
|
35
|
-
RETURNS_NEW_ARRAY_WHEN_NO_BLOCK =
|
35
|
+
RETURNS_NEW_ARRAY_WHEN_NO_BLOCK = %i[zip product].to_set.freeze
|
36
36
|
|
37
37
|
# These methods ALWAYS return a new array
|
38
38
|
# after they're called it's safe to mutate the the resulting array
|
39
|
-
ALWAYS_RETURNS_NEW_ARRAY =
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
ALWAYS_RETURNS_NEW_ARRAY = %i[* + - collect compact drop
|
40
|
+
drop_while flatten map reject
|
41
|
+
reverse rotate select shuffle sort
|
42
|
+
take take_while transpose uniq
|
43
|
+
values_at |].to_set.freeze
|
44
44
|
|
45
45
|
# These methods have a mutation alternative. For example :collect
|
46
46
|
# can be called as :collect!
|
47
|
-
HAS_MUTATION_ALTERNATIVE =
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
HAS_MUTATION_ALTERNATIVE = %i[collect compact flatten map reject
|
48
|
+
reverse rotate select shuffle sort uniq].to_set.freeze
|
49
|
+
|
50
|
+
RETURNS_NEW_ARRAY = (ALWAYS_RETURNS_NEW_ARRAY + RETURNS_NEW_ARRAY_WHEN_NO_BLOCK).freeze
|
51
|
+
|
52
|
+
MSG = 'Use unchained `%<method>s` and `%<second_method>s!` '\
|
51
53
|
'(followed by `return array` if required) instead of chaining '\
|
52
54
|
'`%<method>s...%<second_method>s`.'
|
53
55
|
|
54
|
-
def_node_matcher :
|
55
|
-
{
|
56
|
-
(send
|
57
|
-
(
|
58
|
-
(send
|
59
|
-
}
|
56
|
+
def_node_matcher :chain_array_allocation?, <<~PATTERN
|
57
|
+
(send {
|
58
|
+
(send _ $%RETURN_NEW_ARRAY_WHEN_ARGS {int lvar ivar cvar gvar})
|
59
|
+
(block (send _ $%ALWAYS_RETURNS_NEW_ARRAY) ...)
|
60
|
+
(send _ $%RETURNS_NEW_ARRAY ...)
|
61
|
+
} $%HAS_MUTATION_ALTERNATIVE ...)
|
60
62
|
PATTERN
|
61
63
|
|
62
64
|
def on_send(node)
|
63
|
-
|
64
|
-
range = range_between(
|
65
|
-
|
66
|
-
|
67
|
-
)
|
68
|
-
add_offense(
|
69
|
-
node,
|
70
|
-
location: range,
|
71
|
-
message: format(MSG, method: fm, second_method: sm)
|
72
|
-
)
|
65
|
+
chain_array_allocation?(node) do |fm, sm|
|
66
|
+
range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
|
67
|
+
|
68
|
+
add_offense(range, message: format(MSG, method: fm, second_method: sm))
|
73
69
|
end
|
74
70
|
end
|
75
71
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Performance
|
8
|
+
# This cop identifies places where Array and Hash literals are used
|
9
|
+
# within loops. It is better to extract them into a local variable or constant
|
10
|
+
# to avoid unnecessary allocations on each iteration.
|
11
|
+
#
|
12
|
+
# You can set the minimum number of elements to consider
|
13
|
+
# an offense with `MinSize`.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # bad
|
17
|
+
# users.select do |user|
|
18
|
+
# %i[superadmin admin].include?(user.role)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# admin_roles = %i[superadmin admin]
|
23
|
+
# users.select do |user|
|
24
|
+
# admin_roles.include?(user.role)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# ADMIN_ROLES = %i[superadmin admin]
|
29
|
+
# ...
|
30
|
+
# users.select do |user|
|
31
|
+
# ADMIN_ROLES.include?(user.role)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class CollectionLiteralInLoop < Base
|
35
|
+
MSG = 'Avoid immutable %<literal_class>s literals in loops. '\
|
36
|
+
'It is better to extract it into a local variable or a constant.'
|
37
|
+
|
38
|
+
POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
|
39
|
+
LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
|
40
|
+
|
41
|
+
ENUMERABLE_METHOD_NAMES = (Enumerable.instance_methods + [:each]).to_set.freeze
|
42
|
+
NONMUTATING_ARRAY_METHODS = %i[& * + - <=> == [] all? any? assoc at
|
43
|
+
bsearch bsearch_index collect combination
|
44
|
+
compact count cycle deconstruct difference dig
|
45
|
+
drop drop_while each each_index empty? eql?
|
46
|
+
fetch filter find_index first flatten hash
|
47
|
+
include? index inspect intersection join
|
48
|
+
last length map max min minmax none? one? pack
|
49
|
+
permutation product rassoc reject
|
50
|
+
repeated_combination repeated_permutation reverse
|
51
|
+
reverse_each rindex rotate sample select shuffle
|
52
|
+
size slice sort sum take take_while
|
53
|
+
to_a to_ary to_h to_s transpose union uniq
|
54
|
+
values_at zip |].freeze
|
55
|
+
|
56
|
+
ARRAY_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_ARRAY_METHODS).to_set.freeze
|
57
|
+
|
58
|
+
NONMUTATING_HASH_METHODS = %i[< <= == > >= [] any? assoc compact dig
|
59
|
+
each each_key each_pair each_value empty?
|
60
|
+
eql? fetch fetch_values filter flatten has_key?
|
61
|
+
has_value? hash include? inspect invert key key?
|
62
|
+
keys? length member? merge rassoc rehash reject
|
63
|
+
select size slice to_a to_h to_hash to_proc to_s
|
64
|
+
transform_keys transform_values value? values values_at].freeze
|
65
|
+
|
66
|
+
HASH_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_HASH_METHODS).to_set.freeze
|
67
|
+
|
68
|
+
def_node_matcher :kernel_loop?, <<~PATTERN
|
69
|
+
(block
|
70
|
+
(send {nil? (const nil? :Kernel)} :loop)
|
71
|
+
...)
|
72
|
+
PATTERN
|
73
|
+
|
74
|
+
def_node_matcher :enumerable_loop?, <<~PATTERN
|
75
|
+
(block
|
76
|
+
(send $_ #enumerable_method? ...)
|
77
|
+
...)
|
78
|
+
PATTERN
|
79
|
+
|
80
|
+
def on_send(node)
|
81
|
+
receiver, method, = *node.children
|
82
|
+
return unless check_literal?(receiver, method) && parent_is_loop?(receiver)
|
83
|
+
|
84
|
+
message = format(MSG, literal_class: literal_class(receiver))
|
85
|
+
add_offense(receiver, message: message)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def check_literal?(node, method)
|
91
|
+
!node.nil? &&
|
92
|
+
nonmutable_method_of_array_or_hash?(node, method) &&
|
93
|
+
node.children.size >= min_size &&
|
94
|
+
node.recursive_basic_literal?
|
95
|
+
end
|
96
|
+
|
97
|
+
def nonmutable_method_of_array_or_hash?(node, method)
|
98
|
+
(node.array_type? && ARRAY_METHODS.include?(method)) ||
|
99
|
+
(node.hash_type? && HASH_METHODS.include?(method))
|
100
|
+
end
|
101
|
+
|
102
|
+
def parent_is_loop?(node)
|
103
|
+
node.each_ancestor.any? { |ancestor| loop?(ancestor, node) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def loop?(ancestor, node)
|
107
|
+
keyword_loop?(ancestor.type) ||
|
108
|
+
kernel_loop?(ancestor) ||
|
109
|
+
node_within_enumerable_loop?(node, ancestor)
|
110
|
+
end
|
111
|
+
|
112
|
+
def keyword_loop?(type)
|
113
|
+
LOOP_TYPES.include?(type)
|
114
|
+
end
|
115
|
+
|
116
|
+
def node_within_enumerable_loop?(node, ancestor)
|
117
|
+
enumerable_loop?(ancestor) do |receiver|
|
118
|
+
receiver != node && !receiver&.descendants&.include?(node)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def literal_class(node)
|
123
|
+
if node.array_type?
|
124
|
+
'Array'
|
125
|
+
elsif node.hash_type?
|
126
|
+
'Hash'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def enumerable_method?(method_name)
|
131
|
+
ENUMERABLE_METHOD_NAMES.include?(method_name)
|
132
|
+
end
|
133
|
+
|
134
|
+
def min_size
|
135
|
+
Integer(cop_config['MinSize'] || 1)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -23,8 +23,9 @@ module RuboCop
|
|
23
23
|
# array.max_by(&:foo)
|
24
24
|
# array.min_by(&:foo)
|
25
25
|
# array.sort_by { |a| a[:foo] }
|
26
|
-
class CompareWithBlock <
|
26
|
+
class CompareWithBlock < Base
|
27
27
|
include RangeHelp
|
28
|
+
extend AutoCorrector
|
28
29
|
|
29
30
|
MSG = 'Use `%<compare_method>s_by%<instead>s` instead of ' \
|
30
31
|
'`%<compare_method>s { |%<var_a>s, %<var_b>s| %<str_a>s ' \
|
@@ -51,27 +52,15 @@ module RuboCop
|
|
51
52
|
|
52
53
|
range = compare_range(send, node)
|
53
54
|
|
54
|
-
add_offense(
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
def autocorrect(node)
|
64
|
-
lambda do |corrector|
|
65
|
-
send, var_a, var_b, body = compare?(node)
|
66
|
-
method, arg, = replaceable_body?(body, var_a, var_b)
|
67
|
-
replacement =
|
68
|
-
if method == :[]
|
69
|
-
"#{send.method_name}_by { |a| a[#{arg.first.source}] }"
|
70
|
-
else
|
71
|
-
"#{send.method_name}_by(&:#{method})"
|
55
|
+
add_offense(range, message: message(send, method, var_a, var_b, args_a)) do |corrector|
|
56
|
+
replacement = if method == :[]
|
57
|
+
"#{send.method_name}_by { |a| a[#{args_a.first.source}] }"
|
58
|
+
else
|
59
|
+
"#{send.method_name}_by(&:#{method})"
|
60
|
+
end
|
61
|
+
corrector.replace(range, replacement)
|
72
62
|
end
|
73
|
-
|
74
|
-
replacement)
|
63
|
+
end
|
75
64
|
end
|
76
65
|
end
|
77
66
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop finds regular expressions with dynamic components that are all constants.
|
7
|
+
#
|
8
|
+
# Ruby allocates a new Regexp object every time it executes a code containing such
|
9
|
+
# a regular expression. It is more efficient to extract it into a constant
|
10
|
+
# or add an `/o` option to perform `#{}` interpolation only once and reuse that
|
11
|
+
# Regexp object.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# def tokens(pattern)
|
17
|
+
# pattern.scan(TOKEN).reject { |token| token.match?(/\A#{SEPARATORS}\Z/) }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# ALL_SEPARATORS = /\A#{SEPARATORS}\Z/
|
22
|
+
# def tokens(pattern)
|
23
|
+
# pattern.scan(TOKEN).reject { |token| token.match?(ALL_SEPARATORS) }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# def tokens(pattern)
|
28
|
+
# pattern.scan(TOKEN).reject { |token| token.match?(/\A#{SEPARATORS}\Z/o) }
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
class ConstantRegexp < Base
|
32
|
+
extend AutoCorrector
|
33
|
+
|
34
|
+
MSG = 'Extract this regexp into a constant or append an `/o` option to its options.'
|
35
|
+
|
36
|
+
def on_regexp(node)
|
37
|
+
return if within_const_assignment?(node) ||
|
38
|
+
!include_interpolated_const?(node) ||
|
39
|
+
node.single_interpolation?
|
40
|
+
|
41
|
+
add_offense(node) do |corrector|
|
42
|
+
corrector.insert_after(node, 'o')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def within_const_assignment?(node)
|
49
|
+
node.each_ancestor(:casgn).any?
|
50
|
+
end
|
51
|
+
|
52
|
+
def_node_matcher :regexp_escape?, <<~PATTERN
|
53
|
+
(send
|
54
|
+
(const nil? :Regexp) :escape const_type?)
|
55
|
+
PATTERN
|
56
|
+
|
57
|
+
def include_interpolated_const?(node)
|
58
|
+
return false unless node.interpolation?
|
59
|
+
|
60
|
+
node.each_child_node(:begin).all? do |begin_node|
|
61
|
+
inner_node = begin_node.children.first
|
62
|
+
inner_node && (inner_node.const_type? || regexp_escape?(inner_node))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|