rubocop-performance 0.0.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.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +1 -0
  4. data/lib/rubocop-performance.rb +5 -0
  5. data/lib/rubocop/cop/performance/caller.rb +69 -0
  6. data/lib/rubocop/cop/performance/case_when_splat.rb +173 -0
  7. data/lib/rubocop/cop/performance/casecmp.rb +117 -0
  8. data/lib/rubocop/cop/performance/compare_with_block.rb +119 -0
  9. data/lib/rubocop/cop/performance/count.rb +102 -0
  10. data/lib/rubocop/cop/performance/detect.rb +110 -0
  11. data/lib/rubocop/cop/performance/double_start_end_with.rb +94 -0
  12. data/lib/rubocop/cop/performance/end_with.rb +56 -0
  13. data/lib/rubocop/cop/performance/fixed_size.rb +97 -0
  14. data/lib/rubocop/cop/performance/flat_map.rb +78 -0
  15. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +99 -0
  16. data/lib/rubocop/cop/performance/lstrip_rstrip.rb +46 -0
  17. data/lib/rubocop/cop/performance/range_include.rb +47 -0
  18. data/lib/rubocop/cop/performance/redundant_block_call.rb +93 -0
  19. data/lib/rubocop/cop/performance/redundant_match.rb +56 -0
  20. data/lib/rubocop/cop/performance/redundant_merge.rb +169 -0
  21. data/lib/rubocop/cop/performance/redundant_sort_by.rb +50 -0
  22. data/lib/rubocop/cop/performance/regexp_match.rb +264 -0
  23. data/lib/rubocop/cop/performance/reverse_each.rb +42 -0
  24. data/lib/rubocop/cop/performance/sample.rb +142 -0
  25. data/lib/rubocop/cop/performance/size.rb +71 -0
  26. data/lib/rubocop/cop/performance/start_with.rb +59 -0
  27. data/lib/rubocop/cop/performance/string_replacement.rb +172 -0
  28. data/lib/rubocop/cop/performance/times_map.rb +71 -0
  29. data/lib/rubocop/cop/performance/unfreeze_string.rb +50 -0
  30. data/lib/rubocop/cop/performance/unneeded_sort.rb +165 -0
  31. data/lib/rubocop/cop/performance/uri_default_parser.rb +47 -0
  32. data/lib/rubocop/cop/performance_cops.rb +37 -0
  33. data/lib/rubocop/performance/version.rb +9 -0
  34. metadata +114 -0
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify usages of
7
+ #
8
+ # @example
9
+ # # bad
10
+ # [1, 2, 3, 4].map { |e| [e, e] }.flatten(1)
11
+ # [1, 2, 3, 4].collect { |e| [e, e] }.flatten(1)
12
+ #
13
+ # # good
14
+ # [1, 2, 3, 4].flat_map { |e| [e, e] }
15
+ # [1, 2, 3, 4].map { |e| [e, e] }.flatten
16
+ # [1, 2, 3, 4].collect { |e| [e, e] }.flatten
17
+ class FlatMap < Cop
18
+ include RangeHelp
19
+
20
+ MSG = 'Use `flat_map` instead of `%<method>s...%<flatten>s`.'.freeze
21
+ FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
22
+ 'and `flatten` can be used to flatten ' \
23
+ 'multiple levels.'.freeze
24
+
25
+ def_node_matcher :flat_map_candidate?, <<-PATTERN
26
+ (send (block $(send _ ${:collect :map}) ...) ${:flatten :flatten!} $...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ flat_map_candidate?(node) do |map_node, first_method, flatten, params|
31
+ flatten_level, = *params.first
32
+ if cop_config['EnabledForFlattenWithoutParams'] && !flatten_level
33
+ offense_for_levels(node, map_node, first_method, flatten)
34
+ elsif flatten_level == 1
35
+ offense_for_method(node, map_node, first_method, flatten)
36
+ end
37
+ end
38
+ end
39
+
40
+ def autocorrect(node)
41
+ map_node, _first_method, _flatten, params = flat_map_candidate?(node)
42
+ flatten_level, = *params.first
43
+
44
+ return unless flatten_level
45
+
46
+ range = range_between(node.loc.dot.begin_pos,
47
+ node.source_range.end_pos)
48
+
49
+ lambda do |corrector|
50
+ corrector.remove(range)
51
+ corrector.replace(map_node.loc.selector, 'flat_map')
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def offense_for_levels(node, map_node, first_method, flatten)
58
+ message = MSG + FLATTEN_MULTIPLE_LEVELS
59
+ register_offense(node, map_node, first_method, flatten, message)
60
+ end
61
+
62
+ def offense_for_method(node, map_node, first_method, flatten)
63
+ register_offense(node, map_node, first_method, flatten, MSG)
64
+ end
65
+
66
+ def register_offense(node, map_node, first_method, flatten, message)
67
+ range = range_between(map_node.loc.selector.begin_pos,
68
+ node.loc.expression.end_pos)
69
+
70
+ add_offense(node,
71
+ location: range,
72
+ message: format(message, method: first_method,
73
+ flatten: flatten))
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop checks for inefficient searching of keys and values within
7
+ # hashes.
8
+ #
9
+ # `Hash#keys.include?` is less efficient than `Hash#key?` because
10
+ # the former allocates a new array and then performs an O(n) search
11
+ # through that array, while `Hash#key?` does not allocate any array and
12
+ # performs a faster O(1) search for the key.
13
+ #
14
+ # `Hash#values.include?` is less efficient than `Hash#value?`. While they
15
+ # both perform an O(n) search through all of the values, calling `values`
16
+ # allocates a new array while using `value?` does not.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # { a: 1, b: 2 }.keys.include?(:a)
21
+ # { a: 1, b: 2 }.keys.include?(:z)
22
+ # h = { a: 1, b: 2 }; h.keys.include?(100)
23
+ #
24
+ # # good
25
+ # { a: 1, b: 2 }.key?(:a)
26
+ # { a: 1, b: 2 }.has_key?(:z)
27
+ # h = { a: 1, b: 2 }; h.key?(100)
28
+ #
29
+ # # bad
30
+ # { a: 1, b: 2 }.values.include?(2)
31
+ # { a: 1, b: 2 }.values.include?('garbage')
32
+ # h = { a: 1, b: 2 }; h.values.include?(nil)
33
+ #
34
+ # # good
35
+ # { a: 1, b: 2 }.value?(2)
36
+ # { a: 1, b: 2 }.has_value?('garbage')
37
+ # h = { a: 1, b: 2 }; h.value?(nil)
38
+ #
39
+ class InefficientHashSearch < Cop
40
+ def_node_matcher :inefficient_include?, <<-PATTERN
41
+ (send (send $_ {:keys :values}) :include? _)
42
+ PATTERN
43
+
44
+ def on_send(node)
45
+ inefficient_include?(node) do |receiver|
46
+ return if receiver.nil?
47
+
48
+ add_offense(node)
49
+ end
50
+ end
51
+
52
+ def autocorrect(node)
53
+ lambda do |corrector|
54
+ # Replace `keys.include?` or `values.include?` with the appropriate
55
+ # `key?`/`value?` method.
56
+ corrector.replace(
57
+ node.loc.expression,
58
+ "#{autocorrect_hash_expression(node)}."\
59
+ "#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
60
+ )
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def message(node)
67
+ "Use `##{autocorrect_method(node)}` instead of "\
68
+ "`##{current_method(node)}.include?`."
69
+ end
70
+
71
+ def autocorrect_method(node)
72
+ case current_method(node)
73
+ when :keys then use_long_method ? 'has_key?' : 'key?'
74
+ when :values then use_long_method ? 'has_value?' : 'value?'
75
+ end
76
+ end
77
+
78
+ def current_method(node)
79
+ node.receiver.method_name
80
+ end
81
+
82
+ def use_long_method
83
+ preferred_config = config.for_all_cops['Style/PreferredHashMethods']
84
+ preferred_config &&
85
+ preferred_config['EnforcedStyle'] == 'long' &&
86
+ preferred_config['Enabled']
87
+ end
88
+
89
+ def autocorrect_argument(node)
90
+ node.arguments.first.source
91
+ end
92
+
93
+ def autocorrect_hash_expression(node)
94
+ node.receiver.receiver.source
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `lstrip.rstrip` can be replaced by
7
+ # `strip`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # 'abc'.lstrip.rstrip
12
+ # 'abc'.rstrip.lstrip
13
+ #
14
+ # # good
15
+ # 'abc'.strip
16
+ class LstripRstrip < Cop
17
+ include RangeHelp
18
+
19
+ MSG = 'Use `strip` instead of `%<methods>s`.'.freeze
20
+
21
+ def_node_matcher :lstrip_rstrip, <<-PATTERN
22
+ {(send $(send _ $:rstrip) $:lstrip)
23
+ (send $(send _ $:lstrip) $:rstrip)}
24
+ PATTERN
25
+
26
+ def on_send(node)
27
+ lstrip_rstrip(node) do |first_send, method_one, method_two|
28
+ range = range_between(first_send.loc.selector.begin_pos,
29
+ node.source_range.end_pos)
30
+ add_offense(node,
31
+ location: range,
32
+ message: format(MSG,
33
+ methods: "#{method_one}.#{method_two}"))
34
+ end
35
+ end
36
+
37
+ def autocorrect(node)
38
+ range = range_between(node.receiver.loc.selector.begin_pos,
39
+ node.source_range.end_pos)
40
+
41
+ ->(corrector) { corrector.replace(range, 'strip') }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies uses of `Range#include?`, which iterates over each
7
+ # item in a `Range` to see if a specified item is there. In contrast,
8
+ # `Range#cover?` simply compares the target item with the beginning and
9
+ # end points of the `Range`. In a great majority of cases, this is what
10
+ # is wanted.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # ('a'..'z').include?('b') # => true
15
+ #
16
+ # # good
17
+ # ('a'..'z').cover?('b') # => true
18
+ #
19
+ # # Example of a case where `Range#cover?` may not provide
20
+ # # the desired result:
21
+ #
22
+ # ('a'..'z').cover?('yellow') # => true
23
+ class RangeInclude < Cop
24
+ MSG = 'Use `Range#cover?` instead of `Range#include?`.'.freeze
25
+
26
+ # TODO: If we traced out assignments of variables to their uses, we
27
+ # might pick up on a few more instances of this issue
28
+ # Right now, we only detect direct calls on a Range literal
29
+ # (We don't even catch it if the Range is in double parens)
30
+
31
+ def_node_matcher :range_include, <<-PATTERN
32
+ (send {irange erange (begin {irange erange})} :include? ...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return unless range_include(node)
37
+
38
+ add_offense(node, location: :selector)
39
+ end
40
+
41
+ def autocorrect(node)
42
+ ->(corrector) { corrector.replace(node.loc.selector, 'cover?') }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies the use of a `&block` parameter and `block.call`
7
+ # where `yield` would do just as well.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def method(&block)
12
+ # block.call
13
+ # end
14
+ # def another(&func)
15
+ # func.call 1, 2, 3
16
+ # end
17
+ #
18
+ # # good
19
+ # def method
20
+ # yield
21
+ # end
22
+ # def another
23
+ # yield 1, 2, 3
24
+ # end
25
+ class RedundantBlockCall < Cop
26
+ MSG = 'Use `yield` instead of `%<argname>s.call`.'.freeze
27
+ YIELD = 'yield'.freeze
28
+ OPEN_PAREN = '('.freeze
29
+ CLOSE_PAREN = ')'.freeze
30
+ SPACE = ' '.freeze
31
+
32
+ def_node_matcher :blockarg_def, <<-PATTERN
33
+ {(def _ (args ... (blockarg $_)) $_)
34
+ (defs _ _ (args ... (blockarg $_)) $_)}
35
+ PATTERN
36
+
37
+ def_node_search :blockarg_calls, <<-PATTERN
38
+ (send (lvar %1) :call ...)
39
+ PATTERN
40
+
41
+ def_node_search :blockarg_assigned?, <<-PATTERN
42
+ (lvasgn %1 ...)
43
+ PATTERN
44
+
45
+ def on_def(node)
46
+ blockarg_def(node) do |argname, body|
47
+ next unless body
48
+
49
+ calls_to_report(argname, body).each do |blockcall|
50
+ add_offense(blockcall, message: format(MSG, argname: argname))
51
+ end
52
+ end
53
+ end
54
+
55
+ # offenses are registered on the `block.call` nodes
56
+ def autocorrect(node)
57
+ _receiver, _method, *args = *node
58
+ new_source = String.new(YIELD)
59
+ unless args.empty?
60
+ new_source += if parentheses?(node)
61
+ OPEN_PAREN
62
+ else
63
+ SPACE
64
+ end
65
+
66
+ new_source << args.map(&:source).join(', ')
67
+ end
68
+
69
+ new_source << CLOSE_PAREN if parentheses?(node) && !args.empty?
70
+ ->(corrector) { corrector.replace(node.source_range, new_source) }
71
+ end
72
+
73
+ private
74
+
75
+ def calls_to_report(argname, body)
76
+ return [] if blockarg_assigned?(body, argname)
77
+
78
+ calls = to_enum(:blockarg_calls, body, argname)
79
+
80
+ return [] if calls.any? { |call| args_include_block_pass?(call) }
81
+
82
+ calls
83
+ end
84
+
85
+ def args_include_block_pass?(blockcall)
86
+ _receiver, _call, *args = *blockcall
87
+
88
+ args.any?(&:block_pass_type?)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies the use of `Regexp#match` or `String#match`, which
7
+ # returns `#<MatchData>`/`nil`. The return value of `=~` is an integral
8
+ # index/`nil` and is more performant.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # do_something if str.match(/regex/)
13
+ # while regex.match('str')
14
+ # do_something
15
+ # end
16
+ #
17
+ # # good
18
+ # method(str =~ /regex/)
19
+ # return value unless regex =~ 'str'
20
+ class RedundantMatch < Cop
21
+ MSG = 'Use `=~` in places where the `MatchData` returned by ' \
22
+ '`#match` will not be used.'.freeze
23
+
24
+ # 'match' is a fairly generic name, so we don't flag it unless we see
25
+ # a string or regexp literal on one side or the other
26
+ def_node_matcher :match_call?, <<-PATTERN
27
+ {(send {str regexp} :match _)
28
+ (send !nil? :match {str regexp})}
29
+ PATTERN
30
+
31
+ def_node_matcher :only_truthiness_matters?, <<-PATTERN
32
+ ^({if while until case while_post until_post} equal?(%0) ...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return unless match_call?(node) &&
37
+ (!node.value_used? || only_truthiness_matters?(node)) &&
38
+ !(node.parent && node.parent.block_type?)
39
+
40
+ add_offense(node)
41
+ end
42
+
43
+ def autocorrect(node)
44
+ # Regexp#match can take a second argument, but this cop doesn't
45
+ # register an offense in that case
46
+ return unless node.first_argument.regexp_type?
47
+
48
+ new_source =
49
+ node.receiver.source + ' =~ ' + node.first_argument.source
50
+
51
+ ->(corrector) { corrector.replace(node.source_range, new_source) }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `Hash#merge!` can be replaced by
7
+ # `Hash#[]=`.
8
+ #
9
+ # @example
10
+ # hash.merge!(a: 1)
11
+ # hash.merge!({'key' => 'value'})
12
+ # hash.merge!(a: 1, b: 2)
13
+ class RedundantMerge < Cop
14
+ AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'.freeze
15
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'.freeze
16
+
17
+ def_node_matcher :redundant_merge_candidate, <<-PATTERN
18
+ (send $!nil? :merge! [(hash $...) !kwsplat_type?])
19
+ PATTERN
20
+
21
+ def_node_matcher :modifier_flow_control?, <<-PATTERN
22
+ [{if while until} modifier_form?]
23
+ PATTERN
24
+
25
+ def on_send(node)
26
+ each_redundant_merge(node) do |redundant_merge_node|
27
+ add_offense(redundant_merge_node)
28
+ end
29
+ end
30
+
31
+ def autocorrect(node)
32
+ redundant_merge_candidate(node) do |receiver, pairs|
33
+ new_source = to_assignments(receiver, pairs).join("\n")
34
+
35
+ if node.parent && pairs.size > 1
36
+ correct_multiple_elements(node, node.parent, new_source)
37
+ else
38
+ correct_single_element(node, new_source)
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def message(node)
46
+ redundant_merge_candidate(node) do |receiver, pairs|
47
+ assignments = to_assignments(receiver, pairs).join('; ')
48
+
49
+ format(MSG, prefer: assignments, current: node.source)
50
+ end
51
+ end
52
+
53
+ def each_redundant_merge(node)
54
+ redundant_merge_candidate(node) do |receiver, pairs|
55
+ next if non_redundant_merge?(node, receiver, pairs)
56
+
57
+ yield node
58
+ end
59
+ end
60
+
61
+ def non_redundant_merge?(node, receiver, pairs)
62
+ non_redundant_pairs?(receiver, pairs) ||
63
+ non_redundant_value_used?(receiver, node)
64
+ end
65
+
66
+ def non_redundant_pairs?(receiver, pairs)
67
+ pairs.size > 1 && !receiver.pure? || pairs.size > max_key_value_pairs
68
+ end
69
+
70
+ def non_redundant_value_used?(receiver, node)
71
+ node.value_used? &&
72
+ !EachWithObjectInspector.new(node, receiver).value_used?
73
+ end
74
+
75
+ def correct_multiple_elements(node, parent, new_source)
76
+ if modifier_flow_control?(parent)
77
+ new_source = rewrite_with_modifier(node, parent, new_source)
78
+ node = parent
79
+ else
80
+ padding = "\n#{leading_spaces(node)}"
81
+ new_source.gsub!(/\n/, padding)
82
+ end
83
+
84
+ ->(corrector) { corrector.replace(node.source_range, new_source) }
85
+ end
86
+
87
+ def correct_single_element(node, new_source)
88
+ ->(corrector) { corrector.replace(node.source_range, new_source) }
89
+ end
90
+
91
+ def to_assignments(receiver, pairs)
92
+ pairs.map do |pair|
93
+ key, value = *pair
94
+
95
+ key = key.sym_type? && pair.colon? ? ":#{key.source}" : key.source
96
+
97
+ format(AREF_ASGN, receiver: receiver.source,
98
+ key: key,
99
+ value: value.source)
100
+ end
101
+ end
102
+
103
+ def rewrite_with_modifier(node, parent, new_source)
104
+ cond, = *parent
105
+ padding = "\n#{(' ' * indent_width) + leading_spaces(node)}"
106
+ new_source.gsub!(/\n/, padding)
107
+
108
+ parent.loc.keyword.source << ' ' << cond.source << padding <<
109
+ new_source << "\n" << leading_spaces(node) << 'end'
110
+ end
111
+
112
+ def leading_spaces(node)
113
+ node.source_range.source_line[/\A\s*/]
114
+ end
115
+
116
+ def indent_width
117
+ @config.for_cop('IndentationWidth')['Width'] || 2
118
+ end
119
+
120
+ def max_key_value_pairs
121
+ Integer(cop_config['MaxKeyValuePairs'])
122
+ end
123
+
124
+ # A utility class for checking the use of values within an
125
+ # `each_with_object` call.
126
+ class EachWithObjectInspector
127
+ extend NodePattern::Macros
128
+
129
+ def initialize(node, receiver)
130
+ @node = node
131
+ @receiver = unwind(receiver)
132
+ end
133
+
134
+ def value_used?
135
+ return false unless eligible_receiver? && second_argument
136
+
137
+ receiver.loc.name.source == second_argument.loc.name.source
138
+ end
139
+
140
+ private
141
+
142
+ attr_reader :node, :receiver
143
+
144
+ def eligible_receiver?
145
+ receiver.respond_to?(:lvar_type?) && receiver.lvar_type?
146
+ end
147
+
148
+ def second_argument
149
+ parent = node.parent
150
+ parent = parent.parent if parent.begin_type?
151
+
152
+ @second_argument ||= each_with_object_node(parent)
153
+ end
154
+
155
+ def unwind(receiver)
156
+ while receiver.respond_to?(:send_type?) && receiver.send_type?
157
+ receiver, = *receiver
158
+ end
159
+ receiver
160
+ end
161
+
162
+ def_node_matcher :each_with_object_node, <<-PATTERN
163
+ (block (send _ :each_with_object _) (args _ $_) ...)
164
+ PATTERN
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end