rubocop-performance 1.5.0 → 1.7.0
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 +75 -6
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
- data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
- data/lib/rubocop/cop/performance/ancestors_include.rb +45 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +43 -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 -3
- 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 -1
- 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 +1 -1
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
- data/lib/rubocop/cop/performance/io_readlines.rb +127 -0
- data/lib/rubocop/cop/performance/open_struct.rb +1 -1
- data/lib/rubocop/cop/performance/range_include.rb +10 -8
- 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 +21 -8
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +53 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +137 -0
- 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/reverse_first.rb +78 -0
- data/lib/rubocop/cop/performance/size.rb +35 -37
- data/lib/rubocop/cop/performance/sort_reverse.rb +54 -0
- data/lib/rubocop/cop/performance/squeeze.rb +70 -0
- data/lib/rubocop/cop/performance/start_with.rb +36 -16
- data/lib/rubocop/cop/performance/string_include.rb +57 -0
- 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 +15 -0
- data/lib/rubocop/performance/inject.rb +1 -1
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +25 -11
@@ -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,17 +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
41
|
include RangeHelp
|
42
42
|
|
43
43
|
MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
|
44
44
|
|
45
|
-
def_node_matcher :count_candidate?,
|
45
|
+
def_node_matcher :count_candidate?, <<~PATTERN
|
46
46
|
{
|
47
47
|
(send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
|
48
48
|
(send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
|
@@ -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
|
@@ -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_suffix` has been added.
|
7
|
+
#
|
8
|
+
# This cop identifies places where `gsub(/suffix\z/, '')` and `sub(/suffix\z/, '')`
|
9
|
+
# can be replaced by `delete_suffix('suffix')`.
|
10
|
+
#
|
11
|
+
# This cop has `SafeMultiline` configuration option that `true` by default because
|
12
|
+
# `suffix$` is unsafe as it will behave incompatible with `delete_suffix?`
|
13
|
+
# for receiver is multiline string.
|
14
|
+
#
|
15
|
+
# The `delete_suffix('suffix')` method is faster than `gsub(/suffix\z/, '')`.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# str.gsub(/suffix\z/, '')
|
21
|
+
# str.gsub!(/suffix\z/, '')
|
22
|
+
#
|
23
|
+
# str.sub(/suffix\z/, '')
|
24
|
+
# str.sub!(/suffix\z/, '')
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# str.delete_suffix('suffix')
|
28
|
+
# str.delete_suffix!('suffix')
|
29
|
+
#
|
30
|
+
# @example SafeMultiline: true (default)
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# str.gsub(/suffix$/, '')
|
34
|
+
# str.gsub!(/suffix$/, '')
|
35
|
+
# str.sub(/suffix$/, '')
|
36
|
+
# str.sub!(/suffix$/, '')
|
37
|
+
#
|
38
|
+
# @example SafeMultiline: false
|
39
|
+
#
|
40
|
+
# # bad
|
41
|
+
# str.gsub(/suffix$/, '')
|
42
|
+
# str.gsub!(/suffix$/, '')
|
43
|
+
# str.sub(/suffix$/, '')
|
44
|
+
# str.sub!(/suffix$/, '')
|
45
|
+
#
|
46
|
+
class DeleteSuffix < 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_suffix,
|
56
|
+
gsub!: :delete_suffix!,
|
57
|
+
sub: :delete_suffix,
|
58
|
+
sub!: :delete_suffix!
|
59
|
+
}.freeze
|
60
|
+
|
61
|
+
def_node_matcher :delete_suffix_candidate?, <<~PATTERN
|
62
|
+
(send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
def on_send(node)
|
66
|
+
delete_suffix_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_suffix_candidate?(node) do |receiver, bad_method, regexp_str, _|
|
79
|
+
lambda do |corrector|
|
80
|
+
good_method = PREFERRED_METHODS[bad_method]
|
81
|
+
regexp_str = drop_end_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
|
@@ -28,7 +28,7 @@ module RuboCop
|
|
28
28
|
REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
|
29
29
|
'`%<first_method>s.%<second_method>s`.'
|
30
30
|
|
31
|
-
def_node_matcher :detect_candidate?,
|
31
|
+
def_node_matcher :detect_candidate?, <<~PATTERN
|
32
32
|
{
|
33
33
|
(send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
|
34
34
|
(send $(send _ {:select :find_all} ...) ${:first :last} $...)
|
@@ -75,13 +75,13 @@ module RuboCop
|
|
75
75
|
cop_config['IncludeActiveSupportAliases']
|
76
76
|
end
|
77
77
|
|
78
|
-
def_node_matcher :two_start_end_with_calls,
|
78
|
+
def_node_matcher :two_start_end_with_calls, <<~PATTERN
|
79
79
|
(or
|
80
80
|
(send $_recv [{:start_with? :end_with?} $_method] $...)
|
81
81
|
(send _recv _method $...))
|
82
82
|
PATTERN
|
83
83
|
|
84
|
-
def_node_matcher :check_with_active_support_aliases,
|
84
|
+
def_node_matcher :check_with_active_support_aliases, <<~PATTERN
|
85
85
|
(or
|
86
86
|
(send $_recv
|
87
87
|
[{:start_with? :starts_with? :end_with? :ends_with?} $_method]
|
@@ -3,44 +3,67 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
|
-
# This cop identifies unnecessary use of a regex where `String#end_with?`
|
7
|
-
#
|
6
|
+
# This cop identifies unnecessary use of a regex where `String#end_with?` would suffice.
|
7
|
+
#
|
8
|
+
# This cop has `SafeMultiline` configuration option that `true` by default because
|
9
|
+
# `end$` is unsafe as it will behave incompatible with `end_with?`
|
10
|
+
# for receiver is multiline string.
|
8
11
|
#
|
9
12
|
# @example
|
10
13
|
# # bad
|
11
14
|
# 'abc'.match?(/bc\Z/)
|
15
|
+
# /bc\Z/.match?('abc')
|
12
16
|
# 'abc' =~ /bc\Z/
|
17
|
+
# /bc\Z/ =~ 'abc'
|
13
18
|
# 'abc'.match(/bc\Z/)
|
19
|
+
# /bc\Z/.match('abc')
|
14
20
|
#
|
15
21
|
# # good
|
16
22
|
# 'abc'.end_with?('bc')
|
23
|
+
#
|
24
|
+
# @example SafeMultiline: true (default)
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# 'abc'.match?(/bc$/)
|
28
|
+
# /bc$/.match?('abc')
|
29
|
+
# 'abc' =~ /bc$/
|
30
|
+
# /bc$/ =~ 'abc'
|
31
|
+
# 'abc'.match(/bc$/)
|
32
|
+
# /bc$/.match('abc')
|
33
|
+
#
|
34
|
+
# @example SafeMultiline: false
|
35
|
+
#
|
36
|
+
# # bad
|
37
|
+
# 'abc'.match?(/bc$/)
|
38
|
+
# /bc$/.match?('abc')
|
39
|
+
# 'abc' =~ /bc$/
|
40
|
+
# /bc$/ =~ 'abc'
|
41
|
+
# 'abc'.match(/bc$/)
|
42
|
+
# /bc$/.match('abc')
|
43
|
+
#
|
17
44
|
class EndWith < Cop
|
45
|
+
include RegexpMetacharacter
|
46
|
+
|
18
47
|
MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
|
19
48
|
'the end of the string.'
|
20
|
-
SINGLE_QUOTE = "'"
|
21
49
|
|
22
|
-
def_node_matcher :redundant_regex?,
|
50
|
+
def_node_matcher :redundant_regex?, <<~PATTERN
|
23
51
|
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
|
24
|
-
(send (regexp (str $#literal_at_end?) (regopt)) {:match
|
52
|
+
(send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
|
53
|
+
(match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
|
25
54
|
PATTERN
|
26
55
|
|
27
|
-
def literal_at_end?(regex_str)
|
28
|
-
# is this regexp 'literal' in the sense of only matching literal
|
29
|
-
# chars, rather than using metachars like . and * and so on?
|
30
|
-
# also, is it anchored at the end of the string?
|
31
|
-
regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
|
32
|
-
end
|
33
|
-
|
34
56
|
def on_send(node)
|
35
57
|
return unless redundant_regex?(node)
|
36
58
|
|
37
59
|
add_offense(node)
|
38
60
|
end
|
61
|
+
alias on_match_with_lvasgn on_send
|
39
62
|
|
40
63
|
def autocorrect(node)
|
41
64
|
redundant_regex?(node) do |receiver, regex_str|
|
42
65
|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
43
|
-
regex_str = regex_str
|
66
|
+
regex_str = drop_end_metacharacter(regex_str)
|
44
67
|
regex_str = interpret_string_escapes(regex_str)
|
45
68
|
|
46
69
|
lambda do |corrector|
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
class FixedSize < Cop
|
49
49
|
MSG = 'Do not compute the size of statically sized objects.'
|
50
50
|
|
51
|
-
def_node_matcher :counter,
|
51
|
+
def_node_matcher :counter, <<~MATCHER
|
52
52
|
(send ${array hash str sym} {:count :length :size} $...)
|
53
53
|
MATCHER
|
54
54
|
|
@@ -37,7 +37,7 @@ module RuboCop
|
|
37
37
|
# h = { a: 1, b: 2 }; h.value?(nil)
|
38
38
|
#
|
39
39
|
class InefficientHashSearch < Cop
|
40
|
-
def_node_matcher :inefficient_include?,
|
40
|
+
def_node_matcher :inefficient_include?, <<~PATTERN
|
41
41
|
(send (send $_ {:keys :values}) :include? _)
|
42
42
|
PATTERN
|
43
43
|
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where inefficient `readlines` method
|
7
|
+
# can be replaced by `each_line` to avoid fully loading file content into memory.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# File.readlines('testfile').each { |l| puts l }
|
13
|
+
# IO.readlines('testfile', chomp: true).each { |l| puts l }
|
14
|
+
#
|
15
|
+
# conn.readlines(10).map { |l| l.size }
|
16
|
+
# file.readlines.find { |l| l.start_with?('#') }
|
17
|
+
# file.readlines.each { |l| puts l }
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# File.open('testfile', 'r').each_line { |l| puts l }
|
21
|
+
# IO.open('testfile').each_line(chomp: true) { |l| puts l }
|
22
|
+
#
|
23
|
+
# conn.each_line(10).map { |l| l.size }
|
24
|
+
# file.each_line.find { |l| l.start_with?('#') }
|
25
|
+
# file.each_line { |l| puts l }
|
26
|
+
#
|
27
|
+
class IoReadlines < Cop
|
28
|
+
include RangeHelp
|
29
|
+
|
30
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
31
|
+
ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
|
32
|
+
|
33
|
+
def_node_matcher :readlines_on_class?, <<~PATTERN
|
34
|
+
$(send $(send (const nil? {:IO :File}) :readlines ...) #enumerable_method?)
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_matcher :readlines_on_instance?, <<~PATTERN
|
38
|
+
$(send $(send ${nil? !const_type?} :readlines ...) #enumerable_method? ...)
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def on_send(node)
|
42
|
+
readlines_on_class?(node) do |enumerable_call, readlines_call|
|
43
|
+
offense(node, enumerable_call, readlines_call)
|
44
|
+
end
|
45
|
+
|
46
|
+
readlines_on_instance?(node) do |enumerable_call, readlines_call, _|
|
47
|
+
offense(node, enumerable_call, readlines_call)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def autocorrect(node)
|
52
|
+
readlines_on_instance?(node) do |enumerable_call, readlines_call, receiver|
|
53
|
+
# We cannot safely correct `.readlines` method called on IO/File classes
|
54
|
+
# due to its signature and we are not sure with implicit receiver
|
55
|
+
# if it is called in the context of some instance or mentioned class.
|
56
|
+
return if receiver.nil?
|
57
|
+
|
58
|
+
lambda do |corrector|
|
59
|
+
range = correction_range(enumerable_call, readlines_call)
|
60
|
+
|
61
|
+
if readlines_call.arguments?
|
62
|
+
call_args = build_call_args(readlines_call.arguments)
|
63
|
+
replacement = "each_line(#{call_args})"
|
64
|
+
else
|
65
|
+
replacement = 'each_line'
|
66
|
+
end
|
67
|
+
|
68
|
+
corrector.replace(range, replacement)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def enumerable_method?(node)
|
76
|
+
ENUMERABLE_METHODS.include?(node.to_sym)
|
77
|
+
end
|
78
|
+
|
79
|
+
def offense(node, enumerable_call, readlines_call)
|
80
|
+
range = offense_range(enumerable_call, readlines_call)
|
81
|
+
good_method = build_good_method(enumerable_call)
|
82
|
+
bad_method = build_bad_method(enumerable_call)
|
83
|
+
|
84
|
+
add_offense(
|
85
|
+
node,
|
86
|
+
location: range,
|
87
|
+
message: format(MSG, good: good_method, bad: bad_method)
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def offense_range(enumerable_call, readlines_call)
|
92
|
+
readlines_pos = readlines_call.loc.selector.begin_pos
|
93
|
+
enumerable_pos = enumerable_call.loc.selector.end_pos
|
94
|
+
range_between(readlines_pos, enumerable_pos)
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_good_method(enumerable_call)
|
98
|
+
if enumerable_call.method?(:each)
|
99
|
+
'each_line'
|
100
|
+
else
|
101
|
+
"each_line.#{enumerable_call.method_name}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_bad_method(enumerable_call)
|
106
|
+
"readlines.#{enumerable_call.method_name}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def correction_range(enumerable_call, readlines_call)
|
110
|
+
begin_pos = readlines_call.loc.selector.begin_pos
|
111
|
+
|
112
|
+
end_pos = if enumerable_call.method?(:each)
|
113
|
+
enumerable_call.loc.expression.end_pos
|
114
|
+
else
|
115
|
+
enumerable_call.loc.dot.begin_pos
|
116
|
+
end
|
117
|
+
|
118
|
+
range_between(begin_pos, end_pos)
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_call_args(call_args_node)
|
122
|
+
call_args_node.map(&:source).join(', ')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|