rubocop-performance 0.0.0 → 0.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab4ceb1e31551e3774fc563d05282b5339c9cb132d0e9a9ce1ce6f63358cf468
4
- data.tar.gz: 8d5d42ecb4e72ed9d06e30e7c609569469d4cf2fc847c8fabf602723ac8b4c21
3
+ metadata.gz: 43633d075fac26295caf28e79d71f56cdbacd6d7853aead53b3ca35e4f29f99b
4
+ data.tar.gz: 60886d6ea540f6ed25014424376b2e0cf0f489a2291e298f1f4cbf6084f3e580
5
5
  SHA512:
6
- metadata.gz: a42a85f9011b35eae2a99ffa9763bcfa2f409079b4c1aba3e0659adb351046cc5d8c42e5b374dda1f1d78289870339ee5fe8a34460a89d38a8fe2c24589398b5
7
- data.tar.gz: 2acc0104f805827d517ebca4d9a29a382642af8b53fec7691b86bb53eaabe22a29b7c1df2b91c0d53e27429430fa191788477b19545dec4d17c286432bc03a03
6
+ metadata.gz: b7b30f444965b49aa1597f9f1e483802b3c2f2f786c71fd577f6af18dbdf268e0100793982aa2d8979b86c7a9f302b0b211694e5f8e04c6b2eeaae85c7dd0591
7
+ data.tar.gz: ac1707a5f94ae008d1baf8c90b2ff269a6bfb3da8e27ed4adb1fa968b69e6dfdbc7ae8a074d18c4fa5d6e7c6b3d5de814f5b5dfbd38832338e80d107b854ad33
@@ -0,0 +1,24 @@
1
+ # This is the default configuration file.
2
+
3
+ Performance/CaseWhenSplat:
4
+ Description: >-
5
+ Reordering `when` conditions with a splat to the end
6
+ of the `when` branches can improve performance.
7
+ Enabled: false
8
+ AutoCorrect: false
9
+
10
+ Performance/ChainArrayAllocation:
11
+ Description: >-
12
+ Instead of chaining array methods that allocate new arrays, mutate an
13
+ existing array.
14
+ Reference: 'https://twitter.com/schneems/status/1034123879978029057'
15
+ Enabled: false
16
+
17
+ Performance/DoubleStartEndWith:
18
+ # Used to check for `starts_with?` and `ends_with?`.
19
+ # These methods are defined by `ActiveSupport`.
20
+ IncludeActiveSupportAliases: false
21
+
22
+ Performance/RedundantMerge:
23
+ # Max number of key-value pairs to consider an offense
24
+ MaxKeyValuePairs: 2
@@ -3,16 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # Place `when` conditions that use splat at the end
7
- # of the list of `when` branches.
6
+ # Reordering `when` conditions with a splat to the end
7
+ # of the `when` branches can improve performance.
8
8
  #
9
9
  # Ruby has to allocate memory for the splat expansion every time
10
10
  # that the `case` `when` statement is run. Since Ruby does not support
11
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
12
+ # the order of the `when` branches should not matter. By placing any
13
13
  # splat expansions at the end of the list of `when` branches we will
14
14
  # reduce the number of times that memory has to be allocated for
15
- # the expansion.
15
+ # the expansion. The exception to this is if multiple of your `when`
16
+ # conditions can be true for any given condition. A likely scenario for
17
+ # this defining a higher level when condition to override a condition
18
+ # that is inside of the splat expansion.
16
19
  #
17
20
  # This is not a guaranteed performance improvement. If the data being
18
21
  # processed by the `case` condition is normalized in a manner that favors
@@ -54,9 +57,10 @@ module RuboCop
54
57
  include Alignment
55
58
  include RangeHelp
56
59
 
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
+ MSG = 'Reordering `when` conditions with a splat to the end ' \
61
+ 'of the `when` branches can improve performance.'.freeze
62
+ ARRAY_MSG = 'Pass the contents of array literals ' \
63
+ 'directly to `when` conditions.'.freeze
60
64
 
