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