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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab4ceb1e31551e3774fc563d05282b5339c9cb132d0e9a9ce1ce6f63358cf468
4
+ data.tar.gz: 8d5d42ecb4e72ed9d06e30e7c609569469d4cf2fc847c8fabf602723ac8b4c21
5
+ SHA512:
6
+ metadata.gz: a42a85f9011b35eae2a99ffa9763bcfa2f409079b4c1aba3e0659adb351046cc5d8c42e5b374dda1f1d78289870339ee5fe8a34460a89d38a8fe2c24589398b5
7
+ data.tar.gz: 2acc0104f805827d517ebca4d9a29a382642af8b53fec7691b86bb53eaabe22a29b7c1df2b91c0d53e27429430fa191788477b19545dec4d17c286432bc03a03
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-18 Bozhidar Batsov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # rubocop-performance
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+ require_relative 'rubocop/performance/version'
5
+ require_relative 'rubocop/cop/performance_cops'
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `caller[n]`
7
+ # can be replaced by `caller(n..n).first`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # caller[1]
12
+ # caller.first
13
+ # caller_locations[1]
14
+ # caller_locations.first
15
+ #
16
+ # # good
17
+ # caller(2..2).first
18
+ # caller(1..1).first
19
+ # caller_locations(2..2).first
20
+ # caller_locations(1..1).first
21
+ class Caller < Cop
22
+ MSG_BRACE = 'Use `%<method>s(%<n>d..%<n>d).first`' \
23
+ ' instead of `%<method>s[%<m>d]`.'.freeze
24
+ MSG_FIRST = 'Use `%<method>s(%<n>d..%<n>d).first`' \
25
+ ' instead of `%<method>s.first`.'.freeze
26
+
27
+ def_node_matcher :slow_caller?, <<-PATTERN
28
+ {
29
+ (send nil? {:caller :caller_locations})
30
+ (send nil? {:caller :caller_locations} int)
31
+ }
32
+ PATTERN
33
+
34
+ def_node_matcher :caller_with_scope_method?, <<-PATTERN
35
+ {
36
+ (send #slow_caller? :first)
37
+ (send #slow_caller? :[] int)
38
+ }
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ return unless caller_with_scope_method?(node)
43
+
44
+ add_offense(node)
45
+ end
46
+
47
+ private
48
+
49
+ def message(node)
50
+ method_name = node.receiver.method_name
51
+ caller_arg = node.receiver.first_argument
52
+ n = caller_arg ? int_value(caller_arg) : 1
53
+
54
+ if node.method_name == :[]
55
+ m = int_value(node.first_argument)
56
+ n += m
57
+ format(MSG_BRACE, n: n, m: m, method: method_name)
58
+ else
59
+ format(MSG_FIRST, n: n, method: method_name)
60
+ end
61
+ end
62
+
63
+ def int_value(node)
64
+ node.children[0]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # Place `when` conditions that use splat at the end
7
+ # of the list of `when` branches.
8
+ #
9
+ # Ruby has to allocate memory for the splat expansion every time
10
+ # that the `case` `when` statement is run. Since Ruby does not support
11
+ # fall through inside of `case` `when`, like some other languages do,
12
+ # the order of the `when` branches does not matter. By placing any
13
+ # splat expansions at the end of the list of `when` branches we will
14
+ # reduce the number of times that memory has to be allocated for
15
+ # the expansion.
16
+ #
17
+ # This is not a guaranteed performance improvement. If the data being
18
+ # processed by the `case` condition is normalized in a manner that favors
19
+ # hitting a condition in the splat expansion, it is possible that
20
+ # moving the splat condition to the end will use more memory,
21
+ # and run slightly slower.
22
+ #
23
+ # @example
24
+ # # bad
25
+ # case foo
26
+ # when *condition
27
+ # bar
28
+ # when baz
29
+ # foobar
30
+ # end
31
+ #
32
+ # case foo
33
+ # when *[1, 2, 3, 4]
34
+ # bar
35
+ # when 5
36
+ # baz
37
+ # end
38
+ #
39
+ # # good
40
+ # case foo
41
+ # when baz
42
+ # foobar
43
+ # when *condition
44
+ # bar
45
+ # end
46
+ #
47
+ # case foo
48
+ # when 1, 2, 3, 4
49
+ # bar
50
+ # when 5
51
+ # baz
52
+ # end
53
+ class CaseWhenSplat < Cop
54
+ include Alignment
55
+ include RangeHelp
56
+
57
+ MSG = 'Place `when` conditions with a splat ' \
58
+ 'at the end of the `when` branches.'.freeze
59
+ ARRAY_MSG = 'Do not expand array literals in `when` conditions.'.freeze
60
+
61
+ def on_case(case_node)
62
+ when_conditions = case_node.when_branches.flat_map(&:conditions)
63
+
64
+ splat_offenses(when_conditions).reverse_each do |condition|
65
+ range = condition.parent.loc.keyword.join(condition.source_range)
66
+ variable, = *condition
67
+ message = variable.array_type? ? ARRAY_MSG : MSG
68
+ add_offense(condition.parent, location: range, message: message)
69
+ end
70
+ end
71
+
72
+ def autocorrect(when_node)
73
+ lambda do |corrector|
74
+ if needs_reorder?(when_node)
75
+ reorder_condition(corrector, when_node)
76
+ else
77
+ inline_fix_branch(corrector, when_node)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def replacement(conditions)
85
+ reordered = conditions.partition(&:splat_type?).reverse
86
+ reordered.flatten.map(&:source).join(', ')
87
+ end
88
+
89
+ def inline_fix_branch(corrector, when_node)
90
+ conditions = when_node.conditions
91
+ range = range_between(conditions[0].loc.expression.begin_pos,
92
+ conditions[-1].loc.expression.end_pos)
93
+
94
+ corrector.replace(range, replacement(conditions))
95
+ end
96
+
97
+ def reorder_condition(corrector, when_node)
98
+ when_branches = when_node.parent.when_branches
99
+
100
+ return if when_branches.one?
101
+
102
+ corrector.remove(when_branch_range(when_node))
103
+ corrector.insert_after(when_branches.last.source_range,
104
+ reordering_correction(when_node))
105
+ end
106
+
107
+ def reordering_correction(when_node)
108
+ new_condition = replacement(when_node.conditions)
109
+
110
+ if same_line?(when_node, when_node.body)
111
+ new_condition_with_then(when_node, new_condition)
112
+ else
113
+ new_branch_without_then(when_node, new_condition)
114
+ end
115
+ end
116
+
117
+ def when_branch_range(when_node)
118
+ next_branch =
119
+ when_node.parent.when_branches[when_node.branch_index + 1]
120
+
121
+ range_between(when_node.source_range.begin_pos,
122
+ next_branch.source_range.begin_pos)
123
+ end
124
+
125
+ def new_condition_with_then(node, new_condition)
126
+ "\n#{indent_for(node)}when " \
127
+ "#{new_condition} then #{node.body.source}"
128
+ end
129
+
130
+ def new_branch_without_then(node, new_condition)
131
+ "\n#{indent_for(node)}when #{new_condition}" \
132
+ "\n#{indent_for(node.body)}#{node.body.source}"
133
+ end
134
+
135
+ def indent_for(node)
136
+ ' ' * node.loc.column
137
+ end
138
+
139
+ def splat_offenses(when_conditions)
140
+ found_non_splat = false
141
+
142
+ offenses = when_conditions.reverse.map do |condition|
143
+ found_non_splat ||= non_splat?(condition)
144
+
145
+ next if non_splat?(condition)
146
+
147
+ condition if found_non_splat
148
+ end
149
+
150
+ offenses.compact
151
+ end
152
+
153
+ def non_splat?(condition)
154
+ variable, = *condition
155
+
156
+ (condition.splat_type? && variable.array_type?) ||
157
+ !condition.splat_type?
158
+ end
159
+
160
+ def needs_reorder?(when_node)
161
+ following_branches =
162
+ when_node.parent.when_branches[(when_node.branch_index + 1)..-1]
163
+
164
+ following_branches.any? do |when_branch|
165
+ when_branch.conditions.any? do |condition|
166
+ non_splat?(condition)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where a case-insensitive string comparison
7
+ # can better be implemented using `casecmp`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # str.downcase == 'abc'
12
+ # str.upcase.eql? 'ABC'
13
+ # 'abc' == str.downcase
14
+ # 'ABC'.eql? str.upcase
15
+ # str.downcase == str.downcase
16
+ #
17
+ # # good
18
+ # str.casecmp('ABC').zero?
19
+ # 'abc'.casecmp(str).zero?
20
+ class Casecmp < Cop
21
+ MSG = 'Use `casecmp` instead of `%<methods>s`.'.freeze
22
+ CASE_METHODS = %i[downcase upcase].freeze
23
+
24
+ def_node_matcher :downcase_eq, <<-PATTERN
25
+ (send
26
+ $(send _ ${:downcase :upcase})
27
+ ${:== :eql? :!=}
28
+ ${str (send _ {:downcase :upcase} ...) (begin str)})
29
+ PATTERN
30
+
31
+ def_node_matcher :eq_downcase, <<-PATTERN
32
+ (send
33
+ {str (send _ {:downcase :upcase} ...) (begin str)}
34
+ ${:== :eql? :!=}
35
+ $(send _ ${:downcase :upcase}))
36
+ PATTERN
37
+
38
+ def_node_matcher :downcase_downcase, <<-PATTERN
39
+ (send
40
+ $(send _ ${:downcase :upcase})
41
+ ${:== :eql? :!=}
42
+ $(send _ ${:downcase :upcase}))
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ return if part_of_ignored_node?(node)
47
+
48
+ inefficient_comparison(node) do |range, is_other_part, *methods|
49
+ ignore_node(node) if is_other_part
50
+ add_offense(node, location: range,
51
+ message: format(MSG, methods: methods.join(' ')))
52
+ end
53
+ end
54
+
55
+ def autocorrect(node)
56
+ if downcase_downcase(node)
57
+ receiver, method, rhs = *node
58
+ arg, = *rhs
59
+ elsif downcase_eq(node)
60
+ receiver, method, arg = *node
61
+ elsif eq_downcase(node)
62
+ arg, method, receiver = *node
63
+ else
64
+ return
65
+ end
66
+
67
+ variable, = *receiver
68
+ correction(node, receiver, method, arg, variable)
69
+ end
70
+
71
+ private
72
+
73
+ def inefficient_comparison(node)
74
+ loc = node.loc
75
+
76
+ downcase_eq(node) do |send_downcase, case_method, eq_method, other|
77
+ *_, method = *other
78
+ range, is_other_part = downcase_eq_range(method, loc, send_downcase)
79
+
80
+ yield range, is_other_part, case_method, eq_method
81
+ return
82
+ end
83
+
84
+ eq_downcase(node) do |eq_method, send_downcase, case_method|
85
+ range = loc.selector.join(send_downcase.loc.selector)
86
+ yield range, false, eq_method, case_method
87
+ end
88
+ end
89
+
90
+ def downcase_eq_range(method, loc, send_downcase)
91
+ if CASE_METHODS.include?(method)
92
+ [loc.expression, true]
93
+ else
94
+ [loc.selector.join(send_downcase.loc.selector), false]
95
+ end
96
+ end
97
+
98
+ def correction(node, _receiver, method, arg, variable)
99
+ lambda do |corrector|
100
+ corrector.insert_before(node.loc.expression, '!') if method == :!=
101
+
102
+ # we want resulting call to be parenthesized
103
+ # if arg already includes one or more sets of parens, don't add more
104
+ # or if method call already used parens, again, don't add more
105
+ replacement = if arg.send_type? || !parentheses?(arg)
106
+ "#{variable.source}.casecmp(#{arg.source}).zero?"
107
+ else
108
+ "#{variable.source}.casecmp#{arg.source}.zero?"
109
+ end
110
+
111
+ corrector.replace(node.loc.expression, replacement)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort { |a, b| a.foo <=> b.foo }`
7
+ # can be replaced by `sort_by(&:foo)`.
8
+ # This cop also checks `max` and `min` methods.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # array.sort { |a, b| a.foo <=> b.foo }
13
+ # array.max { |a, b| a.foo <=> b.foo }
14
+ # array.min { |a, b| a.foo <=> b.foo }
15
+ # array.sort { |a, b| a[:foo] <=> b[:foo] }
16
+ #
17
+ # # good
18
+ # array.sort_by(&:foo)
19
+ # array.sort_by { |v| v.foo }
20
+ # array.sort_by do |var|
21
+ # var.foo
22
+ # end
23
+ # array.max_by(&:foo)
24
+ # array.min_by(&:foo)
25
+ # array.sort_by { |a| a[:foo] }
26
+ class CompareWithBlock < Cop
27
+ include RangeHelp
28
+
29
+ MSG = 'Use `%<compare_method>s_by%<instead>s` instead of ' \
30
+ '`%<compare_method>s { |%<var_a>s, %<var_b>s| %<str_a>s ' \
31
+ '<=> %<str_b>s }`.'.freeze
32
+
33
+ def_node_matcher :compare?, <<-PATTERN
34
+ (block
35
+ $(send _ {:sort :min :max})
36
+ (args (arg $_a) (arg $_b))
37
+ $send)
38
+ PATTERN
39
+
40
+ def_node_matcher :replaceable_body?, <<-PATTERN
41
+ (send
42
+ (send (lvar %1) $_method $...)
43
+ :<=>
44
+ (send (lvar %2) _method $...))
45
+ PATTERN
46
+
47
+ def on_block(node)
48
+ compare?(node) do |send, var_a, var_b, body|
49
+ replaceable_body?(body, var_a, var_b) do |method, args_a, args_b|
50
+ return unless slow_compare?(method, args_a, args_b)
51
+ range = compare_range(send, node)
52
+
53
+ add_offense(
54
+ node,
55
+ location: range,
56
+ message: message(send, method, var_a, var_b, args_a)
57
+ )
58
+ end
59
+ end
60
+ end
61
+
62
+ def autocorrect(node)
63
+ lambda do |corrector|
64
+ send, var_a, var_b, body = compare?(node)
65
+ method, arg, = replaceable_body?(body, var_a, var_b)
66
+ replacement =
67
+ if method == :[]
68
+ "#{send.method_name}_by { |a| a[#{arg.first.source}] }"
69
+ else
70
+ "#{send.method_name}_by(&:#{method})"
71
+ end
72
+ corrector.replace(compare_range(send, node),
73
+ replacement)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def slow_compare?(method, args_a, args_b)
80
+ return false unless args_a == args_b
81
+ if method == :[]
82
+ return false unless args_a.size == 1
83
+ key = args_a.first
84
+ return false unless %i[sym str int].include?(key.type)
85
+ else
86
+ return false unless args_a.empty?
87
+ end
88
+ true
89
+ end
90
+
91
+ # rubocop:disable Metrics/MethodLength
92
+ def message(send, method, var_a, var_b, args)
93
+ compare_method = send.method_name
94
+ if method == :[]
95
+ key = args.first
96
+ instead = " { |a| a[#{key.source}] }"
97
+ str_a = "#{var_a}[#{key.source}]"
98
+ str_b = "#{var_b}[#{key.source}]"
99
+ else
100
+ instead = "(&:#{method})"
101
+ str_a = "#{var_a}.#{method}"
102
+ str_b = "#{var_b}.#{method}"
103
+ end
104
+ format(MSG, compare_method: compare_method,
105
+ instead: instead,
106
+ var_a: var_a,
107
+ var_b: var_b,
108
+ str_a: str_a,
109
+ str_b: str_b)
110
+ end
111
+ # rubocop:enable Metrics/MethodLength
112
+
113
+ def compare_range(send, node)
114
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end