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
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