61
65
  def on_case(case_node)
62
66
  when_conditions = case_node.when_branches.flat_map(&:conditions)
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # str.casecmp('ABC').zero?
19
19
  # 'abc'.casecmp(str).zero?
20
20
  class Casecmp < Cop
21
- MSG = 'Use `casecmp` instead of `%<methods>s`.'.freeze
21
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'.freeze
22
22
  CASE_METHODS = %i[downcase upcase].freeze
23
23
 
24
24
  def_node_matcher :downcase_eq, <<-PATTERN
@@ -43,16 +43,29 @@ module RuboCop
43
43
  PATTERN
44
44
 
45
45
  def on_send(node)
46
- return if part_of_ignored_node?(node)
46
+ return unless downcase_eq(node) || eq_downcase(node)
47
+ return unless (parts = take_method_apart(node))
47
48
 
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
49
+ _, _, arg, variable = parts
50
+ good_method = build_good_method(arg, variable)
51
+
52
+ add_offense(
53
+ node,
54
+ message: format(MSG, good: good_method, bad: node.source)
55
+ )
53
56
  end
54
57
 
55
58
  def autocorrect(node)
59
+ return unless (parts = take_method_apart(node))
60
+
61
+ receiver, method, arg, variable = parts
62
+
63
+ correction(node, receiver, method, arg, variable)
64
+ end
65
+
66
+ private
67
+
68
+ def take_method_apart(node)
56
69
  if downcase_downcase(node)
57
70
  receiver, method, rhs = *node
58
71
  arg, = *rhs
@@ -65,52 +78,30 @@ module RuboCop
65
78
  end
66
79
 
67
80
  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
81
 
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
82
+ [receiver, method, arg, variable]
96
83
  end
97
84
 
98
85
  def correction(node, _receiver, method, arg, variable)
99
86
  lambda do |corrector|
100
87
  corrector.insert_before(node.loc.expression, '!') if method == :!=
101
88
 
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
89
+ replacement = build_good_method(arg, variable)
110
90
 
111
91
  corrector.replace(node.loc.expression, replacement)
112
92
  end
113
93
  end
94
+
95
+ def build_good_method(arg, variable)
96
+ # We want resulting call to be parenthesized
97
+ # if arg already includes one or more sets of parens, don't add more
98
+ # or if method call already used parens, again, don't add more
99
+ if arg.send_type? || !parentheses?(arg)
100
+ "#{variable.source}.casecmp(#{arg.source}).zero?"
101
+ else
102
+ "#{variable.source}.casecmp#{arg.source}.zero?"
103
+ end
104
+ end
114
105
  end
115
106
  end
116
107
  end
