rubocop-performance 1.5.2 → 1.6.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 (35) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +19 -3
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +41 -0
  6. data/lib/rubocop/cop/performance/bind_call.rb +87 -0
  7. data/lib/rubocop/cop/performance/caller.rb +2 -2
  8. data/lib/rubocop/cop/performance/casecmp.rb +5 -3
  9. data/lib/rubocop/cop/performance/chain_array_allocation.rb +1 -1
  10. data/lib/rubocop/cop/performance/compare_with_block.rb +2 -2
  11. data/lib/rubocop/cop/performance/count.rb +1 -1
  12. data/lib/rubocop/cop/performance/delete_prefix.rb +72 -0
  13. data/lib/rubocop/cop/performance/delete_suffix.rb +72 -0
  14. data/lib/rubocop/cop/performance/detect.rb +1 -1
  15. data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
  16. data/lib/rubocop/cop/performance/end_with.rb +11 -10
  17. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  18. data/lib/rubocop/cop/performance/flat_map.rb +1 -1
  19. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
  20. data/lib/rubocop/cop/performance/open_struct.rb +1 -1
  21. data/lib/rubocop/cop/performance/range_include.rb +1 -1
  22. data/lib/rubocop/cop/performance/redundant_block_call.rb +3 -3
  23. data/lib/rubocop/cop/performance/redundant_match.rb +2 -2
  24. data/lib/rubocop/cop/performance/redundant_merge.rb +18 -6
  25. data/lib/rubocop/cop/performance/regexp_match.rb +13 -13
  26. data/lib/rubocop/cop/performance/reverse_each.rb +3 -2
  27. data/lib/rubocop/cop/performance/start_with.rb +11 -13
  28. data/lib/rubocop/cop/performance/string_replacement.rb +4 -11
  29. data/lib/rubocop/cop/performance/times_map.rb +1 -1
  30. data/lib/rubocop/cop/performance/unfreeze_string.rb +2 -6
  31. data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
  32. data/lib/rubocop/cop/performance_cops.rb +5 -0
  33. data/lib/rubocop/performance/inject.rb +1 -1
  34. data/lib/rubocop/performance/version.rb +1 -1
  35. metadata +14 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 330b6db292d0709ef76d23cae8b7e84b24413d315b356a10a8a8596859b366ce
4
- data.tar.gz: 32e9f148b06d630da9233b4fab33ea142eb0aa1006cd7cc311aafadabfe3c8fa
2
+ SHA1:
3
+ metadata.gz: 5e13aa59742e803b8445a8149c382c50fdbaab1d
4
+ data.tar.gz: 55d59fd7bb03179a9055e46613a3d1f2d2888967
5
5
  SHA512:
6
- metadata.gz: fddb07ffb4b9ab812d7fcc454f36b3d70fe016783b9cbd863bd0a1391266a39177831b62afb2e35fd4cabc76c94a9b97108177f03ad809a09b69af58365d10dc
7
- data.tar.gz: 1f2375b3dc17a9053a3219a04c45b7f5dee9a99ce472d2e38383c50f463dd8c8c716b6bf558eb6f4245abca24b2d89391ea6fa7fe131dbd0bc7b9eaa2fb994cf
6
+ metadata.gz: 01a28951033ec2b751828d6df2a80f63a5a2d9e9d9576a2228c12b645087b2a631c3676757bd5037e3eecc22d89a7ef28765194545393210a9068da92931d065
7
+ data.tar.gz: 79b46ec046a4c1bade7e1e9dd07a5fd892f25280565adb6a91d8dddcce3931722cecf93fb72d4d0e32b7d40441829d26883973b11b5139389b6cebf91bdca435
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-18 Bozhidar Batsov
1
+ Copyright (c) 2012-20 Bozhidar Batsov
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -16,7 +16,7 @@ gem install rubocop-performance
16
16
  or if you use bundler put this in your `Gemfile`
17
17
 
18
18
  ```ruby
19
- gem 'rubocop-performance'
19
+ gem 'rubocop-performance', require: false
20
20
  ```
21
21
 
22
22
  ## Usage
@@ -72,6 +72,10 @@ Performance/Size:
72
72
  - lib/example.rb
73
73
  ```
74
74
 
75
+ ## Documentation
76
+
77
+ You can read a lot more about RuboCop Performance in its [official docs](https://docs.rubocop.org/projects/performance/).
78
+
75
79
  ## Contributing
76
80
 
77
81
  Checkout the [contribution guidelines](CONTRIBUTING.md).
@@ -1,5 +1,10 @@
1
1
  # This is the default configuration file.
2
2
 
3
+ Performance/BindCall:
4
+ Description: 'Use `bind_call(obj, args, ...)` instead of `bind(obj).call(args, ...)`.'
5
+ Enabled: true
6
+ VersionAdded: '1.6'
7
+
3
8
  Performance/Caller:
4
9
  Description: >-
5
10
  Use `caller(n..n)` instead of `caller`.
@@ -21,6 +26,7 @@ Performance/Casecmp:
21
26
  Use `casecmp` rather than `downcase ==`, `upcase ==`, `== downcase`, or `== upcase`..
22
27
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringcasecmp-vs-stringdowncase---code'
23
28
  Enabled: true
29
+ Safe: false
24
30
  VersionAdded: '0.36'
25
31
 
26
32
  Performance/ChainArrayAllocation:
@@ -49,6 +55,16 @@ Performance/Count:
49
55
  VersionAdded: '0.31'
50
56
  VersionChanged: '1.5'
51
57
 
58
+ Performance/DeletePrefix:
59
+ Description: 'Use `delete_prefix` instead of `gsub`.'
60
+ Enabled: true
61
+ VersionAdded: '1.6'
62
+
63
+ Performance/DeleteSuffix:
64
+ Description: 'Use `delete_suffix` instead of `gsub`.'
65
+ Enabled: true
66
+ VersionAdded: '1.6'
67
+
52
68
  Performance/Detect:
53
69
  Description: >-
54
70
  Use `detect` instead of `select.first`, `find_all.first`,
@@ -87,7 +103,7 @@ Performance/EndWith:
87
103
  VersionChanged: '0.44'
88
104
 
89
105
  Performance/FixedSize:
90
- Description: 'Do not compute the size of statically sized objects except in constants'
106
+ Description: 'Do not compute the size of statically sized objects except in constants.'
91
107
  Enabled: true
92
108
  VersionAdded: '0.35'
93
109
 
@@ -95,7 +111,7 @@ Performance/FlatMap:
95
111
  Description: >-
96
112
  Use `Enumerable#flat_map`
