rubocop-performance 1.4.1 → 1.6.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/LICENSE.txt +1 -1
- data/README.md +5 -1
- data/config/default.yml +29 -10
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
- data/lib/rubocop/cop/performance/bind_call.rb +87 -0
- data/lib/rubocop/cop/performance/caller.rb +3 -3
- data/lib/rubocop/cop/performance/casecmp.rb +5 -3
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +1 -1
- data/lib/rubocop/cop/performance/compare_with_block.rb +2 -2
- data/lib/rubocop/cop/performance/count.rb +3 -6
- data/lib/rubocop/cop/performance/delete_prefix.rb +96 -0
- data/lib/rubocop/cop/performance/delete_suffix.rb +96 -0
- data/lib/rubocop/cop/performance/detect.rb +1 -5
- data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
- data/lib/rubocop/cop/performance/end_with.rb +36 -13
- data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
- data/lib/rubocop/cop/performance/flat_map.rb +9 -2
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
- data/lib/rubocop/cop/performance/open_struct.rb +1 -1
- data/lib/rubocop/cop/performance/range_include.rb +1 -1
- data/lib/rubocop/cop/performance/redundant_block_call.rb +3 -3
- data/lib/rubocop/cop/performance/redundant_match.rb +2 -2
- data/lib/rubocop/cop/performance/redundant_merge.rb +22 -9
- data/lib/rubocop/cop/performance/regexp_match.rb +13 -13
- data/lib/rubocop/cop/performance/reverse_each.rb +3 -2
- data/lib/rubocop/cop/performance/size.rb +2 -2
- data/lib/rubocop/cop/performance/start_with.rb +36 -16
- data/lib/rubocop/cop/performance/string_replacement.rb +4 -11
- data/lib/rubocop/cop/performance/times_map.rb +1 -1
- data/lib/rubocop/cop/performance/unfreeze_string.rb +3 -7
- data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
- data/lib/rubocop/cop/performance_cops.rb +5 -0
- data/lib/rubocop/performance/inject.rb +1 -1
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +13 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 111d420207542c8522d9f34b5b8bf24356572b5fbcf0c11dfcb1e7f738a4c76f
|
4
|
+
data.tar.gz: 97632031f51e154320bc3d585da448740596436882b67277f1b5d26ddfe55fba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a36e6950e6fe7847303c87862df76767d32c4f68640c229ad4ceb6115d4ae2093be18a8296c2e49324c67e8eb06dd99db42f8823586f1b572e10a74693b831d1
|
7
|
+
data.tar.gz: 782352b1a7c420ac3c5498aeff749577e6280fb35d6e36844ae875511e9b6b44becfd9a2789d1b4c672cb71a91b8ab1cc63c89ec41abda8d1f34f1dacd26a07c
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -16,7 +16,7 @@ gem install rubocop-performance
|
|
16
16
|
or if you use bundler put this in your `Gemfile`
|
17
17
|
|
18
18
|
```ruby
|
19
|
-
gem 'rubocop-performance'
|
19
|
+
gem 'rubocop-performance', require: false
|
20
20
|
```
|
21
21
|
|
22
22
|
## Usage
|
@@ -72,6 +72,10 @@ Performance/Size:
|
|
72
72
|
- lib/example.rb
|
73
73
|
```
|
74
74
|
|
75
|
+
## Documentation
|
76
|
+
|
77
|
+
You can read a lot more about RuboCop Performance in its [official docs](https://docs.rubocop.org/rubocop-performance/).
|
78
|
+
|
75
79
|
## Contributing
|
76
80
|
|
77
81
|
Checkout the [contribution guidelines](CONTRIBUTING.md).
|
data/config/default.yml
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# This is the default configuration file.
|
2
2
|
|
3
|
+
Performance/BindCall:
|
4
|
+
Description: 'Use `bind_call(obj, args, ...)` instead of `bind(obj).call(args, ...)`.'
|
5
|
+
Enabled: true
|
6
|
+
VersionAdded: '1.6'
|
7
|
+
|
3
8
|
Performance/Caller:
|
4
9
|
Description: >-
|
5
10
|
Use `caller(n..n)` instead of `caller`.
|
@@ -21,6 +26,7 @@ Performance/Casecmp:
|
|
21
26
|
Use `casecmp` rather than `downcase ==`, `upcase ==`, `== downcase`, or `== upcase`..
|
22
27
|
Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringcasecmp-vs-stringdowncase---code'
|
23
28
|
Enabled: true
|
29
|
+
Safe: false
|
24
30
|
VersionAdded: '0.36'
|
25
31
|
|
26
32
|
Performance/ChainArrayAllocation:
|
@@ -44,11 +50,22 @@ Performance/Count:
|
|
44
50
|
# This cop has known compatibility issues with `ActiveRecord` and other
|
45
51
|
# frameworks. ActiveRecord's `count` ignores the block that is passed to it.
|
46
52
|
# For more information, see the documentation in the cop itself.
|
47
|
-
|
48
|
-
SafeMode: true
|
53
|
+
SafeAutoCorrect: false
|
49
54
|
Enabled: true
|
50
55
|
VersionAdded: '0.31'
|
51
|
-
VersionChanged: '
|
56
|
+
VersionChanged: '1.5'
|
57
|
+
|
58
|
+
Performance/DeletePrefix:
|
59
|
+
Description: 'Use `delete_prefix` instead of `gsub`.'
|
60
|
+
Enabled: true
|
61
|
+
SafeMultiline: true
|
62
|
+
VersionAdded: '1.6'
|
63
|
+
|
64
|
+
Performance/DeleteSuffix:
|
65
|
+
Description: 'Use `delete_suffix` instead of `gsub`.'
|
66
|
+
Enabled: true
|
67
|
+
SafeMultiline: true
|
68
|
+
VersionAdded: '1.6'
|
52
69
|
|
53
70
|
Performance/Detect:
|
54
71
|
Description: >-
|
@@ -59,10 +76,10 @@ Performance/Detect:
|
|
59
76
|
# frameworks. `ActiveRecord` does not implement a `detect` method and `find`
|
60
77
|
# has its own meaning. Correcting `ActiveRecord` methods with this cop
|
61
78
|
# should be considered unsafe.
|
62
|
-
|
79
|
+
SafeAutoCorrect: false
|
63
80
|
Enabled: true
|
64
81
|
VersionAdded: '0.30'
|
65
|
-
VersionChanged: '
|
82
|
+
VersionChanged: '1.5'
|
66
83
|
|
67
84
|
Performance/DoubleStartEndWith:
|
68
85
|
Description: >-
|
@@ -84,11 +101,12 @@ Performance/EndWith:
|
|
84
101
|
SafeAutoCorrect: false
|
85
102
|
AutoCorrect: false
|
86
103
|
Enabled: true
|
104
|
+
SafeMultiline: true
|
87
105
|
VersionAdded: '0.36'
|
88
|
-
VersionChanged: '
|
106
|
+
VersionChanged: '1.6'
|
89
107
|
|
90
108
|
Performance/FixedSize:
|
91
|
-
Description: 'Do not compute the size of statically sized objects except in constants'
|
109
|
+
Description: 'Do not compute the size of statically sized objects except in constants.'
|
92
110
|
Enabled: true
|
93
111
|
VersionAdded: '0.35'
|
94
112
|
|
@@ -96,7 +114,7 @@ Performance/FlatMap:
|
|
96
114
|
Description: >-
|
97
115
|
Use `Enumerable#flat_map`
|
98
116
|
instead of `Enumerable#map...Array#flatten(1)`
|
99
|
-
or `Enumberable#collect..Array#flatten(1)
|
117
|
+
or `Enumberable#collect..Array#flatten(1)`.
|
100
118
|
Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code'
|
101
119
|
Enabled: true
|
102
120
|
VersionAdded: '0.30'
|
@@ -107,7 +125,7 @@ Performance/FlatMap:
|
|
107
125
|
# `flatten` without any parameters can flatten multiple levels.
|
108
126
|
|
109
127
|
Performance/InefficientHashSearch:
|
110
|
-
Description: 'Use `key?` or `value?` instead of `keys.include?` or `values.include
|
128
|
+
Description: 'Use `key?` or `value?` instead of `keys.include?` or `values.include?`.'
|
111
129
|
Reference: 'https://github.com/JuanitoFatas/fast-ruby#hashkey-instead-of-hashkeysinclude-code'
|
112
130
|
Enabled: true
|
113
131
|
VersionAdded: '0.56'
|
@@ -178,8 +196,9 @@ Performance/StartWith:
|
|
178
196
|
SafeAutoCorrect: false
|
179
197
|
AutoCorrect: false
|
180
198
|
Enabled: true
|
199
|
+
SafeMultiline: true
|
181
200
|
VersionAdded: '0.36'
|
182
|
-
VersionChanged: '
|
201
|
+
VersionChanged: '1.6'
|
183
202
|
|
184
203
|
Performance/StringReplacement:
|
185
204
|
Description: >-
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for handling regexp metacharacters.
|
6
|
+
module RegexpMetacharacter
|
7
|
+
private
|
8
|
+
|
9
|
+
def literal_at_start?(regexp)
|
10
|
+
return true if literal_at_start_with_backslash_a?(regexp)
|
11
|
+
|
12
|
+
!safe_multiline? && literal_at_start_with_caret?(regexp)
|
13
|
+
end
|
14
|
+
|
15
|
+
def literal_at_end?(regexp)
|
16
|
+
return true if literal_at_end_with_backslash_z?(regexp)
|
17
|
+
|
18
|
+
!safe_multiline? && literal_at_end_with_dollar?(regexp)
|
19
|
+
end
|
20
|
+
|
21
|
+
def literal_at_start_with_backslash_a?(regex_str)
|
22
|
+
# is this regexp 'literal' in the sense of only matching literal
|
23
|
+
# chars, rather than using metachars like `.` and `*` and so on?
|
24
|
+
# also, is it anchored at the start of the string?
|
25
|
+
# (tricky: \s, \d, and so on are metacharacters, but other characters
|
26
|
+
# escaped with a slash are just literals. LITERAL_REGEX takes all
|
27
|
+
# that into account.)
|
28
|
+
/\A\\A(?:#{Util::LITERAL_REGEX})+\z/.match?(regex_str)
|
29
|
+
end
|
30
|
+
|
31
|
+
def literal_at_start_with_caret?(regex_str)
|
32
|
+
# is this regexp 'literal' in the sense of only matching literal
|
33
|
+
# chars, rather than using metachars like `.` and `*` and so on?
|
34
|
+
# also, is it anchored at the start of the string?
|
35
|
+
# (tricky: \s, \d, and so on are metacharacters, but other characters
|
36
|
+
# escaped with a slash are just literals. LITERAL_REGEX takes all
|
37
|
+
# that into account.)
|
38
|
+
/\A\^(?:#{Util::LITERAL_REGEX})+\z/.match?(regex_str)
|
39
|
+
end
|
40
|
+
|
41
|
+
def literal_at_end_with_backslash_z?(regex_str)
|
42
|
+
# is this regexp 'literal' in the sense of only matching literal
|
43
|
+
# chars, rather than using metachars like . and * and so on?
|
44
|
+
# also, is it anchored at the end of the string?
|
45
|
+
/\A(?:#{Util::LITERAL_REGEX})+\\z\z/.match?(regex_str)
|
46
|
+
end
|
47
|
+
|
48
|
+
def literal_at_end_with_dollar?(regex_str)
|
49
|
+
# is this regexp 'literal' in the sense of only matching literal
|
50
|
+
# chars, rather than using metachars like . and * and so on?
|
51
|
+
# also, is it anchored at the end of the string?
|
52
|
+
/\A(?:#{Util::LITERAL_REGEX})+\$\z/.match?(regex_str)
|
53
|
+
end
|
54
|
+
|
55
|
+
def drop_start_metacharacter(regexp_string)
|
56
|
+
if regexp_string.start_with?('\\A')
|
57
|
+
regexp_string[2..-1] # drop `\A` anchor
|
58
|
+
else
|
59
|
+
regexp_string[1..-1] # drop `^` anchor
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def drop_end_metacharacter(regexp_string)
|
64
|
+
if regexp_string.end_with?('\\z')
|
65
|
+
regexp_string.chomp('\z') # drop `\z` anchor
|
66
|
+
else
|
67
|
+
regexp_string.chop # drop `$` anchor
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def safe_multiline?
|
72
|
+
cop_config.fetch('SafeMultiline', true)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# In Ruby 2.7, `UnboundMethod#bind_call` has been added.
|
7
|
+
#
|
8
|
+
# This cop identifies places where `bind(obj).call(args, ...)`
|
9
|
+
# can be replaced by `bind_call(obj, args, ...)`.
|
10
|
+
#
|
11
|
+
# The `bind_call(obj, args, ...)` method is faster than
|
12
|
+
# `bind(obj).call(args, ...)`.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# # bad
|
16
|
+
# umethod.bind(obj).call(foo, bar)
|
17
|
+
# umethod.bind(obj).(foo, bar)
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# umethod.bind_call(obj, foo, bar)
|
21
|
+
#
|
22
|
+
class BindCall < Cop
|
23
|
+
include RangeHelp
|
24
|
+
extend TargetRubyVersion
|
25
|
+
|
26
|
+
minimum_target_ruby_version 2.7
|
27
|
+
|
28
|
+
MSG = 'Use `bind_call(%<bind_arg>s%<comma>s%<call_args>s)` ' \
|
29
|
+
'instead of `bind(%<bind_arg>s).call(%<call_args>s)`.'
|
30
|
+
|
31
|
+
def_node_matcher :bind_with_call_method?, <<~PATTERN
|
32
|
+
(send
|
33
|
+
$(send
|
34
|
+
(send nil? _) :bind
|
35
|
+
$(...)) :call
|
36
|
+
$...)
|
37
|
+
PATTERN
|
38
|
+
|
39
|
+
def on_send(node)
|
40
|
+
bind_with_call_method?(node) do |receiver, bind_arg, call_args_node|
|
41
|
+
range = correction_range(receiver, node)
|
42
|
+
|
43
|
+
call_args = build_call_args(call_args_node)
|
44
|
+
|
45
|
+
message = message(bind_arg.source, call_args)
|
46
|
+
|
47
|
+
add_offense(node, location: range, message: message)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def autocorrect(node)
|
52
|
+
receiver, bind_arg, call_args_node = bind_with_call_method?(node)
|
53
|
+
|
54
|
+
range = correction_range(receiver, node)
|
55
|
+
|
56
|
+
call_args = build_call_args(call_args_node)
|
57
|
+
call_args = ", #{call_args}" unless call_args.empty?
|
58
|
+
|
59
|
+
replacement_method = "bind_call(#{bind_arg.source}#{call_args})"
|
60
|
+
|
61
|
+
lambda do |corrector|
|
62
|
+
corrector.replace(range, replacement_method)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def message(bind_arg, call_args)
|
69
|
+
comma = call_args.empty? ? '' : ', '
|
70
|
+
|
71
|
+
format(MSG, bind_arg: bind_arg, comma: comma, call_args: call_args)
|
72
|
+
end
|
73
|
+
|
74
|
+
def correction_range(receiver, node)
|
75
|
+
location_of_bind = receiver.loc.selector.begin_pos
|
76
|
+
location_of_call = node.loc.end.end_pos
|
77
|
+
|
78
|
+
range_between(location_of_bind, location_of_call)
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_call_args(call_args_node)
|
82
|
+
call_args_node.map(&:source).join(', ')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -24,14 +24,14 @@ module RuboCop
|
|
24
24
|
MSG_FIRST = 'Use `%<method>s(%<n>d..%<n>d).first`' \
|
25
25
|
' instead of `%<method>s.first`.'
|
26
26
|
|
27
|
-
def_node_matcher :slow_caller?,
|
27
|
+
def_node_matcher :slow_caller?, <<~PATTERN
|
28
28
|
{
|
29
29
|
(send nil? {:caller :caller_locations})
|
30
30
|
(send nil? {:caller :caller_locations} int)
|
31
31
|
}
|
32
32
|
PATTERN
|
33
33
|
|
34
|
-
def_node_matcher :caller_with_scope_method?,
|
34
|
+
def_node_matcher :caller_with_scope_method?, <<~PATTERN
|
35
35
|
{
|
36
36
|
(send #slow_caller? :first)
|
37
37
|
(send #slow_caller? :[] int)
|
@@ -51,7 +51,7 @@ module RuboCop
|
|
51
51
|
caller_arg = node.receiver.first_argument
|
52
52
|
n = caller_arg ? int_value(caller_arg) : 1
|
53
53
|
|
54
|
-
if node.
|
54
|
+
if node.method?(:[])
|
55
55
|
m = int_value(node.first_argument)
|
56
56
|
n += m
|
57
57
|
format(MSG_BRACE, n: n, m: m, method: method_name)
|
@@ -5,6 +5,8 @@ module RuboCop
|
|
5
5
|
module Performance
|
6
6
|
# This cop identifies places where a case-insensitive string comparison
|
7
7
|
# can better be implemented using `casecmp`.
|
8
|
+
# This cop is unsafe because `String#casecmp` and `String#casecmp?` behave
|
9
|
+
# differently when using Non-ASCII characters.
|
8
10
|
#
|
9
11
|
# @example
|
10
12
|
# # bad
|
@@ -21,21 +23,21 @@ module RuboCop
|
|
21
23
|
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
22
24
|
CASE_METHODS = %i[downcase upcase].freeze
|
23
25
|
|
24
|
-
def_node_matcher :downcase_eq,
|
26
|
+
def_node_matcher :downcase_eq, <<~PATTERN
|
25
27
|
(send
|
26
28
|
$(send _ ${:downcase :upcase})
|
27
29
|
${:== :eql? :!=}
|
28
30
|
${str (send _ {:downcase :upcase} ...) (begin str)})
|
29
31
|
PATTERN
|
30
32
|
|
31
|
-
def_node_matcher :eq_downcase,
|
33
|
+
def_node_matcher :eq_downcase, <<~PATTERN
|
32
34
|
(send
|
33
35
|
{str (send _ {:downcase :upcase} ...) (begin str)}
|
34
36
|
${:== :eql? :!=}
|
35
37
|
$(send _ ${:downcase :upcase}))
|
36
38
|
PATTERN
|
37
39
|
|
38
|
-
def_node_matcher :downcase_downcase,
|
40
|
+
def_node_matcher :downcase_downcase, <<~PATTERN
|
39
41
|
(send
|
40
42
|
$(send _ ${:downcase :upcase})
|
41
43
|
${:== :eql? :!=}
|
@@ -51,7 +51,7 @@ module RuboCop
|
|
51
51
|
'(followed by `return array` if required) instead of chaining '\
|
52
52
|
'`%<method>s...%<second_method>s`.'
|
53
53
|
|
54
|
-
def_node_matcher :flat_map_candidate?,
|
54
|
+
def_node_matcher :flat_map_candidate?, <<~PATTERN
|
55
55
|
{
|
56
56
|
(send (send _ ${#{RETURN_NEW_ARRAY_WHEN_ARGS}} {int lvar ivar cvar gvar}) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
|
57
57
|
(send (block (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY} }) ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
|
@@ -30,14 +30,14 @@ module RuboCop
|
|
30
30
|
'`%<compare_method>s { |%<var_a>s, %<var_b>s| %<str_a>s ' \
|
31
31
|
'<=> %<str_b>s }`.'
|
32
32
|
|
33
|
-
def_node_matcher :compare?,
|
33
|
+
def_node_matcher :compare?, <<~PATTERN
|
34
34
|
(block
|
35
35
|
$(send _ {:sort :min :max})
|
36
36
|
(args (arg $_a) (arg $_b))
|
37
37
|
$send)
|
38
38
|
PATTERN
|
39
39
|
|
40
|
-
def_node_matcher :replaceable_body?,
|
40
|
+
def_node_matcher :replaceable_body?, <<~PATTERN
|
41
41
|
(send
|
42
42
|
(send (lvar %1) $_method $...)
|
43
43
|
:<=>
|
@@ -32,18 +32,17 @@ module RuboCop
|
|
32
32
|
# make `count` work with a block is to call `to_a.count {...}`.
|
33
33
|
#
|
34
34
|
# Example:
|
35
|
-
# Model.where(id: [1, 2, 3].select { |m| m.method == true }.size
|
35
|
+
# `Model.where(id: [1, 2, 3]).select { |m| m.method == true }.size`
|
36
36
|
#
|
37
37
|
# becomes:
|
38
38
|
#
|
39
|
-
# Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }
|
39
|
+
# `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`
|
40
40
|
class Count < Cop
|
41
|
-
include SafeMode
|
42
41
|
include RangeHelp
|
43
42
|
|
44
43
|
MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
|
45
44
|
|
46
|
-
def_node_matcher :count_candidate?,
|
45
|
+
def_node_matcher :count_candidate?, <<~PATTERN
|
47
46
|
{
|
48
47
|
(send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
|
49
48
|
(send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
|
@@ -51,8 +50,6 @@ module RuboCop
|
|
51
50
|
PATTERN
|
52
51
|
|
53
52
|
def on_send(node)
|
54
|
-
return if rails_safe_mode?
|
55
|
-
|
56
53
|
count_candidate?(node) do |selector_node, selector, counter|
|
57
54
|
return unless eligible_node?(node)
|
58
55
|
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# In Ruby 2.5, `String#delete_prefix` has been added.
|
7
|
+
#
|
8
|
+
# This cop identifies places where `gsub(/\Aprefix/, '')` and `sub(/\Aprefix/, '')`
|
9
|
+
# can be replaced by `delete_prefix('prefix')`.
|
10
|
+
#
|
11
|
+
# This cop has `SafeMultiline` configuration option that `true` by default because
|
12
|
+
# `^prefix` is unsafe as it will behave incompatible with `delete_prefix`
|
13
|
+
# for receiver is multiline string.
|
14
|
+
#
|
15
|
+
# The `delete_prefix('prefix')` method is faster than `gsub(/\Aprefix/, '')`.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# str.gsub(/\Aprefix/, '')
|
21
|
+
# str.gsub!(/\Aprefix/, '')
|
22
|
+
#
|
23
|
+
# str.sub(/\Aprefix/, '')
|
24
|
+
# str.sub!(/\Aprefix/, '')
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# str.delete_prefix('prefix')
|
28
|
+
# str.delete_prefix!('prefix')
|
29
|
+
#
|
30
|
+
# @example SafeMultiline: true (default)
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# str.gsub(/^prefix/, '')
|
34
|
+
# str.gsub!(/^prefix/, '')
|
35
|
+
# str.sub(/^prefix/, '')
|
36
|
+
# str.sub!(/^prefix/, '')
|
37
|
+
#
|
38
|
+
# @example SafeMultiline: false
|
39
|
+
#
|
40
|
+
# # bad
|
41
|
+
# str.gsub(/^prefix/, '')
|
42
|
+
# str.gsub!(/^prefix/, '')
|
43
|
+
# str.sub(/^prefix/, '')
|
44
|
+
# str.sub!(/^prefix/, '')
|
45
|
+
#
|
46
|
+
class DeletePrefix < Cop
|
47
|
+
extend TargetRubyVersion
|
48
|
+
include RegexpMetacharacter
|
49
|
+
|
50
|
+
minimum_target_ruby_version 2.5
|
51
|
+
|
52
|
+
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
53
|
+
|
54
|
+
PREFERRED_METHODS = {
|
55
|
+
gsub: :delete_prefix,
|
56
|
+
gsub!: :delete_prefix!,
|
57
|
+
sub: :delete_prefix,
|
58
|
+
sub!: :delete_prefix!
|
59
|
+
}.freeze
|
60
|
+
|
61
|
+
def_node_matcher :delete_prefix_candidate?, <<~PATTERN
|
62
|
+
(send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_start?) (regopt)) (str $_))
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
def on_send(node)
|
66
|
+
delete_prefix_candidate?(node) do |_, bad_method, _, replace_string|
|
67
|
+
return unless replace_string.blank?
|
68
|
+
|
69
|
+
good_method = PREFERRED_METHODS[bad_method]
|
70
|
+
|
71
|
+
message = format(MSG, current: bad_method, prefer: good_method)
|
72
|
+
|
73
|
+
add_offense(node, location: :selector, message: message)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def autocorrect(node)
|
78
|
+
delete_prefix_candidate?(node) do |receiver, bad_method, regexp_str, _|
|
79
|
+
lambda do |corrector|
|
80
|
+
good_method = PREFERRED_METHODS[bad_method]
|
81
|
+
regexp_str = drop_start_metacharacter(regexp_str)
|
82
|
+
regexp_str = interpret_string_escapes(regexp_str)
|
83
|
+
string_literal = to_string_literal(regexp_str)
|
84
|
+
|
85
|
+
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
|
86
|
+
|
87
|
+
# TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped.
|
88
|
+
# https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
|
89
|
+
corrector.replace(node.source_range, new_code)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|