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,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify usages of `count` on an `Enumerable` that
7
+ # follow calls to `select` or `reject`. Querying logic can instead be
8
+ # passed to the `count` call.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # [1, 2, 3].select { |e| e > 2 }.size
13
+ # [1, 2, 3].reject { |e| e > 2 }.size
14
+ # [1, 2, 3].select { |e| e > 2 }.length
15
+ # [1, 2, 3].reject { |e| e > 2 }.length
16
+ # [1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }
17
+ # [1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }
18
+ # array.select(&:value).count
19
+ #
20
+ # # good
21
+ # [1, 2, 3].count { |e| e > 2 }
22
+ # [1, 2, 3].count { |e| e < 2 }
23
+ # [1, 2, 3].count { |e| e > 2 && e.odd? }
24
+ # [1, 2, 3].count { |e| e < 2 && e.even? }
25
+ # Model.select('field AS field_one').count
26
+ # Model.select(:value).count
27
+ #
28
+ # `ActiveRecord` compatibility:
29
+ # `ActiveRecord` will ignore the block that is passed to `count`.
30
+ # Other methods, such as `select`, will convert the association to an
31
+ # array and then run the block on the array. A simple work around to
32
+ # make `count` work with a block is to call `to_a.count {...}`.
33
+ #
34
+ # Example:
35
+ # Model.where(id: [1, 2, 3].select { |m| m.method == true }.size
36
+ #
37
+ # becomes:
38
+ #
39
+ # Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }
40
+ class Count < Cop
41
+ include SafeMode
42
+ include RangeHelp
43
+
44
+ MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'.freeze
45
+
46
+ def_node_matcher :count_candidate?, <<-PATTERN
47
+ {
48
+ (send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
49
+ (send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
50
+ }
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ return if rails_safe_mode?
55
+
56
+ count_candidate?(node) do |selector_node, selector, counter|
57
+ return unless eligible_node?(node)
58
+
59
+ range = source_starting_at(node) do
60
+ selector_node.loc.selector.begin_pos
61
+ end
62
+
63
+ add_offense(node,
64
+ location: range,
65
+ message: format(MSG, selector: selector,
66
+ counter: counter))
67
+ end
68
+ end
69
+
70
+ def autocorrect(node)
71
+ selector_node, selector, _counter = count_candidate?(node)
72
+ selector_loc = selector_node.loc.selector
73
+
74
+ return if selector == :reject
75
+
76
+ range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
77
+
78
+ lambda do |corrector|
79
+ corrector.remove(range)
80
+ corrector.replace(selector_loc, 'count')
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def eligible_node?(node)
87
+ !(node.parent && node.parent.block_type?)
88
+ end
89
+
90
+ def source_starting_at(node)
91
+ begin_pos = if block_given?
92
+ yield node
93
+ else
94
+ node.source_range.begin_pos
95
+ end
96
+
97
+ range_between(begin_pos, node.source_range.end_pos)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,110 @@
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
+ # `select.first`, `select.last`, `find_all.first`, and `find_all.last`
8
+ # and change them to use `detect` instead.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # [].select { |item| true }.first
13
+ # [].select { |item| true }.last
14
+ # [].find_all { |item| true }.first
15
+ # [].find_all { |item| true }.last
16
+ #
17
+ # # good
18
+ # [].detect { |item| true }
19
+ # [].reverse.detect { |item| true }
20
+ #
21
+ # `ActiveRecord` compatibility:
22
+ # `ActiveRecord` does not implement a `detect` method and `find` has its
23
+ # own meaning. Correcting ActiveRecord methods with this cop should be
24
+ # considered unsafe.
25
+ class Detect < Cop
26
+ include SafeMode
27
+
28
+ MSG = 'Use `%<prefer>s` instead of ' \
29
+ '`%<first_method>s.%<second_method>s`.'.freeze
30
+ REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
31
+ '`%<first_method>s.%<second_method>s`.'.freeze
32
+
33
+ def_node_matcher :detect_candidate?, <<-PATTERN
34
+ {
35
+ (send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
36
+ (send $(send _ {:select :find_all} ...) ${:first :last} $...)
37
+ }
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ return if rails_safe_mode?
42
+
43
+ detect_candidate?(node) do |receiver, second_method, args|
44
+ return unless args.empty?
45
+ return unless receiver
46
+
47
+ receiver, _args, body = *receiver if receiver.block_type?
48
+ return if accept_first_call?(receiver, body)
49
+
50
+ register_offense(node, receiver, second_method)
51
+ end
52
+ end
53
+
54
+ def autocorrect(node)
55
+ receiver, first_method = *node
56
+
57
+ replacement = if first_method == :last
58
+ "reverse.#{preferred_method}"
59
+ else
60
+ preferred_method
61
+ end
62
+
63
+ first_range = receiver.source_range.end.join(node.loc.selector)
64
+
65
+ receiver, _args, _body = *receiver if receiver.block_type?
66
+
67
+ lambda do |corrector|
68
+ corrector.remove(first_range)
69
+ corrector.replace(receiver.loc.selector, replacement)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def accept_first_call?(receiver, body)
76
+ caller, _first_method, args = *receiver
77
+
78
+ # check that we have usual block or block pass
79
+ return true if body.nil? && (args.nil? || !args.block_pass_type?)
80
+
81
+ lazy?(caller)
82
+ end
83
+
84
+ def register_offense(node, receiver, second_method)
85
+ _caller, first_method, _args = *receiver
86
+ range = receiver.loc.selector.join(node.loc.selector)
87
+
88
+ message = second_method == :last ? REVERSE_MSG : MSG
89
+ formatted_message = format(message, prefer: preferred_method,
90
+ first_method: first_method,
91
+ second_method: second_method)
92
+
93
+ add_offense(node, location: range, message: formatted_message)
94
+ end
95
+
96
+ def preferred_method
97
+ config.for_cop('Style/CollectionMethods') \
98
+ ['PreferredMethods']['detect'] || 'detect'
99
+ end
100
+
101
+ def lazy?(node)
102
+ return false unless node
103
+
104
+ receiver, method, _args = *node
105
+ method == :lazy && !receiver.nil?
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop checks for double `#start_with?` or `#end_with?` calls
7
+ # separated by `||`. In some cases such calls can be replaced
8
+ # with an single `#start_with?`/`#end_with?` call.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # str.start_with?("a") || str.start_with?(Some::CONST)
13
+ # str.start_with?("a", "b") || str.start_with?("c")
14
+ # str.end_with?(var1) || str.end_with?(var2)
15
+ #
16
+ # # good
17
+ # str.start_with?("a", Some::CONST)
18
+ # str.start_with?("a", "b", "c")
19
+ # str.end_with?(var1, var2)
20
+ class DoubleStartEndWith < Cop
21
+ MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
22
+ 'instead of `%<original_code>s`.'.freeze
23
+
24
+ def on_or(node)
25
+ receiver,
26
+ method,
27
+ first_call_args,
28
+ second_call_args = process_source(node)
29
+
30
+ return unless receiver && second_call_args.all?(&:pure?)
31
+
32
+ combined_args = combine_args(first_call_args, second_call_args)
33
+
34
+ add_offense_for_double_call(node, receiver, method, combined_args)
35
+ end
36
+
37
+ def autocorrect(node)
38
+ _receiver, _method,
39
+ first_call_args, second_call_args = process_source(node)
40
+
41
+ combined_args = combine_args(first_call_args, second_call_args)
42
+ first_argument = first_call_args.first.loc.expression
43
+ last_argument = second_call_args.last.loc.expression
44
+ range = first_argument.join(last_argument)
45
+
46
+ lambda do |corrector|
47
+ corrector.replace(range, combined_args)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def process_source(node)
54
+ if check_for_active_support_aliases?
55
+ check_with_active_support_aliases(node)
56
+ else
57
+ two_start_end_with_calls(node)
58
+ end
59
+ end
60
+
61
+ def combine_args(first_call_args, second_call_args)
62
+ (first_call_args + second_call_args).map(&:source).join(', ')
63
+ end
64
+
65
+ def add_offense_for_double_call(node, receiver, method, combined_args)
66
+ msg = format(MSG, receiver: receiver.source,
67
+ method: method,
68
+ combined_args: combined_args,
69
+ original_code: node.source)
70
+
71
+ add_offense(node, message: msg)
72
+ end
73
+
74
+ def check_for_active_support_aliases?
75
+ cop_config['IncludeActiveSupportAliases']
76
+ end
77
+
78
+ def_node_matcher :two_start_end_with_calls, <<-PATTERN
79
+ (or
80
+ (send $_recv [{:start_with? :end_with?} $_method] $...)
81
+ (send _recv _method $...))
82
+ PATTERN
83
+
84
+ def_node_matcher :check_with_active_support_aliases, <<-PATTERN
85
+ (or
86
+ (send $_recv
87
+ [{:start_with? :starts_with? :end_with? :ends_with?} $_method]
88
+ $...)
89
+ (send _recv _method $...))
90
+ PATTERN
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies unnecessary use of a regex where `String#end_with?`
7
+ # would suffice.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # 'abc'.match?(/bc\Z/)
12
+ # 'abc' =~ /bc\Z/
13
+ # 'abc'.match(/bc\Z/)
14
+ #
15
+ # # good
16
+ # 'abc'.end_with?('bc')
17
+ class EndWith < Cop
18
+ MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
19
+ 'the end of the string.'.freeze
20
+ SINGLE_QUOTE = "'".freeze
21
+
22
+ def_node_matcher :redundant_regex?, <<-PATTERN
23
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
24
+ (send (regexp (str $#literal_at_end?) (regopt)) {:match :=~} $_)}
25
+ PATTERN
26
+
27
+ def literal_at_end?(regex_str)
28
+ # is this regexp 'literal' in the sense of only matching literal
29
+ # chars, rather than using metachars like . and * and so on?
30
+ # also, is it anchored at the end of the string?
31
+ regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
32
+ end
33
+
34
+ def on_send(node)
35
+ return unless redundant_regex?(node)
36
+
37
+ add_offense(node)
38
+ end
39
+
40
+ def autocorrect(node)
41
+ redundant_regex?(node) do |receiver, regex_str|
42
+ receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
43
+ regex_str = regex_str[0..-3] # drop \Z anchor
44
+ regex_str = interpret_string_escapes(regex_str)
45
+
46
+ lambda do |corrector|
47
+ new_source = receiver.source + '.end_with?(' +
48
+ to_string_literal(regex_str) + ')'
49
+ corrector.replace(node.source_range, new_source)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # Do not compute the size of statically sized objects.
7
+ #
8
+ # @example
9
+ # # String methods
10
+ # # bad
11
+ # 'foo'.size
12
+ # %q[bar].count
13
+ # %(qux).length
14
+ #
15
+ # # Symbol methods
16
+ # # bad
17
+ # :fred.size
18
+ # :'baz'.length
19
+ #
20
+ # # Array methods
21
+ # # bad
22
+ # [1, 2, thud].count
23
+ # %W(1, 2, bar).size
24
+ #
25
+ # # Hash methods
26
+ # # bad
27
+ # { a: corge, b: grault }.length
28
+ #
29
+ # # good
30
+ # foo.size
31
+ # bar.count
32
+ # qux.length
33
+ #
34
+ # # good
35
+ # :"#{fred}".size
36
+ # CONST = :baz.length
37
+ #
38
+ # # good
39
+ # [1, 2, *thud].count
40
+ # garply = [1, 2, 3]
41
+ # garly.size
42
+ #
43
+ # # good
44
+ # { a: corge, **grault }.length
45
+ # waldo = { a: corge, b: grault }
46
+ # waldo.size
47
+ #
48
+ class FixedSize < Cop
49
+ MSG = 'Do not compute the size of statically sized objects.'.freeze
50
+
51
+ def_node_matcher :counter, <<-MATCHER
52
+ (send ${array hash str sym} {:count :length :size} $...)
53
+ MATCHER
54
+
55
+ def on_send(node)
56
+ return if allowed_parent?(node.parent)
57
+
58
+ counter(node) do |var, arg|
59
+ return if allowed_variable?(var) || allowed_argument?(arg)
60
+
61
+ add_offense(node)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def allowed_variable?(var)
68
+ contains_splat?(var) || contains_double_splat?(var)
69
+ end
70
+
71
+ def allowed_argument?(arg)
72
+ arg && non_string_argument?(arg.first)
73
+ end
74
+
75
+ def allowed_parent?(node)
76
+ node && (node.casgn_type? || node.block_type?)
77
+ end
78
+
79
+ def contains_splat?(node)
80
+ return unless node.array_type?
81
+
82
+ node.each_child_node(:splat).any?
83
+ end
84
+
85
+ def contains_double_splat?(node)
86
+ return unless node.hash_type?
87
+
88
+ node.each_child_node(:kwsplat).any?
89
+ end
90
+
91
+ def non_string_argument?(node)
92
+ node && !node.str_type?
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end