rubocop-performance 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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