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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort_by { ... }` can be replaced by
7
+ # `sort`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.sort_by { |x| x }
12
+ # array.sort_by do |var|
13
+ # var
14
+ # end
15
+ #
16
+ # # good
17
+ # array.sort
18
+ class RedundantSortBy < Cop
19
+ include RangeHelp
20
+
21
+ MSG = 'Use `sort` instead of `sort_by { |%<var>s| %<var>s }`.'.freeze
22
+
23
+ def_node_matcher :redundant_sort_by, <<-PATTERN
24
+ (block $(send _ :sort_by) (args (arg $_x)) (lvar _x))
25
+ PATTERN
26
+
27
+ def on_block(node)
28
+ redundant_sort_by(node) do |send, var_name|
29
+ range = sort_by_range(send, node)
30
+
31
+ add_offense(node,
32
+ location: range,
33
+ message: format(MSG, var: var_name))
34
+ end
35
+ end
36
+
37
+ def autocorrect(node)
38
+ send, = *node
39
+ ->(corrector) { corrector.replace(sort_by_range(send, node), 'sort') }
40
+ end
41
+
42
+ private
43
+
44
+ def sort_by_range(send, node)
45
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.4, `String#match?`, `Regexp#match?` and `Symbol#match?`
7
+ # have been added. The methods are faster than `match`.
8
+ # Because the methods avoid creating a `MatchData` object or saving
9
+ # backref.
10
+ # So, when `MatchData` is not used, use `match?` instead of `match`.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # def foo
15
+ # if x =~ /re/
16
+ # do_something
17
+ # end
18
+ # end
19
+ #
20
+ # # bad
21
+ # def foo
22
+ # if x !~ /re/
23
+ # do_something
24
+ # end
25
+ # end
26
+ #
27
+ # # bad
28
+ # def foo
29
+ # if x.match(/re/)
30
+ # do_something
31
+ # end
32
+ # end
33
+ #
34
+ # # bad
35
+ # def foo
36
+ # if /re/ === x
37
+ # do_something
38
+ # end
39
+ # end
40
+ #
41
+ # # good
42
+ # def foo
43
+ # if x.match?(/re/)
44
+ # do_something
45
+ # end
46
+ # end
47
+ #
48
+ # # good
49
+ # def foo
50
+ # if !x.match?(/re/)
51
+ # do_something
52
+ # end
53
+ # end
54
+ #
55
+ # # good
56
+ # def foo
57
+ # if x =~ /re/
58
+ # do_something(Regexp.last_match)
59
+ # end
60
+ # end
61
+ #
62
+ # # good
63
+ # def foo
64
+ # if x.match(/re/)
65
+ # do_something($~)
66
+ # end
67
+ # end
68
+ #
69
+ # # good
70
+ # def foo
71
+ # if /re/ === x
72
+ # do_something($~)
73
+ # end
74
+ # end
75
+ class RegexpMatch < Cop
76
+ extend TargetRubyVersion
77
+
78
+ minimum_target_ruby_version 2.4
79
+
80
+ # Constants are included in this list because it is unlikely that
81
+ # someone will store `nil` as a constant and then use it for comparison
82
+ TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
83
+ MSG =
84
+ 'Use `match?` instead of `%<current>s` when `MatchData` ' \
85
+ 'is not used.'.freeze
86
+
87
+ def_node_matcher :match_method?, <<-PATTERN
88
+ {
89
+ (send _recv :match _)
90
+ (send _recv :match _ (int ...))
91
+ }
92
+ PATTERN
93
+
94
+ def_node_matcher :match_operator?, <<-PATTERN
95
+ (send !nil? {:=~ :!~} !nil?)
96
+ PATTERN
97
+
98
+ def_node_matcher :match_threequals?, <<-PATTERN
99
+ (send (regexp (str _) {(regopt) (regopt _)}) :=== !nil?)
100
+ PATTERN
101
+
102
+ def match_with_lvasgn?(node)
103
+ return false unless node.match_with_lvasgn_type?
104
+ regexp, _rhs = *node
105
+ regexp.to_regexp.named_captures.empty?
106
+ end
107
+
108
+ MATCH_NODE_PATTERN = <<-PATTERN.freeze
109
+ {
110
+ #match_method?
111
+ #match_operator?
112
+ #match_threequals?
113
+ #match_with_lvasgn?
114
+ }
115
+ PATTERN
116
+
117
+ def_node_matcher :match_node?, MATCH_NODE_PATTERN
118
+ def_node_search :search_match_nodes, MATCH_NODE_PATTERN
119
+
120
+ def_node_search :last_matches, <<-PATTERN
121
+ {
122
+ (send (const nil? :Regexp) :last_match)
123
+ (send (const nil? :Regexp) :last_match _)
124
+ ({back_ref nth_ref} _)
125
+ (gvar #match_gvar?)
126
+ }
127
+ PATTERN
128
+
129
+ def on_if(node)
130
+ check_condition(node.condition)
131
+ end
132
+
133
+ def on_case(node)
134
+ return if node.condition
135
+
136
+ node.each_when do |when_node|
137
+ when_node.each_condition do |condition|
138
+ check_condition(condition)
139
+ end
140
+ end
141
+ end
142
+
143
+ def autocorrect(node)
144
+ lambda do |corrector|
145
+ if match_method?(node)
146
+ corrector.replace(node.loc.selector, 'match?')
147
+ elsif match_operator?(node) || match_threequals?(node)
148
+ recv, oper, arg = *node
149
+ correct_operator(corrector, recv, arg, oper)
150
+ elsif match_with_lvasgn?(node)
151
+ recv, arg = *node
152
+ correct_operator(corrector, recv, arg)
153
+ end
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def check_condition(cond)
160
+ match_node?(cond) do
161
+ return if last_match_used?(cond)
162
+
163
+ add_offense(cond)
164
+ end
165
+ end
166
+
167
+ def message(node)
168
+ format(MSG, current: node.loc.selector.source)
169
+ end
170
+
171
+ def last_match_used?(match_node)
172
+ scope_root = scope_root(match_node)
173
+ body = scope_root ? scope_body(scope_root) : match_node.ancestors.last
174
+
175
+ return true if match_node.parent.if_type? &&
176
+ match_node.parent.modifier_form?
177
+
178
+ match_node_pos = match_node.loc.expression.begin_pos
179
+
180
+ next_match_pos = next_match_pos(body, match_node_pos, scope_root)
181
+ range = match_node_pos..next_match_pos
182
+
183
+ find_last_match(body, range, scope_root)
184
+ end
185
+
186
+ def next_match_pos(body, match_node_pos, scope_root)
187
+ node = search_match_nodes(body).find do |match|
188
+ match.loc.expression.begin_pos > match_node_pos &&
189
+ scope_root(match) == scope_root
190
+ end
191
+ node ? node.loc.expression.begin_pos : Float::INFINITY
192
+ end
193
+
194
+ def find_last_match(body, range, scope_root)
195
+ last_matches(body).find do |ref|
196
+ ref_pos = ref.loc.expression.begin_pos
197
+ range.cover?(ref_pos) &&
198
+ scope_root(ref) == scope_root
199
+ end
200
+ end
201
+
202
+ def scope_body(node)
203
+ children = node.children
204
+ case node.type
205
+ when :module
206
+ children[1]
207
+ when :defs
208
+ children[3]
209
+ else
210
+ children[2]
211
+ end
212
+ end
213
+
214
+ def scope_root(node)
215
+ node.each_ancestor.find do |ancestor|
216
+ ancestor.def_type? ||
217
+ ancestor.defs_type? ||
218
+ ancestor.class_type? ||
219
+ ancestor.module_type?
220
+ end
221
+ end
222
+
223
+ def match_gvar?(sym)
224
+ %i[
225
+ $~
226
+ $MATCH
227
+ $PREMATCH
228
+ $POSTMATCH
229
+ $LAST_PAREN_MATCH
230
+ $LAST_MATCH_INFO
231
+ ].include?(sym)
232
+ end
233
+
234
+ def correct_operator(corrector, recv, arg, oper = nil)
235
+ op_range = correction_range(recv, arg)
236
+
237
+ if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
238
+ corrector.replace(op_range, '.match?(')
239
+ elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
240
+ corrector.replace(op_range, '.match?(')
241
+ swap_receiver_and_arg(corrector, recv, arg)
242
+ else
243
+ corrector.replace(op_range, '&.match?(')
244
+ end
245
+
246
+ corrector.insert_after(arg.loc.expression, ')')
247
+ corrector.insert_before(recv.loc.expression, '!') if oper == :!~
248
+ end
249
+
250
+ def swap_receiver_and_arg(corrector, recv, arg)
251
+ corrector.replace(recv.loc.expression, arg.source)
252
+ corrector.replace(arg.loc.expression, recv.source)
253
+ end
254
+
255
+ def correction_range(recv, arg)
256
+ buffer = processed_source.buffer
257
+ op_begin_pos = recv.loc.expression.end_pos
258
+ op_end_pos = arg.loc.expression.begin_pos
259
+ Parser::Source::Range.new(buffer, op_begin_pos, op_end_pos)
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify usages of `reverse.each` and
7
+ # change them to use `reverse_each` instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # [].reverse.each
12
+ #
13
+ # # good
14
+ # [].reverse_each
15
+ class ReverseEach < Cop
16
+ include RangeHelp
17
+
18
+ MSG = 'Use `reverse_each` instead of `reverse.each`.'.freeze
19
+ UNDERSCORE = '_'.freeze
20
+
21
+ def_node_matcher :reverse_each?, <<-MATCHER
22
+ (send $(send _ :reverse) :each)
23
+ MATCHER
24
+
25
+ def on_send(node)
26
+ reverse_each?(node) do |receiver|
27
+ location_of_reverse = receiver.loc.selector.begin_pos
28
+ end_location = node.loc.selector.end_pos
29
+
30
+ range = range_between(location_of_reverse, end_location)
31
+
32
+ add_offense(node, location: range)
33
+ end
34
+ end
35
+
36
+ def autocorrect(node)
37
+ ->(corrector) { corrector.replace(node.loc.dot, UNDERSCORE) }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify usages of `shuffle.first`, `shuffle.last`
7
+ # and `shuffle[]` and change them to use `sample` instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # [1, 2, 3].shuffle.first
12
+ # [1, 2, 3].shuffle.first(2)
13
+ # [1, 2, 3].shuffle.last
14
+ # [2, 1, 3].shuffle.at(0)
15
+ # [2, 1, 3].shuffle.slice(0)
16
+ # [1, 2, 3].shuffle[2]
17
+ # [1, 2, 3].shuffle[0, 2] # sample(2) will do the same
18
+ # [1, 2, 3].shuffle[0..2] # sample(3) will do the same
19
+ # [1, 2, 3].shuffle(random: Random.new).first
20
+ #
21
+ # # good
22
+ # [1, 2, 3].shuffle
23
+ # [1, 2, 3].sample
24
+ # [1, 2, 3].sample(3)
25
+ # [1, 2, 3].shuffle[1, 3] # sample(3) might return a longer Array
26
+ # [1, 2, 3].shuffle[1..3] # sample(3) might return a longer Array
27
+ # [1, 2, 3].shuffle[foo, bar]
28
+ # [1, 2, 3].shuffle(random: Random.new)
29
+ class Sample < Cop
30
+ MSG = 'Use `%<correct>s` instead of `%<incorrect>s`.'.freeze
31
+
32
+ def_node_matcher :sample_candidate?, <<-PATTERN
33
+ (send $(send _ :shuffle $...) ${:first :last :[] :at :slice} $...)
34
+ PATTERN
35
+
36
+ def on_send(node)
37
+ sample_candidate?(node) do |shuffle, shuffle_arg, method, method_args|
38
+ return unless offensive?(method, method_args)
39
+
40
+ range = source_range(shuffle, node)
41
+ message = message(shuffle_arg, method, method_args, range)
42
+ add_offense(node, location: range, message: message)
43
+ end
44
+ end
45
+
46
+ def autocorrect(node)
47
+ shuffle_node, shuffle_arg, method, method_args =
48
+ sample_candidate?(node)
49
+
50
+ lambda do |corrector|
51
+ corrector.replace(source_range(shuffle_node, node),
52
+ correction(shuffle_arg, method, method_args))
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def offensive?(method, method_args)
59
+ case method
60
+ when :first, :last
61
+ true
62
+ when :[], :at, :slice
63
+ sample_size(method_args) != :unknown
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ def sample_size(method_args)
70
+ case method_args.size
71
+ when 1
72
+ sample_size_for_one_arg(method_args.first)
73
+ when 2
74
+ sample_size_for_two_args(*method_args)
75
+ end
76
+ end
77
+
78
+ def sample_size_for_one_arg(arg)
79
+ case arg.type
80
+ when :erange, :irange
81
+ range_size(arg)
82
+ when :int
83
+ [0, -1].include?(arg.to_a.first) ? nil : :unknown
84
+ else
85
+ :unknown
86
+ end
87
+ end
88
+
89
+ def sample_size_for_two_args(first, second)
90
+ return :unknown unless first.int_type? && first.to_a.first.zero?
91
+ second.int_type? ? second.to_a.first : :unknown
92
+ end
93
+
94
+ def range_size(range_node)
95
+ vals = range_node.to_a
96
+ return :unknown unless vals.all?(&:int_type?)
97
+ low, high = vals.map { |val| val.children[0] }
98
+ return :unknown unless low.zero? && high >= 0
99
+
100
+ case range_node.type
101
+ when :erange
102
+ (low...high).size
103
+ when :irange
104
+ (low..high).size
105
+ end
106
+ end
107
+
108
+ def source_range(shuffle_node, node)
109
+ Parser::Source::Range.new(shuffle_node.source_range.source_buffer,
110
+ shuffle_node.loc.selector.begin_pos,
111
+ node.source_range.end_pos)
112
+ end
113
+
114
+ def message(shuffle_arg, method, method_args, range)
115
+ format(MSG,
116
+ correct: correction(shuffle_arg, method, method_args),
117
+ incorrect: range.source)
118
+ end
119
+
120
+ def correction(shuffle_arg, method, method_args)
121
+ shuffle_arg = extract_source(shuffle_arg)
122
+ sample_arg = sample_arg(method, method_args)
123
+ args = [sample_arg, shuffle_arg].compact.join(', ')
124
+ args.empty? ? 'sample' : "sample(#{args})"
125
+ end
126
+
127
+ def sample_arg(method, method_args)
128
+ case method
129
+ when :first, :last
130
+ extract_source(method_args)
131
+ when :[], :slice
132
+ sample_size(method_args)
133
+ end
134
+ end
135
+
136
+ def extract_source(args)
137
+ args.empty? ? nil : args.first.source
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end