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