97
113
  instead of `Enumerable#map...Array#flatten(1)`
98
- or `Enumberable#collect..Array#flatten(1)`
114
+ or `Enumberable#collect..Array#flatten(1)`.
99
115
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code'
100
116
  Enabled: true
101
117
  VersionAdded: '0.30'
@@ -106,7 +122,7 @@ Performance/FlatMap:
106
122
  # `flatten` without any parameters can flatten multiple levels.
107
123
 
108
124
  Performance/InefficientHashSearch:
109
- Description: 'Use `key?` or `value?` instead of `keys.include?` or `values.include?`'
125
+ Description: 'Use `key?` or `value?` instead of `keys.include?` or `values.include?`.'
110
126
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#hashkey-instead-of-hashkeysinclude-code'
111
127
  Enabled: true
112
128
  VersionAdded: '0.56'
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for handling regexp metacharacters.
6
+ module RegexpMetacharacter
7
+ def literal_at_start?(regex_str)
8
+ # is this regexp 'literal' in the sense of only matching literal
9
+ # chars, rather than using metachars like `.` and `*` and so on?
10
+ # also, is it anchored at the start of the string?
11
+ # (tricky: \s, \d, and so on are metacharacters, but other characters
12
+ # escaped with a slash are just literals. LITERAL_REGEX takes all
13
+ # that into account.)
14
+ regex_str =~ /\A(\\A|\^)(?:#{Util::LITERAL_REGEX})+\z/
15
+ end
16
+
17
+ def literal_at_end?(regex_str)
18
+ # is this regexp 'literal' in the sense of only matching literal
19
+ # chars, rather than using metachars like . and * and so on?
20
+ # also, is it anchored at the end of the string?
21
+ regex_str =~ /\A(?:#{Util::LITERAL_REGEX})+(\\z|\$)\z/
22
+ end
23
+
24
+ def drop_start_metacharacter(regexp_string)
25
+ if regexp_string.start_with?('\\A')
26
+ regexp_string[2..-1] # drop `\A` anchor
27
+ else
28
+ regexp_string[1..-1] # drop `^` anchor
29
+ end
30
+ end
31
+
32
+ def drop_end_metacharacter(regexp_string)
33
+ if regexp_string.end_with?('\\z')
34
+ regexp_string.chomp('\z') # drop `\z` anchor
35
+ else
36
+ regexp_string.chop # drop `$` anchor
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.7, `UnboundMethod#bind_call` has been added.
7
+ #
8
+ # This cop identifies places where `bind(obj).call(args, ...)`
9
+ # can be replaced by `bind_call(obj, args, ...)`.
10
+ #
11
+ # The `bind_call(obj, args, ...)` method is faster than
12
+ # `bind(obj).call(args, ...)`.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # umethod.bind(obj).call(foo, bar)
17
+ # umethod.bind(obj).(foo, bar)
18
+ #
19
+ # # good
20
+ # umethod.bind_call(obj, foo, bar)
21
+ #
22
+ class BindCall < Cop
23
+ include RangeHelp
24
+ extend TargetRubyVersion
25
+
26
+ minimum_target_ruby_version 2.7
27
+
28
+ MSG = 'Use `bind_call(%<bind_arg>s%<comma>s%<call_args>s)` ' \
29
+ 'instead of `bind(%<bind_arg>s).call(%<call_args>s)`.'
30
+
31
+ def_node_matcher :bind_with_call_method?, <<~PATTERN
32
+ (send
33
+ $(send
34
+ (send nil? _) :bind
35
+ $(...)) :call
36
+ $...)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ bind_with_call_method?(node) do |receiver, bind_arg, call_args_node|
41
+ range = correction_range(receiver, node)
42
+
43
+ call_args = build_call_args(call_args_node)
44
+
45
+ message = message(bind_arg.source, call_args)
46
+
47
+ add_offense(node, location: range, message: message)
48
+ end
49
+ end
50
+
51
+ def autocorrect(node)
52
+ receiver, bind_arg, call_args_node = bind_with_call_method?(node)
53
+
54
+ range = correction_range(receiver, node)
55
+
56
+ call_args = build_call_args(call_args_node)
57
+ call_args = ", #{call_args}" unless call_args.empty?
58
+
59
+ replacement_method = "bind_call(#{bind_arg.source}#{call_args})"
60
+
61
+ lambda do |corrector|
62
+ corrector.replace(range, replacement_method)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def message(bind_arg, call_args)
69
+ comma = call_args.empty? ? '' : ', '
70
+
71
+ format(MSG, bind_arg: bind_arg, comma: comma, call_args: call_args)
72
+ end
73
+
74
+ def correction_range(receiver, node)
75
+ location_of_bind = receiver.loc.selector.begin_pos
76
+ location_of_call = node.loc.end.end_pos
77
+
78
+ range_between(location_of_bind, location_of_call)
79
+ end
80
+
81
+ def build_call_args(call_args_node)
82
+ call_args_node.map(&:source).join(', ')
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -24,14 +24,14 @@ module RuboCop
24
24
  MSG_FIRST = 'Use `%<method>s(%<n>d..%<n>d).first`' \
25
25
  ' instead of `%<method>s.first`.'
26
26
 
27
- def_node_matcher :slow_caller?, <<-PATTERN
27
+ def_node_matcher :slow_caller?, <<~PATTERN
28
28
  {
29
29
  (send nil? {:caller :caller_locations})
30
30
  (send nil? {:caller :caller_locations} int)
31
31
  }
32
32
  PATTERN
33
33
 
34
- def_node_matcher :caller_with_scope_method?, <<-PATTERN
34
+ def_node_matcher :caller_with_scope_method?, <<~PATTERN
35
35
  {
36
36
  (send #slow_caller? :first)
37
37
  (send #slow_caller? :[] int)
@@ -5,6 +5,8 @@ module RuboCop
5
5
  module Performance
6
6
  # This cop identifies places where a case-insensitive string comparison
7
7
  # can better be implemented using `casecmp`.
8
+ # This cop is unsafe because `String#casecmp` and `String#casecmp?` behave
9
+ # differently when using Non-ASCII characters.
8
10
  #
9
11
  # @example
10
12
  # # bad
@@ -21,21 +23,21 @@ module RuboCop
21
23
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
22
24
  CASE_METHODS = %i[downcase upcase].freeze
23
25
 
24
- def_node_matcher :downcase_eq, <<-PATTERN
26
+ def_node_matcher :downcase_eq, <<~PATTERN
25
27
  (send
26
28
  $(send _ ${:downcase :upcase})
27
29
  ${:== :eql? :!=}
28
30
  ${str (send _ {:downcase :upcase} ...) (begin str)})
29
31
  PATTERN
30
32
 
31
- def_node_matcher :eq_downcase, <<-PATTERN
33
+ def_node_matcher :eq_downcase, <<~PATTERN
32
34
  (send
33
35
  {str (send _ {:downcase :upcase} ...) (begin str)}
34
36
  ${:== :eql? :!=}
35
37
  $(send _ ${:downcase :upcase}))
36
38
  PATTERN
37
39
 
38
- def_node_matcher :downcase_downcase, <<-PATTERN
40
+ def_node_matcher :downcase_downcase, <<~PATTERN
39
41
  (send
40
42
  $(send _ ${:downcase :upcase})
41
43
  ${:== :eql? :!=}
@@ -51,7 +51,7 @@ module RuboCop
51
51
  '(followed by `return array` if required) instead of chaining '\
52
52
  '`%<method>s...%<second_method>s`.'
53
53
 
54
- def_node_matcher :flat_map_candidate?, <<-PATTERN
54
+ def_node_matcher :flat_map_candidate?, <<~PATTERN
55
55
  {
56
56
  (send (send _ ${#{RETURN_NEW_ARRAY_WHEN_ARGS}} {int lvar ivar cvar gvar}) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
57
57
  (send (block (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY} }) ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
@@ -30,14 +30,14 @@ module RuboCop
30
30
  '`%<compare_method>s { |%<var_a>s, %<var_b>s| %<str_a>s ' \
31
31
  '<=> %<str_b>s }`.'
32
32
 
33
- def_node_matcher :compare?, <<-PATTERN
33
+ def_node_matcher :compare?, <<~PATTERN
34
34
  (block
35
35
  $(send _ {:sort :min :max})
36
36
  (args (arg $_a) (arg $_b))
37
37
  $send)
38
38
  PATTERN
39
39
 
40
- def_node_matcher :replaceable_body?, <<-PATTERN
40
+ def_node_matcher :replaceable_body?, <<~PATTERN
41
41
  (send
42
42
  (send (lvar %1) $_method $...)
43
43
  :<=>
@@ -42,7 +42,7 @@ module RuboCop
42
42
 
43
43
  MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
44
44
 
45
- def_node_matcher :count_candidate?, <<-PATTERN
45
+ def_node_matcher :count_candidate?, <<~PATTERN
46
46
  {
47
47
  (send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
48
48
  (send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.5, `String#delete_prefix` has been added.
7
+ #
8
+ # This cop identifies places where `gsub(/\Aprefix/, '')`
9
+ # can be replaced by `delete_prefix('prefix')`.
10
+ #
11
+ # The `delete_prefix('prefix')` method is faster than
12
+ # `gsub(/\Aprefix/, '')`.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # str.gsub(/\Aprefix/, '')
18
+ # str.gsub!(/\Aprefix/, '')
19
+ # str.gsub(/^prefix/, '')
20
+ # str.gsub!(/^prefix/, '')
21
+ #
22
+ # # good
23
+ # str.delete_prefix('prefix')
24
+ # str.delete_prefix!('prefix')
25
+ #
26
+ class DeletePrefix < Cop
27
+ extend TargetRubyVersion
28
+ include RegexpMetacharacter
29
+
30
+ minimum_target_ruby_version 2.5
31
+
32
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
33
+
34
+ PREFERRED_METHODS = {
35
+ gsub: :delete_prefix,
36
+ gsub!: :delete_prefix!
37
+ }.freeze
38
+
39
+ def_node_matcher :gsub_method?, <<~PATTERN
40
+ (send $!nil? ${:gsub :gsub!} (regexp (str $#literal_at_start?) (regopt)) (str $_))
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ gsub_method?(node) do |_, bad_method, _, replace_string|
45
+ return unless replace_string.blank?
46
+
47
+ good_method = PREFERRED_METHODS[bad_method]
48
+
49
+ message = format(MSG, current: bad_method, prefer: good_method)
50
+
51
+ add_offense(node, location: :selector, message: message)
52
+ end
53
+ end
54
+
55
+ def autocorrect(node)
56
+ gsub_method?(node) do |receiver, bad_method, regexp_str, _|
57
+ lambda do |corrector|
58
+ good_method = PREFERRED_METHODS[bad_method]
59
+ regexp_str = drop_start_metacharacter(regexp_str)
60
+ regexp_str = interpret_string_escapes(regexp_str)
61
+ string_literal = to_string_literal(regexp_str)
62
+
63
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
64
+
65
+ corrector.replace(node, new_code)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.5, `String#delete_suffix` has been added.
7
+ #
8
+ # This cop identifies places where `gsub(/suffix\z/, '')`
9
+ # can be replaced by `delete_suffix('suffix')`.
10
+ #
11
+ # The `delete_suffix('suffix')` method is faster than
12
+ # `gsub(/suffix\z/, '')`.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # str.gsub(/suffix\z/, '')
18
+ # str.gsub!(/suffix\z/, '')
19
+ # str.gsub(/suffix$/, '')
20
+ # str.gsub!(/suffix$/, '')
21
+ #
22
+ # # good
23
+ # str.delete_suffix('suffix')
24
+ # str.delete_suffix!('suffix')
25
+ #
26
+ class DeleteSuffix < Cop
27
+ extend TargetRubyVersion
28
+ include RegexpMetacharacter
29
+
30
+ minimum_target_ruby_version 2.5
31
+
32
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
33
+
34
+ PREFERRED_METHODS = {
35
+ gsub: :delete_suffix,
36
+ gsub!: :delete_suffix!
37
+ }.freeze
38
+
39
+ def_node_matcher :gsub_method?, <<~PATTERN
40
+ (send $!nil? ${:gsub :gsub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
41
+ PATTERN
42
+
43
+ def on_send(node)
44
+ gsub_method?(node) do |_, bad_method, _, replace_string|
45
+ return unless replace_string.blank?
46
+
47
+ good_method = PREFERRED_METHODS[bad_method]
48
+
49
+ message = format(MSG, current: bad_method, prefer: good_method)
50
+
51
+ add_offense(node, location: :selector, message: message)
52
+ end
53
+ end
54
+
55
+ def autocorrect(node)
56
+ gsub_method?(node) do |receiver, bad_method, regexp_str, _|
57
+ lambda do |corrector|
58
+ good_method = PREFERRED_METHODS[bad_method]
59
+ regexp_str = drop_end_metacharacter(regexp_str)
60
+ regexp_str = interpret_string_escapes(regexp_str)
61
+ string_literal = to_string_literal(regexp_str)
62
+
63
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
64
+
65
+ corrector.replace(node, new_code)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -28,7 +28,7 @@ module RuboCop
28
28
  REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
29
29
  '`%<first_method>s.%<second_method>s`.'
30
30
 
31
- def_node_matcher :detect_candidate?, <<-PATTERN
31
+ def_node_matcher :detect_candidate?, <<~PATTERN
32
32
  {
33
33
  (send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
34
34
  (send $(send _ {:select :find_all} ...) ${:first :last} $...)
@@ -75,13 +75,13 @@ module RuboCop
75
75
  cop_config['IncludeActiveSupportAliases']
76
76
  end
77
77
 
78
- def_node_matcher :two_start_end_with_calls, <<-PATTERN
78
+ def_node_matcher :two_start_end_with_calls, <<~PATTERN
79
79
  (or
80
80
  (send $_recv [{:start_with? :end_with?} $_method] $...)
81
81
  (send _recv _method $...))
82
82
  PATTERN
83
83
 
84
- def_node_matcher :check_with_active_support_aliases, <<-PATTERN
84
+ def_node_matcher :check_with_active_support_aliases, <<~PATTERN
85
85
  (or
86
86
  (send $_recv
87
87
  [{:start_with? :starts_with? :end_with? :ends_with?} $_method]
@@ -15,26 +15,27 @@ module RuboCop
15
15
  # 'abc'.match(/bc\Z/)
16
16
  # /bc\Z/.match('abc')
17
17
  #
18
+ # 'abc'.match?(/bc$/)
19
+ # /bc$/.match?('abc')
20
+ # 'abc' =~ /bc$/
21
+ # /bc$/ =~ 'abc'
22
+ # 'abc'.match(/bc$/)
23
+ # /bc$/.match('abc')
24
+ #
18
25
  # # good
19
26
  # 'abc'.end_with?('bc')
20
27
  class EndWith < Cop
28
+ include RegexpMetacharacter
29
+
21
30
  MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
22
31
  'the end of the string.'
23
- SINGLE_QUOTE = "'"
24
32
 
25
- def_node_matcher :redundant_regex?, <<-PATTERN
33
+ def_node_matcher :redundant_regex?, <<~PATTERN
26
34
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
27
35
  (send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
28
36
  (match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
29
37
  PATTERN
30
38
 
31
- def literal_at_end?(regex_str)
32
- # is this regexp 'literal' in the sense of only matching literal
33
- # chars, rather than using metachars like . and * and so on?
34
- # also, is it anchored at the end of the string?
35
- regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
36
- end
37
-
38
39
  def on_send(node)
39
40
  return unless redundant_regex?(node)
40
41
 
@@ -45,7 +46,7 @@ module RuboCop
45
46
  def autocorrect(node)
46
47
  redundant_regex?(node) do |receiver, regex_str|
47
48
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
48
- regex_str = regex_str[0..-3] # drop \Z anchor
49
+ regex_str = drop_end_metacharacter(regex_str)
49
50
  regex_str = interpret_string_escapes(regex_str)
50
51
 
51
52
  lambda do |corrector|
@@ -48,7 +48,7 @@ module RuboCop
48
48
  class FixedSize < Cop
49
49
  MSG = 'Do not compute the size of statically sized objects.'
50
50
 
51
- def_node_matcher :counter, <<-MATCHER
51
+ def_node_matcher :counter, <<~MATCHER
52
52
  (send ${array hash str sym} {:count :length :size} $...)
53
53
  MATCHER
54
54
 
@@ -22,7 +22,7 @@ module RuboCop
22
22
  'and `flatten` can be used to flatten ' \
23
23
  'multiple levels.'
24
24
 
25
- def_node_matcher :flat_map_candidate?, <<-PATTERN
25
+ def_node_matcher :flat_map_candidate?, <<~PATTERN
26
26
  (send
27
27
  {
28
28
  (block $(send _ ${:collect :map}) ...)
@@ -37,7 +37,7 @@ module RuboCop
37
37
  # h = { a: 1, b: 2 }; h.value?(nil)
38
38
  #
39
39
  class InefficientHashSearch < Cop
40
- def_node_matcher :inefficient_include?, <<-PATTERN
40
+ def_node_matcher :inefficient_include?, <<~PATTERN
41
41
  (send (send $_ {:keys :values}) :include? _)
42
42
  PATTERN
43
43
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  MSG = 'Consider using `Struct` over `OpenStruct` ' \
32
32
  'to optimize the performance.'
33
33
 
34
- def_node_matcher :open_struct, <<-PATTERN
34
+ def_node_matcher :open_struct, <<~PATTERN
35
35
  (send (const {nil? cbase} :OpenStruct) :new ...)
36
36
  PATTERN
37
37
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  # Right now, we only detect direct calls on a Range literal
32
32
  # (We don't even catch it if the Range is in double parens)
33
33
 
34
- def_node_matcher :range_include, <<-PATTERN
34
+ def_node_matcher :range_include, <<~PATTERN
35
35
  (send {irange erange (begin {irange erange})} :include? ...)
36
36
  PATTERN
37
37
 
@@ -29,16 +29,16 @@ module RuboCop
29
29
  CLOSE_PAREN = ')'
30
30
  SPACE = ' '
31
31
 
32
- def_node_matcher :blockarg_def, <<-PATTERN
32
+ def_node_matcher :blockarg_def, <<~PATTERN
33
33
  {(def _ (args ... (blockarg $_)) $_)
34
34
  (defs _ _ (args ... (blockarg $_)) $_)}
35
35
  PATTERN
36
36
 
37
- def_node_search :blockarg_calls, <<-PATTERN
37
+ def_node_search :blockarg_calls, <<~PATTERN
38
38
  (send (lvar %1) :call ...)
39
39
  PATTERN
40
40
 
41
- def_node_search :blockarg_assigned?, <<-PATTERN
41
+ def_node_search :blockarg_assigned?, <<~PATTERN
42
42
  (lvasgn %1 ...)
43
43
  PATTERN
44
44
 
@@ -23,12 +23,12 @@ module RuboCop
23
23
 
24
24
  # 'match' is a fairly generic name, so we don't flag it unless we see
25
25
  # a string or regexp literal on one side or the other
26
- def_node_matcher :match_call?, <<-PATTERN
26
+ def_node_matcher :match_call?, <<~PATTERN
27
27
  {(send {str regexp} :match _)
28
28
  (send !nil? :match {str regexp})}
29
29
  PATTERN
30
30
 
31
- def_node_matcher :only_truthiness_matters?, <<-PATTERN
31
+ def_node_matcher :only_truthiness_matters?, <<~PATTERN
32
32
  ^({if while until case while_post until_post} equal?(%0) ...)
33
33
  PATTERN
34
34
 
@@ -5,11 +5,25 @@ module RuboCop
5
5
  module Performance
6
6
  # This cop identifies places where `Hash#merge!` can be replaced by
7
7
  # `Hash#[]=`.
8
+ # You can set the maximum number of key-value pairs to consider
9
+ # an offense with `MaxKeyValuePairs`.
8
10
  #
9
11
  # @example
12
+ # # bad
10
13
  # hash.merge!(a: 1)
11
14
  # hash.merge!({'key' => 'value'})
15
+ #
16
+ # # good
17
+ # hash[:a] = 1
18
+ # hash['key'] = 'value'
19
+ #
20
+ # @example MaxKeyValuePairs: 2 (default)
21
+ # # bad
12
22
  # hash.merge!(a: 1, b: 2)
23
+ #
24
+ # # good
25
+ # hash[:a] = 1
26
+ # hash[:b] = 2
13
27
  class RedundantMerge < Cop
14
28
  AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
15
29
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
@@ -20,11 +34,11 @@ module RuboCop
20
34
  %<leading_space>send
21
35
  RUBY
22
36
 
23
- def_node_matcher :redundant_merge_candidate, <<-PATTERN
37
+ def_node_matcher :redundant_merge_candidate, <<~PATTERN
24
38
  (send $!nil? :merge! [(hash $...) !kwsplat_type?])
25
39
  PATTERN
26
40
 
27
- def_node_matcher :modifier_flow_control?, <<-PATTERN
41
+ def_node_matcher :modifier_flow_control?, <<~PATTERN
28
42
  [{if while until} modifier_form?]
29
43
  PATTERN
30
44
 
@@ -168,13 +182,11 @@ module RuboCop
168
182
  end
169
183
 
170
184
  def unwind(receiver)
171
- while receiver.respond_to?(:send_type?) && receiver.send_type?
172
- receiver, = *receiver
173
- end
185
+ receiver, = *receiver while receiver.respond_to?(:send_type?) && receiver.send_type?
174
186
  receiver
175
187
  end
176
188
 
177
- def_node_matcher :each_with_object_node, <<-PATTERN
189
+ def_node_matcher :each_with_object_node, <<~PATTERN
178
190
  (block (send _ :each_with_object _) (args _ $_) ...)
179
191
  PATTERN
180
192
  end
@@ -73,32 +73,28 @@ module RuboCop
73
73
  # end
74
74
  # end
75
75
  class RegexpMatch < Cop
76
- extend TargetRubyVersion
77
-
78
- minimum_target_ruby_version 2.4
79
-
80
76
  # Constants are included in this list because it is unlikely that
81
77
  # someone will store `nil` as a constant and then use it for comparison
82
78
  TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
83
79
  MSG = 'Use `match?` instead of `%<current>s` when `MatchData` ' \
84
80
  'is not used.'
85
81
 
86
- def_node_matcher :match_method?, <<-PATTERN
82
+ def_node_matcher :match_method?, <<~PATTERN
87
83
  {
88
84
  (send _recv :match {regexp str sym})
89
85
  (send {regexp str sym} :match _)
90
86
  }
91
87
  PATTERN
92
88
 
93
- def_node_matcher :match_with_int_arg_method?, <<-PATTERN
89
+ def_node_matcher :match_with_int_arg_method?, <<~PATTERN
94
90
  (send _recv :match _ (int ...))
95
91
  PATTERN
96
92
 
97
- def_node_matcher :match_operator?, <<-PATTERN
93
+ def_node_matcher :match_operator?, <<~PATTERN
98
94
  (send !nil? {:=~ :!~} !nil?)
99
95
  PATTERN
100
96
 
101
- def_node_matcher :match_threequals?, <<-PATTERN
97
+ def_node_matcher :match_threequals?, <<~PATTERN
102
98
  (send (regexp (str _) {(regopt) (regopt _)}) :=== !nil?)
103
99
  PATTERN
104
100
 
@@ -109,7 +105,7 @@ module RuboCop
109
105
  regexp.to_regexp.named_captures.empty?
110
106
  end
111
107
 
112
- MATCH_NODE_PATTERN = <<-PATTERN
108
+ MATCH_NODE_PATTERN = <<~PATTERN
113
109
  {
114
110
  #match_method?
115
111
  #match_with_int_arg_method?
@@ -122,7 +118,7 @@ module RuboCop
122
118
  def_node_matcher :match_node?, MATCH_NODE_PATTERN
123
119
  def_node_search :search_match_nodes, MATCH_NODE_PATTERN
124
120
 
125
- def_node_search :last_matches, <<-PATTERN
121
+ def_node_search :last_matches, <<~PATTERN
126
122
  {
127
123
  (send (const nil? :Regexp) :last_match)
128
124
  (send (const nil? :Regexp) :last_match _)
@@ -256,6 +252,13 @@ module RuboCop
256
252
  def correct_operator(corrector, recv, arg, oper = nil)
257
253
  op_range = correction_range(recv, arg)
258
254
 
255
+ replace_with_match_predicate_method(corrector, recv, arg, op_range)
256
+
257
+ corrector.insert_after(arg.loc.expression, ')') unless op_range.source.end_with?('(')
258
+ corrector.insert_before(recv.loc.expression, '!') if oper == :!~
259
+ end
260
+
261
+ def replace_with_match_predicate_method(corrector, recv, arg, op_range)
259
262
  if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
260
263
  corrector.replace(op_range, '.match?(')
261
264
  elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
@@ -264,9 +267,6 @@ module RuboCop
264
267
  else
265
268
  corrector.replace(op_range, '&.match?(')
266
269
  end
267
-
268
- corrector.insert_after(arg.loc.expression, ')')
269
- corrector.insert_before(recv.loc.expression, '!') if oper == :!~
270
270
  end
271
271
 
272
272
  def swap_receiver_and_arg(corrector, recv, arg)
@@ -18,7 +18,7 @@ module RuboCop
18
18
  MSG = 'Use `reverse_each` instead of `reverse.each`.'
19
19
  UNDERSCORE = '_'
20
20
 
21
- def_node_matcher :reverse_each?, <<-MATCHER
21
+ def_node_matcher :reverse_each?, <<~MATCHER
22
22
  (send $(send _ :reverse) :each)
23
23
  MATCHER
24
24
 
@@ -34,7 +34,8 @@ module RuboCop
34
34
  end
35
35
 
36
36
  def autocorrect(node)
37
- ->(corrector) { corrector.replace(node.loc.dot, UNDERSCORE) }
37
+ range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
+ ->(corrector) { corrector.replace(range, UNDERSCORE) }
38
39
  end
39
40
  end
40
41
  end
@@ -15,29 +15,27 @@ module RuboCop
15
15
  # 'abc'.match(/\Aab/)
16
16
  # /\Aab/.match('abc')
17
17
  #
18
+ # 'abc'.match?(/^ab/)
19
+ # /^ab/.match?('abc')
20
+ # 'abc' =~ /^ab/
21
+ # /^ab/ =~ 'abc'
22
+ # 'abc'.match(/^ab/)
23
+ # /^ab/.match('abc')
24
+ #
18
25
  # # good
19
26
  # 'abc'.start_with?('ab')
20
27
  class StartWith < Cop
28
+ include RegexpMetacharacter
29
+
21
30
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
22
31
  'the beginning of the string.'
23
- SINGLE_QUOTE = "'"
24
32
 
25
- def_node_matcher :redundant_regex?, <<-PATTERN
33
+ def_node_matcher :redundant_regex?, <<~PATTERN
26
34
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
27
35
  (send (regexp (str $#literal_at_start?) (regopt)) {:match :match?} $_)
28
36
  (match-with-lvasgn (regexp (str $#literal_at_start?) (regopt)) $_)}
29
37
  PATTERN
30
38
 
31
- def literal_at_start?(regex_str)
32
- # is this regexp 'literal' in the sense of only matching literal
33
- # chars, rather than using metachars like `.` and `*` and so on?
34
- # also, is it anchored at the start of the string?
35
- # (tricky: \s, \d, and so on are metacharacters, but other characters
36
- # escaped with a slash are just literals. LITERAL_REGEX takes all
37
- # that into account.)
38
- regex_str =~ /\A\\A(?:#{LITERAL_REGEX})+\z/
39
- end
40
-
41
39
  def on_send(node)
42
40
  return unless redundant_regex?(node)
43
41
 
@@ -48,7 +46,7 @@ module RuboCop
48
46
  def autocorrect(node)
49
47
  redundant_regex?(node) do |receiver, regex_str|
50
48
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
51
- regex_str = regex_str[2..-1] # drop \A anchor
49
+ regex_str = drop_start_metacharacter(regex_str)
52
50
  regex_str = interpret_string_escapes(regex_str)
53
51
 
54
52
  lambda do |corrector|
@@ -26,9 +26,8 @@ module RuboCop
26
26
  DELETE = 'delete'
27
27
  TR = 'tr'
28
28
  BANG = '!'
29
- SINGLE_QUOTE = "'"
30
29
 
31
- def_node_matcher :string_replacement?, <<-PATTERN
30
+ def_node_matcher :string_replacement?, <<~PATTERN
32
31
  (send _ {:gsub :gsub!}
33
32
  ${regexp str (send (const nil? :Regexp) {:new :compile} _)}
34
33
  $str)
@@ -48,9 +47,7 @@ module RuboCop
48
47
  first_source, = first_source(first_param)
49
48
  second_source, = *second_param
50
49
 
51
- unless first_param.str_type?
52
- first_source = interpret_string_escapes(first_source)
53
- end
50
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
54
51
 
55
52
  replacement_method =
56
53
  replacement_method(node, first_source, second_source)
@@ -67,9 +64,7 @@ module RuboCop
67
64
  to_string_literal(first))
68
65
  end
69
66
 
70
- if second.empty? && first.length == 1
71
- remove_second_param(corrector, node, first_param)
72
- end
67
+ remove_second_param(corrector, node, first_param) if second.empty? && first.length == 1
73
68
  end
74
69
  end
75
70
 
@@ -99,9 +94,7 @@ module RuboCop
99
94
 
100
95
  def offense(node, first_param, second_param)
101
96
  first_source, = first_source(first_param)
102
- unless first_param.str_type?
103
- first_source = interpret_string_escapes(first_source)
104
- end
97
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
105
98
  second_source, = *second_param
106
99
  message = message(node, first_source, second_source)
107
100
 
@@ -61,7 +61,7 @@ module RuboCop
61
61
  map_or_collect: map_or_collect.method_name)
62
62
  end
63
63
 
64
- def_node_matcher :times_map_call, <<-PATTERN
64
+ def_node_matcher :times_map_call, <<~PATTERN
65
65
  {(block $(send (send $!nil? :times) {:map :collect}) ...)
66
66
  $(send (send $!nil? :times) {:map :collect} (block_pass ...))}
67
67
  PATTERN
@@ -24,17 +24,13 @@ module RuboCop
24
24
  # +'something'
25
25
  # +''
26
26
  class UnfreezeString < Cop
27
- extend TargetRubyVersion
28
-
29
- minimum_target_ruby_version 2.3
30
-
31
27
  MSG = 'Use unary plus to get an unfrozen string literal.'
32
28
 
33
- def_node_matcher :dup_string?, <<-PATTERN
29
+ def_node_matcher :dup_string?, <<~PATTERN
34
30
  (send {str dstr} :dup)
35
31
  PATTERN
36
32
 
37
- def_node_matcher :string_new?, <<-PATTERN
33
+ def_node_matcher :string_new?, <<~PATTERN
38
34
  {
39
35
  (send (const nil? :String) :new {str dstr})
40
36
  (send (const nil? :String) :new)
@@ -17,7 +17,7 @@ module RuboCop
17
17
  MSG = 'Use `%<double_colon>sURI::DEFAULT_PARSER` instead of ' \
18
18
  '`%<double_colon>sURI::Parser.new`.'
19
19
 
20
- def_node_matcher :uri_parser_new?, <<-PATTERN
20
+ def_node_matcher :uri_parser_new?, <<~PATTERN
21
21
  (send
22
22
  (const
23
23
  (const ${nil? cbase} :URI) :Parser) :new)
@@ -1,10 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'mixin/regexp_metacharacter'
4
+
5
+ require_relative 'performance/bind_call'
3
6
  require_relative 'performance/caller'
4
7
  require_relative 'performance/case_when_splat'
5
8
  require_relative 'performance/casecmp'
6
9
  require_relative 'performance/compare_with_block'
7
10
  require_relative 'performance/count'
11
+ require_relative 'performance/delete_prefix'
12
+ require_relative 'performance/delete_suffix'
8
13
  require_relative 'performance/detect'
9
14
  require_relative 'performance/double_start_end_with'
10
15
  require_relative 'performance/end_with'
@@ -8,7 +8,7 @@ module RuboCop
8
8
  def self.defaults!
9
9
  path = CONFIG_DEFAULT.to_s
10
10
  hash = ConfigLoader.send(:load_yaml_configuration, path)
11
- config = Config.new(hash, path)
11
+ config = Config.new(hash, path).tap(&:make_excludes_absolute)
12
12
  puts "configuration from #{path}" if ConfigLoader.debug?
13
13
  config = ConfigLoader.merge_with_default(config, path)
14
14
  ConfigLoader.instance_variable_set(:@default_configuration, config)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Performance
5
5
  module Version
6
- STRING = '1.5.2'
6
+ STRING = '1.6.0'
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: 1.5.2
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-12-25 00:00:00.000000000 Z
13
+ date: 2020-05-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -40,9 +40,9 @@ dependencies:
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0'
43
- description: |2
44
- A collection of RuboCop cops to check for performance optimizations
45
- in Ruby code.
43
+ description: |
44
+ A collection of RuboCop cops to check for performance optimizations
45
+ in Ruby code.
46
46
  email: rubocop@googlegroups.com
47
47
  executables: []
48
48
  extensions: []
@@ -54,12 +54,16 @@ files:
54
54
  - README.md
55
55
  - config/default.yml
56
56
  - lib/rubocop-performance.rb
57
+ - lib/rubocop/cop/mixin/regexp_metacharacter.rb
58
+ - lib/rubocop/cop/performance/bind_call.rb
57
59
  - lib/rubocop/cop/performance/caller.rb
58
60
  - lib/rubocop/cop/performance/case_when_splat.rb
59
61
  - lib/rubocop/cop/performance/casecmp.rb
60
62
  - lib/rubocop/cop/performance/chain_array_allocation.rb
61
63
  - lib/rubocop/cop/performance/compare_with_block.rb
62
64
  - lib/rubocop/cop/performance/count.rb
65
+ - lib/rubocop/cop/performance/delete_prefix.rb
66
+ - lib/rubocop/cop/performance/delete_suffix.rb
63
67
  - lib/rubocop/cop/performance/detect.rb
64
68
  - lib/rubocop/cop/performance/double_start_end_with.rb
65
69
  - lib/rubocop/cop/performance/end_with.rb
@@ -87,10 +91,10 @@ homepage: https://github.com/rubocop-hq/rubocop-performance
87
91
  licenses:
88
92
  - MIT
89
93
  metadata:
90
- homepage_uri: https://docs.rubocop.org/projects/performance
94
+ homepage_uri: https://docs.rubocop.org/projects/performance/
91
95
  changelog_uri: https://github.com/rubocop-hq/rubocop-performance/blob/master/CHANGELOG.md
92
96
  source_code_uri: https://github.com/rubocop-hq/rubocop-performance/
93
- documentation_uri: https://docs.rubocop.org/projects/performance
97
+ documentation_uri: https://docs.rubocop.org/projects/performance/
94
98
  bug_tracker_uri: https://github.com/rubocop-hq/rubocop-performance/issues
95
99
  post_install_message:
96
100
  rdoc_options: []
@@ -100,14 +104,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
104
  requirements:
101
105
  - - ">="
102
106
  - !ruby/object:Gem::Version
103
- version: 2.3.0
107
+ version: 2.4.0
104
108
  required_rubygems_version: !ruby/object:Gem::Requirement
105
109
  requirements:
106
110
  - - ">="
107
111
  - !ruby/object:Gem::Version
108
112
  version: '0'
109
113
  requirements: []
110
- rubygems_version: 3.1.2
114
+ rubyforge_project:
115
+ rubygems_version: 2.6.14.4
111
116
  signing_key:
112
117
  specification_version: 4
113
118
  summary: Automatic performance checking tool for Ruby code.