rubocop-performance 1.6.0 → 1.8.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 +5 -5
- data/README.md +1 -1
- data/config/default.yml +77 -10
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +39 -4
- data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
- data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
- data/lib/rubocop/cop/performance/bind_call.rb +8 -18
- data/lib/rubocop/cop/performance/caller.rb +3 -2
- data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
- data/lib/rubocop/cop/performance/casecmp.rb +12 -20
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
- data/lib/rubocop/cop/performance/count.rb +13 -16
- data/lib/rubocop/cop/performance/delete_prefix.rb +43 -28
- data/lib/rubocop/cop/performance/delete_suffix.rb +43 -28
- data/lib/rubocop/cop/performance/detect.rb +63 -31
- data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
- data/lib/rubocop/cop/performance/end_with.rb +29 -17
- data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
- data/lib/rubocop/cop/performance/flat_map.rb +20 -22
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +13 -14
- data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
- data/lib/rubocop/cop/performance/open_struct.rb +2 -2
- data/lib/rubocop/cop/performance/range_include.rb +14 -11
- data/lib/rubocop/cop/performance/redundant_block_call.rb +11 -6
- data/lib/rubocop/cop/performance/redundant_match.rb +11 -6
- data/lib/rubocop/cop/performance/redundant_merge.rb +18 -17
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
- data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
- data/lib/rubocop/cop/performance/reverse_each.rb +9 -5
- data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
- data/lib/rubocop/cop/performance/size.rb +41 -43
- data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
- data/lib/rubocop/cop/performance/squeeze.rb +66 -0
- data/lib/rubocop/cop/performance/start_with.rb +29 -17
- data/lib/rubocop/cop/performance/string_include.rb +55 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +23 -27
- data/lib/rubocop/cop/performance/sum.rb +134 -0
- data/lib/rubocop/cop/performance/times_map.rb +11 -18
- data/lib/rubocop/cop/performance/unfreeze_string.rb +2 -2
- data/lib/rubocop/cop/performance/uri_default_parser.rb +6 -12
- data/lib/rubocop/cop/performance_cops.rb +12 -0
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +33 -8
| @@ -72,7 +72,9 @@ module RuboCop | |
| 72 72 | 
             
                  #       do_something($~)
         | 
| 73 73 | 
             
                  #     end
         | 
| 74 74 | 
             
                  #   end
         | 
| 75 | 
            -
                  class RegexpMatch <  | 
| 75 | 
            +
                  class RegexpMatch < Base
         | 
| 76 | 
            +
                    extend AutoCorrector
         | 
| 77 | 
            +
             | 
| 76 78 | 
             
                    # Constants are included in this list because it is unlikely that
         | 
| 77 79 | 
             
                    # someone will store `nil` as a constant and then use it for comparison
         | 
| 78 80 | 
             
                    TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
         | 
| @@ -141,27 +143,28 @@ module RuboCop | |
| 141 143 | 
             
                      end
         | 
| 142 144 | 
             
                    end
         | 
| 143 145 |  | 
| 144 | 
            -
                    def autocorrect(node)
         | 
| 145 | 
            -
                      lambda do |corrector|
         | 
| 146 | 
            -
                        if match_method?(node) || match_with_int_arg_method?(node)
         | 
| 147 | 
            -
                          corrector.replace(node.loc.selector, 'match?')
         | 
| 148 | 
            -
                        elsif match_operator?(node) || match_threequals?(node)
         | 
| 149 | 
            -
                          recv, oper, arg = *node
         | 
| 150 | 
            -
                          correct_operator(corrector, recv, arg, oper)
         | 
| 151 | 
            -
                        elsif match_with_lvasgn?(node)
         | 
| 152 | 
            -
                          recv, arg = *node
         | 
| 153 | 
            -
                          correct_operator(corrector, recv, arg)
         | 
| 154 | 
            -
                        end
         | 
| 155 | 
            -
                      end
         | 
| 156 | 
            -
                    end
         | 
| 157 | 
            -
             | 
| 158 146 | 
             
                    private
         | 