@@ -0,0 +1,77 @@
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
+ # @example
8
+ # # bad
9
+ # array = ["a", "b", "c"]
10
+ # array.compact.flatten.map { |x| x.downcase }
11
+ #
12
+ # Each of these methods (`compact`, `flatten`, `map`) will generate a
13
+ # new intermediate array that is promptly thrown away. Instead it is
14
+ # faster to mutate when we know it's safe.
15
+ #
16
+ # @example
17
+ # # good.
18
+ # array = ["a", "b", "c"]
19
+ # array.compact!
20
+ # array.flatten!
21
+ # array.map! { |x| x.downcase }
22
+ # array
23
+ class ChainArrayAllocation < Cop
24
+ include RangeHelp
25
+
26
+ # These methods return a new array but only sometimes. They must be
27
+ # called with an argument. For example:
28
+ #
29
+ # [1,2].first # => 1
30
+ # [1,2].first(1) # => [1]
31
+ #
32
+ RETURN_NEW_ARRAY_WHEN_ARGS = ':first :last :pop :sample :shift '.freeze
33
+
34
+ # These methods return a new array only when called without a block.
35
+ RETURNS_NEW_ARRAY_WHEN_NO_BLOCK = ':zip :product '.freeze
36
+
37
+ # These methods ALWAYS return a new array
38
+ # after they're called it's safe to mutate the the resulting array
39
+ ALWAYS_RETURNS_NEW_ARRAY = ':* :+ :- :collect :compact :drop '\
40
+ ':drop_while :flatten :map :reject ' \
41
+ ':reverse :rotate :select :shuffle :sort ' \
42
+ ':take :take_while :transpose :uniq ' \
43
+ ':values_at :| '.freeze
44
+
45
+ # These methods have a mutation alternative. For example :collect
46
+ # can be called as :collect!
47
+ HAS_MUTATION_ALTERNATIVE = ':collect :compact :flatten :map :reject '\
48
+ ':reverse :rotate :select :shuffle :sort '\
49
+ ':uniq '.freeze
50
+ MSG = 'Use `%<method>s...%<second_method>s!` instead of `%<method>s' \
51
+ '...%<second_method>s`.'.freeze
52
+
53
+ def_node_matcher :flat_map_candidate?, <<-PATTERN
54
+ {
55
+ (send (send _ ${#{RETURN_NEW_ARRAY_WHEN_ARGS}} {int lvar ivar cvar gvar}) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
56
+ (send (block (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY} }) ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
57
+ (send (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY + RETURNS_NEW_ARRAY_WHEN_NO_BLOCK}} ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
58
+ }
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ flat_map_candidate?(node) do |fm, sm, _|
63
+ range = range_between(
64
+ node.loc.dot.begin_pos,
65
+ node.source_range.end_pos
66
+ )
67
+ add_offense(
68
+ node,
69
+ location: range,
70
+ message: format(MSG, method: fm, second_method: sm)
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -48,6 +48,7 @@ module RuboCop
48
48
  compare?(node) do |send, var_a, var_b, body|
49
49
  replaceable_body?(body, var_a, var_b) do |method, args_a, args_b|
50
50
  return unless slow_compare?(method, args_a, args_b)
51
+
51
52
  range = compare_range(send, node)
52
53
 
53
54
  add_offense(
@@ -78,8 +79,10 @@ module RuboCop
78
79
 
79
80
  def slow_compare?(method, args_a, args_b)
80
81
  return false unless args_a == args_b
82
+
81
83
  if method == :[]
82
84
  return false unless args_a.size == 1
85
+
83
86
  key = args_a.first
84
87
  return false unless %i[sym str int].include?(key.type)
85
88
  else
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # In Ruby 2.4, `String#match?`, `Regexp#match?` and `Symbol#match?`
6
+ # In Ruby 2.4, `String#match?`, `Regexp#match?`, and `Symbol#match?`
7
7
  # have been added. The methods are faster than `match`.
8
8
  # Because the methods avoid creating a `MatchData` object or saving
9
9
  # backref.
@@ -101,6 +101,7 @@ module RuboCop
101
101
 
102
102
  def match_with_lvasgn?(node)
103
103
  return false unless node.match_with_lvasgn_type?
104
+
104
105
  regexp, _rhs = *node
105
106
  regexp.to_regexp.named_captures.empty?
106
107
  end
@@ -3,8 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
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.
6
+ # This cop is used to identify usages of `shuffle.first`,
7
+ # `shuffle.last`, and `shuffle[]` and change them to use
8
+ # `sample` instead.
8
9
  #
9
10
  # @example
10
11
  # # bad
@@ -88,12 +89,14 @@ module RuboCop
88
89
 
89
90
  def sample_size_for_two_args(first, second)
90
91
  return :unknown unless first.int_type? && first.to_a.first.zero?
92
+
91
93
  second.int_type? ? second.to_a.first : :unknown
92
94
  end
93
95
 
94
96
  def range_size(range_node)
95
97
  vals = range_node.to_a
96
98
  return :unknown unless vals.all?(&:int_type?)
99
+
97
100
  low, high = vals.map { |val| val.children[0] }
98
101
  return :unknown unless low.zero? && high >= 0
99
102
 
@@ -55,15 +55,21 @@ module RuboCop
55
55
  end
56
56
 
57
57
  def array?(node)
58
+ return true if node.array_type?
59
+ return false unless node.send_type?
60
+
58
61
  _, constant = *node.receiver
59
62
 
60
- node.array_type? || constant == :Array || node.method_name == :to_a
63
+ constant == :Array || node.method_name == :to_a
61
64
  end
62
65
 
63
66
  def hash?(node)
67
+ return true if node.hash_type?
68
+ return false unless node.send_type?
69
+
64
70
  _, constant = *node.receiver
65
71
 
66
- node.hash_type? || constant == :Hash || node.method_name == :to_h
72
+ constant == :Hash || node.method_name == :to_h
67
73
  end
68
74
  end
69
75
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  def literal_at_start?(regex_str)
28
28
  # is this regexp 'literal' in the sense of only matching literal
29
- # chars, rather than using metachars like . and * and so on?
29
+ # chars, rather than using metachars like `.` and `*` and so on?
30
30
  # also, is it anchored at the start of the string?
31
31
  # (tricky: \s, \d, and so on are metacharacters, but other characters
32
32
  # escaped with a slash are just literals. LITERAL_REGEX takes all
@@ -22,7 +22,7 @@ module RuboCop
22
22
  include RangeHelp
23
23
 
24
24
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'.freeze
25
- DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/
25
+ DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
26
26
  DELETE = 'delete'.freeze
27
27
  TR = 'tr'.freeze
28
28
  BANG = '!'.freeze
@@ -87,6 +87,7 @@ module RuboCop
87
87
  unless first_param.str_type?
88
88
  return true if options
89
89
  return true unless first_source =~ DETERMINISTIC_REGEX
90
+
90
91
  # This must be done after checking DETERMINISTIC_REGEX
91
92
  # Otherwise things like \s will trip us up
92
93
  first_source = interpret_string_escapes(first_source)
@@ -35,3 +35,4 @@ require_relative 'performance/times_map'
35
35
  require_relative 'performance/unfreeze_string'
36
36
  require_relative 'performance/unneeded_sort'
37
37
  require_relative 'performance/uri_default_parser'
38
+ require_relative 'performance/chain_array_allocation'
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Performance
5
5
  module Version
6
- STRING = '0.0.0'
6
+ STRING = '0.0.1'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-performance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,22 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-07-10 00:00:00.000000000 Z
13
+ date: 2018-10-31 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - "~>"
19
+ - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '0.57'
21
+ version: 0.58.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - "~>"
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '0.57'
28
+ version: 0.58.0
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: simplecov
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -52,10 +52,12 @@ extra_rdoc_files:
52
52
  files:
53
53
  - LICENSE.txt
54
54
  - README.md
55
+ - config/default.yml
55
56
  - lib/rubocop-performance.rb
56
57
  - lib/rubocop/cop/performance/caller.rb
57
58
  - lib/rubocop/cop/performance/case_when_splat.rb
58
59
  - lib/rubocop/cop/performance/casecmp.rb
60
+ - lib/rubocop/cop/performance/chain_array_allocation.rb
59
61
  - lib/rubocop/cop/performance/compare_with_block.rb
60
62
  - lib/rubocop/cop/performance/count.rb
61
63
  - lib/rubocop/cop/performance/detect.rb
@@ -99,7 +101,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
99
101
  requirements:
100
102
  - - ">="
101
103
  - !ruby/object:Gem::Version
102
- version: 2.3.0
104
+ version: 2.2.0
103
105
  required_rubygems_version: !ruby/object:Gem::Requirement
104
106
  requirements:
105
107
  - - ">="
@@ -107,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
109
  version: '0'
108
110
  requirements: []
109
111
  rubyforge_project:
110
- rubygems_version: 2.7.7
112
+ rubygems_version: 2.7.6
111
113
  signing_key:
112
114
  specification_version: 4
113
115
  summary: Automatic performance checking tool for Ruby code.