| 159 147 |  | 
| 160 148 | 
             
                    def check_condition(cond)
         | 
| 161 149 | 
             
                      match_node?(cond) do
         | 
| 162 150 | 
             
                        return if last_match_used?(cond)
         | 
| 163 151 |  | 
| 164 | 
            -
                         | 
| 152 | 
            +
                        message = message(cond)
         | 
| 153 | 
            +
                        add_offense(cond, message: message) do |corrector|
         | 
| 154 | 
            +
                          autocorrect(corrector, cond)
         | 
| 155 | 
            +
                        end
         | 
| 156 | 
            +
                      end
         | 
| 157 | 
            +
                    end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                    def autocorrect(corrector, node)
         | 
| 160 | 
            +
                      if match_method?(node) || match_with_int_arg_method?(node)
         | 
| 161 | 
            +
                        corrector.replace(node.loc.selector, 'match?')
         | 
| 162 | 
            +
                      elsif match_operator?(node) || match_threequals?(node)
         | 
| 163 | 
            +
                        recv, oper, arg = *node
         | 
| 164 | 
            +
                        correct_operator(corrector, recv, arg, oper)
         | 
| 165 | 
            +
                      elsif match_with_lvasgn?(node)
         | 
| 166 | 
            +
                        recv, arg = *node
         | 
| 167 | 
            +
                        correct_operator(corrector, recv, arg)
         | 
| 165 168 | 
             
                      end
         | 
| 166 169 | 
             
                    end
         | 
| 167 170 |  | 
| @@ -231,10 +234,7 @@ module RuboCop | |
| 231 234 |  | 
| 232 235 | 
             
                    def scope_root(node)
         | 
| 233 236 | 
             
                      node.each_ancestor.find do |ancestor|
         | 
| 234 | 
            -
                        ancestor.def_type? ||
         | 
| 235 | 
            -
                          ancestor.defs_type? ||
         | 
| 236 | 
            -
                          ancestor.class_type? ||
         | 
| 237 | 
            -
                          ancestor.module_type?
         | 
| 237 | 
            +
                        ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type?
         | 
| 238 238 | 
             
                      end
         | 
| 239 239 | 
             
                    end
         | 
| 240 240 |  | 
| @@ -12,8 +12,9 @@ module RuboCop | |
| 12 12 | 
             
                  #
         | 
| 13 13 | 
             
                  #   # good
         | 
| 14 14 | 
             
                  #   [].reverse_each
         | 
| 15 | 
            -
                  class ReverseEach <  | 
| 15 | 
            +
                  class ReverseEach < Base
         | 
| 16 16 | 
             
                    include RangeHelp
         | 
| 17 | 
            +
                    extend AutoCorrector
         | 
| 17 18 |  | 
| 18 19 | 
             
                    MSG = 'Use `reverse_each` instead of `reverse.each`.'
         | 
| 19 20 | 
             
                    UNDERSCORE = '_'
         | 
| @@ -29,13 +30,16 @@ module RuboCop | |
| 29 30 |  | 
| 30 31 | 
             
                        range = range_between(location_of_reverse, end_location)
         | 
| 31 32 |  | 
| 32 | 
            -
                        add_offense( | 
| 33 | 
            +
                        add_offense(range) do |corrector|
         | 
| 34 | 
            +
                          corrector.replace(replacement_range(node), UNDERSCORE)
         | 
| 35 | 
            +
                        end
         | 
| 33 36 | 
             
                      end
         | 
| 34 37 | 
             
                    end
         | 
| 35 38 |  | 
| 36 | 
            -
                     | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            +
                    private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def replacement_range(node)
         | 
| 42 | 
            +
                      range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
         | 
| 39 43 | 
             
                    end
         | 
| 40 44 | 
             
                  end
         | 
| 41 45 | 
             
                end
         | 
| @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop identifies places where `reverse.first(n)` and `reverse.first`
         | 
| 7 | 
            +
                  # can be replaced by `last(n).reverse` and `last`.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # @example
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  #   # bad
         | 
| 12 | 
            +
                  #   array.reverse.first(5)
         | 
| 13 | 
            +
                  #   array.reverse.first
         | 
| 14 | 
            +
                  #
         | 
| 15 | 
            +
                  #   # good
         | 
| 16 | 
            +
                  #   array.last(5).reverse
         | 
| 17 | 
            +
                  #   array.last
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  class ReverseFirst < Base
         | 
| 20 | 
            +
                    include RangeHelp
         | 
| 21 | 
            +
                    extend AutoCorrector
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def_node_matcher :reverse_first_candidate?, <<~PATTERN
         | 
| 26 | 
            +
                      (send $(send _ :reverse) :first (int _)?)
         | 
| 27 | 
            +
                    PATTERN
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def on_send(node)
         | 
| 30 | 
            +
                      reverse_first_candidate?(node) do |receiver|
         | 
| 31 | 
            +
                        range = correction_range(receiver, node)
         | 
| 32 | 
            +
                        message = build_message(node)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                        add_offense(range, message: message) do |corrector|
         | 
| 35 | 
            +
                          replacement = build_good_method(node)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                          corrector.replace(range, replacement)
         | 
| 38 | 
            +
                        end
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    private
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def correction_range(receiver, node)
         | 
| 45 | 
            +
                      range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def build_message(node)
         | 
| 49 | 
            +
                      good_method = build_good_method(node)
         | 
| 50 | 
            +
                      bad_method = build_bad_method(node)
         | 
| 51 | 
            +
                      format(MSG, good_method: good_method, bad_method: bad_method)
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    def build_good_method(node)
         | 
| 55 | 
            +
                      if node.arguments?
         | 
| 56 | 
            +
                        "last(#{node.arguments.first.source}).reverse"
         | 
| 57 | 
            +
                      else
         | 
| 58 | 
            +
                        'last'
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    def build_bad_method(node)
         | 
| 63 | 
            +
                      if node.arguments?
         | 
| 64 | 
            +
                        "reverse.first(#{node.arguments.first.source})"
         | 
| 65 | 
            +
                      else
         | 
| 66 | 
            +
                        'reverse.first'
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
            end
         | 
| @@ -9,67 +9,65 @@ module RuboCop | |
| 9 9 | 
             
                  # @example
         | 
| 10 10 | 
             
                  #   # bad
         | 
| 11 11 | 
             
                  #   [1, 2, 3].count
         | 
| 12 | 
            +
                  #   (1..3).to_a.count
         | 
| 13 | 
            +
                  #   Array[*1..3].count
         | 
| 14 | 
            +
                  #   Array(1..3).count
         | 
| 12 15 | 
             
                  #
         | 
| 13 16 | 
             
                  #   # bad
         | 
| 14 17 | 
             
                  #   {a: 1, b: 2, c: 3}.count
         | 
| 18 | 
            +
                  #   [[:foo, :bar], [1, 2]].to_h.count
         | 
| 19 | 
            +
                  #   Hash[*('a'..'z')].count
         | 
| 20 | 
            +
                  #   Hash(key: :value).count
         | 
| 15 21 | 
             
                  #
         | 
| 16 22 | 
             
                  #   # good
         | 
| 17 23 | 
             
                  #   [1, 2, 3].size
         | 
| 24 | 
            +
                  #   (1..3).to_a.size
         | 
| 25 | 
            +
                  #   Array[*1..3].size
         | 
| 26 | 
            +
                  #   Array(1..3).size
         | 
| 18 27 | 
             
                  #
         | 
| 19 28 | 
             
                  #   # good
         | 
| 20 29 | 
             
                  #   {a: 1, b: 2, c: 3}.size
         | 
| 30 | 
            +
                  #   [[:foo, :bar], [1, 2]].to_h.size
         | 
| 31 | 
            +
                  #   Hash[*('a'..'z')].size
         | 
| 32 | 
            +
                  #   Hash(key: :value).size
         | 
| 21 33 | 
             
                  #
         | 
| 22 34 | 
             
                  #   # good
         | 
| 23 35 | 
             
                  #   [1, 2, 3].count { |e| e > 2 }
         | 
| 24 36 | 
             
                  # TODO: Add advanced detection of variables that could
         | 
| 25 37 | 
             
                  # have been assigned to an array or a hash.
         | 
| 26 | 
            -
                  class Size <  | 
| 27 | 
            -
                     | 
| 28 | 
            -
             | 
| 29 | 
            -
                    def on_send(node)
         | 
| 30 | 
            -
                      return unless eligible_node?(node)
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                      add_offense(node, location: :selector)
         | 
| 33 | 
            -
                    end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                    def autocorrect(node)
         | 
| 36 | 
            -
                      ->(corrector) { corrector.replace(node.loc.selector, 'size') }
         | 
| 37 | 
            -
                    end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                    private
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                    def eligible_node?(node)
         | 
| 42 | 
            -
                      return false unless node.method?(:count) && !node.arguments?
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                      eligible_receiver?(node.receiver) && !allowed_parent?(node.parent)
         | 
| 45 | 
            -
                    end
         | 
| 38 | 
            +
                  class Size < Base
         | 
| 39 | 
            +
                    extend AutoCorrector
         | 
| 46 40 |  | 
| 47 | 
            -
                     | 
| 48 | 
            -
                      return false unless node
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                      array?(node) || hash?(node)
         | 
| 51 | 
            -
                    end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                    def allowed_parent?(node)
         | 
| 54 | 
            -
                      node&.block_type?
         | 
| 55 | 
            -
                    end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                    def array?(node)
         | 
| 58 | 
            -
                      return true if node.array_type?
         | 
| 59 | 
            -
                      return false unless node.send_type?
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                      _, constant = *node.receiver
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                      constant == :Array || node.method?(:to_a)
         | 
| 64 | 
            -
                    end
         | 
| 41 | 
            +
                    MSG = 'Use `size` instead of `count`.'
         | 
| 65 42 |  | 
| 66 | 
            -
                     | 
| 67 | 
            -
                       | 
| 68 | 
            -
             | 
| 43 | 
            +
                    def_node_matcher :array?, <<~PATTERN
         | 
| 44 | 
            +
                      {
         | 
| 45 | 
            +
                        [!nil? array_type?]
         | 
| 46 | 
            +
                        (send _ :to_a)
         | 
| 47 | 
            +
                        (send (const nil? :Array) :[] _)
         | 
| 48 | 
            +
                        (send nil? :Array _)
         | 
| 49 | 
            +
                      }
         | 
| 50 | 
            +
                    PATTERN
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def_node_matcher :hash?, <<~PATTERN
         | 
| 53 | 
            +
                      {
         | 
| 54 | 
            +
                        [!nil? hash_type?]
         | 
| 55 | 
            +
                        (send _ :to_h)
         | 
| 56 | 
            +
                        (send (const nil? :Hash) :[] _)
         | 
| 57 | 
            +
                        (send nil? :Hash _)
         | 
| 58 | 
            +
                      }
         | 
| 59 | 
            +
                    PATTERN
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def_node_matcher :count?, <<~PATTERN
         | 
| 62 | 
            +
                      (send {#array? #hash?} :count)
         | 
| 63 | 
            +
                    PATTERN
         | 
| 69 64 |  | 
| 70 | 
            -
             | 
| 65 | 
            +
                    def on_send(node)
         | 
| 66 | 
            +
                      return if node.parent&.block_type? || !count?(node)
         | 
| 71 67 |  | 
| 72 | 
            -
                       | 
| 68 | 
            +
                      add_offense(node.loc.selector) do |corrector|
         | 
| 69 | 
            +
                        corrector.replace(node.loc.selector, 'size')
         | 
| 70 | 
            +
                      end
         | 
| 73 71 | 
             
                    end
         | 
| 74 72 | 
             
                  end
         | 
| 75 73 | 
             
                end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop identifies places where `sort { |a, b| b <=> a }`
         | 
| 7 | 
            +
                  # can be replaced by a faster `sort.reverse`.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # @example
         | 
| 10 | 
            +
                  #   # bad
         | 
| 11 | 
            +
                  #   array.sort { |a, b| b <=> a }
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  #   # good
         | 
| 14 | 
            +
                  #   array.sort.reverse
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  class SortReverse < Base
         | 
| 17 | 
            +
                    include SortBlock
         | 
| 18 | 
            +
                    extend AutoCorrector
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    def on_block(node)
         | 
| 23 | 
            +
                      sort_with_block?(node) do |send, var_a, var_b, body|
         | 
| 24 | 
            +
                        replaceable_body?(body, var_b, var_a) do
         | 
| 25 | 
            +
                          range = sort_range(send, node)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                          add_offense(range, message: message(var_a, var_b)) do |corrector|
         | 
| 28 | 
            +
                            replacement = 'sort.reverse'
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                            corrector.replace(range, replacement)
         | 
| 31 | 
            +
                          end
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    private
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def message(var_a, var_b)
         | 
| 39 | 
            +
                      bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
         | 
| 40 | 
            +
                      format(MSG, bad_method: bad_method)
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop identifies places where `gsub(/a+/, 'a')` and `gsub!(/a+/, 'a')`
         | 
| 7 | 
            +
                  # can be replaced by `squeeze('a')` and `squeeze!('a')`.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # The `squeeze('a')` method is faster than `gsub(/a+/, 'a')`.
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  # @example
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  #   # bad
         | 
| 14 | 
            +
                  #   str.gsub(/a+/, 'a')
         | 
| 15 | 
            +
                  #   str.gsub!(/a+/, 'a')
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  #   # good
         | 
| 18 | 
            +
                  #   str.squeeze('a')
         | 
| 19 | 
            +
                  #   str.squeeze!('a')
         | 
| 20 | 
            +
                  #
         | 
| 21 | 
            +
                  class Squeeze < Base
         | 
| 22 | 
            +
                    extend AutoCorrector
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    PREFERRED_METHODS = {
         | 
| 27 | 
            +
                      gsub: :squeeze,
         | 
| 28 | 
            +
                      gsub!: :squeeze!
         | 
| 29 | 
            +
                    }.freeze
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def_node_matcher :squeeze_candidate?, <<~PATTERN
         | 
| 32 | 
            +
                      (send
         | 
| 33 | 
            +
                        $!nil? ${:gsub :gsub!}
         | 
| 34 | 
            +
                        (regexp
         | 
| 35 | 
            +
                          (str $#repeating_literal?)
         | 
| 36 | 
            +
                          (regopt))
         | 
| 37 | 
            +
                        (str $_))
         | 
| 38 | 
            +
                    PATTERN
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def on_send(node)
         | 
| 41 | 
            +
                      squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
         | 
| 42 | 
            +
                        regexp_str = regexp_str[0..-2] # delete '+' from the end
         | 
| 43 | 
            +
                        regexp_str = interpret_string_escapes(regexp_str)
         | 
| 44 | 
            +
                        return unless replace_str == regexp_str
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                        good_method = PREFERRED_METHODS[bad_method]
         | 
| 47 | 
            +
                        message = format(MSG, current: bad_method, prefer: good_method)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                        add_offense(node.loc.selector, message: message) do |corrector|
         | 
| 50 | 
            +
                          string_literal = to_string_literal(replace_str)
         | 
| 51 | 
            +
                          new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                          corrector.replace(node.source_range, new_code)
         | 
| 54 | 
            +
                        end
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    private
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def repeating_literal?(regex_str)
         | 
| 61 | 
            +
                      regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -3,8 +3,11 @@ | |
| 3 3 | 
             
            module RuboCop
         | 
| 4 4 | 
             
              module Cop
         | 
| 5 5 | 
             
                module Performance
         | 
| 6 | 
            -
                  # This cop identifies unnecessary use of a regex where
         | 
| 7 | 
            -
                  # | 
| 6 | 
            +
                  # This cop identifies unnecessary use of a regex where `String#start_with?` would suffice.
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # This cop has `SafeMultiline` configuration option that `true` by default because
         | 
| 9 | 
            +
                  # `^start` is unsafe as it will behave incompatible with `start_with?`
         | 
| 10 | 
            +
                  # for receiver is multiline string.
         | 
| 8 11 | 
             
                  #
         | 
| 9 12 | 
             
                  # @example
         | 
| 10 13 | 
             
                  #   # bad
         | 
| @@ -15,6 +18,12 @@ module RuboCop | |
| 15 18 | 
             
                  #   'abc'.match(/\Aab/)
         | 
| 16 19 | 
             
                  #   /\Aab/.match('abc')
         | 
| 17 20 | 
             
                  #
         | 
| 21 | 
            +
                  #   # good
         | 
| 22 | 
            +
                  #   'abc'.start_with?('ab')
         | 
| 23 | 
            +
                  #
         | 
| 24 | 
            +
                  # @example SafeMultiline: true (default)
         | 
| 25 | 
            +
                  #
         | 
| 26 | 
            +
                  #   # good
         | 
| 18 27 | 
             
                  #   'abc'.match?(/^ab/)
         | 
| 19 28 | 
             
                  #   /^ab/.match?('abc')
         | 
| 20 29 | 
             
                  #   'abc' =~ /^ab/
         | 
| @@ -22,10 +31,19 @@ module RuboCop | |
| 22 31 | 
             
                  #   'abc'.match(/^ab/)
         | 
| 23 32 | 
             
                  #   /^ab/.match('abc')
         | 
| 24 33 | 
             
                  #
         | 
| 25 | 
            -
                  # | 
| 26 | 
            -
                  # | 
| 27 | 
            -
                   | 
| 34 | 
            +
                  # @example SafeMultiline: false
         | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  #   # bad
         | 
| 37 | 
            +
                  #   'abc'.match?(/^ab/)
         | 
| 38 | 
            +
                  #   /^ab/.match?('abc')
         | 
| 39 | 
            +
                  #   'abc' =~ /^ab/
         | 
| 40 | 
            +
                  #   /^ab/ =~ 'abc'
         | 
| 41 | 
            +
                  #   'abc'.match(/^ab/)
         | 
| 42 | 
            +
                  #   /^ab/.match('abc')
         | 
| 43 | 
            +
                  #
         | 
| 44 | 
            +
                  class StartWith < Base
         | 
| 28 45 | 
             
                    include RegexpMetacharacter
         | 
| 46 | 
            +
                    extend AutoCorrector
         | 
| 29 47 |  | 
| 30 48 | 
             
                    MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
         | 
| 31 49 | 
             
                          'the beginning of the string.'
         | 
| @@ -37,25 +55,19 @@ module RuboCop | |
| 37 55 | 
             
                    PATTERN
         | 
| 38 56 |  | 
| 39 57 | 
             
                    def on_send(node)
         | 
| 40 | 
            -
                      return unless redundant_regex?(node)
         | 
| 58 | 
            +
                      return unless (receiver, regex_str = redundant_regex?(node))
         | 
| 41 59 |  | 
| 42 | 
            -
                      add_offense(node)
         | 
| 43 | 
            -
                    end
         | 
| 44 | 
            -
                    alias on_match_with_lvasgn on_send
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                    def autocorrect(node)
         | 
| 47 | 
            -
                      redundant_regex?(node) do |receiver, regex_str|
         | 
| 60 | 
            +
                      add_offense(node) do |corrector|
         | 
| 48 61 | 
             
                        receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
         | 
| 49 62 | 
             
                        regex_str = drop_start_metacharacter(regex_str)
         | 
| 50 63 | 
             
                        regex_str = interpret_string_escapes(regex_str)
         | 
| 51 64 |  | 
| 52 | 
            -
                         | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
                          corrector.replace(node.source_range, new_source)
         | 
| 56 | 
            -
                        end
         | 
| 65 | 
            +
                        new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                        corrector.replace(node.source_range, new_source)
         | 
| 57 68 | 
             
                      end
         | 
| 58 69 | 
             
                    end
         | 
| 70 | 
            +
                    alias on_match_with_lvasgn on_send
         | 
| 59 71 | 
             
                  end
         | 
| 60 72 | 
             
                end
         | 
| 61 73 | 
             
              end
         |