rubocop-performance 1.6.1 → 1.7.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.
- checksums.yaml +4 -4
- data/config/default.yml +50 -1
- data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
- data/lib/rubocop/cop/performance/ancestors_include.rb +45 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +43 -0
- data/lib/rubocop/cop/performance/io_readlines.rb +127 -0
- data/lib/rubocop/cop/performance/range_include.rb +9 -7
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +53 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +137 -0
- data/lib/rubocop/cop/performance/reverse_first.rb +78 -0
- data/lib/rubocop/cop/performance/size.rb +35 -37
- data/lib/rubocop/cop/performance/sort_reverse.rb +54 -0
- data/lib/rubocop/cop/performance/squeeze.rb +70 -0
- data/lib/rubocop/cop/performance/string_include.rb +57 -0
- data/lib/rubocop/cop/performance_cops.rb +10 -0
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +15 -5
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5be372d62d20424e05201d4b78bb06bf19fc707d845d6b799599ba72958f72b7
         | 
| 4 | 
            +
              data.tar.gz: 87c59d78e37de238add70195fa26c95d31b23eb3065695f443a1694d3acf9145
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d6caf119ca1a8b11d829ed555f3a0653a1087d3a3ee3be078460c9ecfb5a5fb236a7abf2c058f57647d4f74dffccc66aebcf4d330f5a6a5bd5deaa12a8fea480
         | 
| 7 | 
            +
              data.tar.gz: d8a6e634d4453300d24e5869bffa690457d07048b1e2978cc00dbc930c40e2dc80789a6faa95823e63414e5c579f8784cbe4f743607e2fd9598cf478aa1801ed
         | 
    
        data/config/default.yml
    CHANGED
    
    | @@ -1,5 +1,16 @@ | |
| 1 1 | 
             
            # This is the default configuration file.
         | 
| 2 2 |  | 
| 3 | 
            +
            Performance/AncestorsInclude:
         | 
| 4 | 
            +
              Description: 'Use `A <= B` instead of `A.ancestors.include?(B)`.'
         | 
| 5 | 
            +
              Reference: 'https://github.com/JuanitoFatas/fast-ruby#ancestorsinclude-vs--code'
         | 
| 6 | 
            +
              Enabled: 'pending'
         | 
| 7 | 
            +
              VersionAdded: '1.7'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Performance/BigDecimalWithNumericArgument:
         | 
| 10 | 
            +
              Description: 'Convert numeric argument to string before passing to BigDecimal.'
         | 
| 11 | 
            +
              Enabled: 'pending'
         | 
| 12 | 
            +
              VersionAdded: '1.7'
         | 
| 13 | 
            +
             | 
| 3 14 | 
             
            Performance/BindCall:
         | 
| 4 15 | 
             
              Description: 'Use `bind_call(obj, args, ...)` instead of `bind(obj).call(args, ...)`.'
         | 
| 5 16 | 
             
              Enabled: true
         | 
| @@ -131,6 +142,12 @@ Performance/InefficientHashSearch: | |
| 131 142 | 
             
              VersionAdded: '0.56'
         | 
| 132 143 | 
             
              Safe: false
         | 
| 133 144 |  | 
| 145 | 
            +
            Performance/IoReadlines:
         | 
| 146 | 
            +
              Description: 'Use `IO.each_line` (`IO#each_line`) instead of `IO.readlines` (`IO#readlines`).'
         | 
| 147 | 
            +
              Reference: 'https://docs.gitlab.com/ee/development/performance.html#reading-from-files-and-other-data-sources'
         | 
| 148 | 
            +
              Enabled: false
         | 
| 149 | 
            +
              VersionAdded: '1.7'
         | 
| 150 | 
            +
             | 
| 134 151 | 
             
            Performance/OpenStruct:
         | 
| 135 152 | 
             
              Description: 'Use `Struct` instead of `OpenStruct`.'
         | 
| 136 153 | 
             
              Enabled: false
         | 
| @@ -138,10 +155,11 @@ Performance/OpenStruct: | |
| 138 155 | 
             
              Safe: false
         | 
| 139 156 |  | 
| 140 157 | 
             
            Performance/RangeInclude:
         | 
| 141 | 
            -
              Description: 'Use `Range#cover?` instead of `Range#include | 
| 158 | 
            +
              Description: 'Use `Range#cover?` instead of `Range#include?` (or `Range#member?`).'
         | 
| 142 159 | 
             
              Reference: 'https://github.com/JuanitoFatas/fast-ruby#cover-vs-include-code'
         | 
| 143 160 | 
             
              Enabled: true
         | 
| 144 161 | 
             
              VersionAdded: '0.36'
         | 
| 162 | 
            +
              VersionChanged: '1.7'
         | 
| 145 163 | 
             
              Safe: false
         | 
| 146 164 |  | 
| 147 165 | 
             
            Performance/RedundantBlockCall:
         | 
| @@ -165,6 +183,16 @@ Performance/RedundantMerge: | |
| 165 183 | 
             
              # Max number of key-value pairs to consider an offense
         | 
| 166 184 | 
             
              MaxKeyValuePairs: 2
         | 
| 167 185 |  | 
| 186 | 
            +
            Performance/RedundantSortBlock:
         | 
| 187 | 
            +
              Description: 'Use `sort` instead of `sort { |a, b| a <=> b }`.'
         | 
| 188 | 
            +
              Enabled: 'pending'
         | 
| 189 | 
            +
              VersionAdded: '1.7'
         | 
| 190 | 
            +
             | 
| 191 | 
            +
            Performance/RedundantStringChars:
         | 
| 192 | 
            +
              Description: 'Checks for redundant `String#chars`.'
         | 
| 193 | 
            +
              Enabled: 'pending'
         | 
| 194 | 
            +
              VersionAdded: '1.7'
         | 
| 195 | 
            +
             | 
| 168 196 | 
             
            Performance/RegexpMatch:
         | 
| 169 197 | 
             
              Description: >-
         | 
| 170 198 | 
             
                              Use `match?` instead of `Regexp#match`, `String#match`, `Symbol#match`,
         | 
| @@ -179,6 +207,11 @@ Performance/ReverseEach: | |
| 179 207 | 
             
              Enabled: true
         | 
| 180 208 | 
             
              VersionAdded: '0.30'
         | 
| 181 209 |  | 
| 210 | 
            +
            Performance/ReverseFirst:
         | 
| 211 | 
            +
              Description: 'Use `last(n).reverse` instead of `reverse.first(n)`.'
         | 
| 212 | 
            +
              Enabled: 'pending'
         | 
| 213 | 
            +
              VersionAdded: '1.7'
         | 
| 214 | 
            +
             | 
| 182 215 | 
             
            Performance/Size:
         | 
| 183 216 | 
             
              Description: >-
         | 
| 184 217 | 
             
                              Use `size` instead of `count` for counting
         | 
| @@ -187,6 +220,17 @@ Performance/Size: | |
| 187 220 | 
             
              Enabled: true
         | 
| 188 221 | 
             
              VersionAdded: '0.30'
         | 
| 189 222 |  | 
| 223 | 
            +
            Performance/SortReverse:
         | 
| 224 | 
            +
              Description: 'Use `sort.reverse` instead of `sort { |a, b| b <=> a }`.'
         | 
| 225 | 
            +
              Enabled: 'pending'
         | 
| 226 | 
            +
              VersionAdded: '1.7'
         | 
| 227 | 
            +
             | 
| 228 | 
            +
            Performance/Squeeze:
         | 
| 229 | 
            +
              Description: "Use `squeeze('a')` instead of `gsub(/a+/, 'a')`."
         | 
| 230 | 
            +
              Reference: 'https://github.com/JuanitoFatas/fast-ruby#remove-extra-spaces-or-other-contiguous-characters-code'
         | 
| 231 | 
            +
              Enabled: 'pending'
         | 
| 232 | 
            +
              VersionAdded: '1.7'
         | 
| 233 | 
            +
             | 
| 190 234 | 
             
            Performance/StartWith:
         | 
| 191 235 | 
             
              Description: 'Use `start_with?` instead of a regex match anchored to the beginning of a string.'
         | 
| 192 236 | 
             
              Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringmatch-vs-stringstart_withstringend_with-code-start-code-end'
         | 
| @@ -200,6 +244,11 @@ Performance/StartWith: | |
| 200 244 | 
             
              VersionAdded: '0.36'
         | 
| 201 245 | 
             
              VersionChanged: '1.6'
         | 
| 202 246 |  | 
| 247 | 
            +
            Performance/StringInclude:
         | 
| 248 | 
            +
              Description: 'Use `String#include?` instead of a regex match with literal-only pattern.'
         | 
| 249 | 
            +
              Enabled: 'pending'
         | 
| 250 | 
            +
              VersionAdded: '1.7'
         | 
| 251 | 
            +
             | 
| 203 252 | 
             
            Performance/StringReplacement:
         | 
| 204 253 | 
             
              Description: >-
         | 
| 205 254 | 
             
                              Use `tr` instead of `gsub` when you are replacing the same
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                # Common functionality for cops checking `Enumerable#sort` blocks.
         | 
| 6 | 
            +
                module SortBlock
         | 
| 7 | 
            +
                  extend NodePattern::Macros
         | 
| 8 | 
            +
                  include RangeHelp
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def_node_matcher :sort_with_block?, <<~PATTERN
         | 
| 11 | 
            +
                    (block
         | 
| 12 | 
            +
                      $(send _ :sort)
         | 
| 13 | 
            +
                      (args (arg $_a) (arg $_b))
         | 
| 14 | 
            +
                      $send)
         | 
| 15 | 
            +
                  PATTERN
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def_node_matcher :replaceable_body?, <<~PATTERN
         | 
| 18 | 
            +
                    (send (lvar %1) :<=> (lvar %2))
         | 
| 19 | 
            +
                  PATTERN
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  private
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def sort_range(send, node)
         | 
| 24 | 
            +
                    range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop is used to identify usages of `ancestors.include?` and
         | 
| 7 | 
            +
                  # change them to use `<=` instead.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # @example
         | 
| 10 | 
            +
                  #   # bad
         | 
| 11 | 
            +
                  #   A.ancestors.include?(B)
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  #   # good
         | 
| 14 | 
            +
                  #   A <= B
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  class AncestorsInclude < Cop
         | 
| 17 | 
            +
                    include RangeHelp
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    MSG = 'Use `<=` instead of `ancestors.include?`.'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def_node_matcher :ancestors_include_candidate?, <<~PATTERN
         | 
| 22 | 
            +
                      (send (send $_subclass :ancestors) :include? $_superclass)
         | 
| 23 | 
            +
                    PATTERN
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def on_send(node)
         | 
| 26 | 
            +
                      return unless ancestors_include_candidate?(node)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      location_of_ancestors = node.children[0].loc.selector.begin_pos
         | 
| 29 | 
            +
                      end_location = node.loc.selector.end_pos
         | 
| 30 | 
            +
                      range = range_between(location_of_ancestors, end_location)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      add_offense(node, location: range)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def autocorrect(node)
         | 
| 36 | 
            +
                      ancestors_include_candidate?(node) do |subclass, superclass|
         | 
| 37 | 
            +
                        lambda do |corrector|
         | 
| 38 | 
            +
                          corrector.replace(node, "#{subclass.source} <= #{superclass.source}")
         | 
| 39 | 
            +
                        end
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop identifies places where numeric argument to BigDecimal should be
         | 
| 7 | 
            +
                  # converted to string. Initializing from String is faster
         | 
| 8 | 
            +
                  # than from Numeric for BigDecimal.
         | 
| 9 | 
            +
                  #
         | 
| 10 | 
            +
                  # @example
         | 
| 11 | 
            +
                  #
         | 
| 12 | 
            +
                  #   # bad
         | 
| 13 | 
            +
                  # BigDecimal(1, 2)
         | 
| 14 | 
            +
                  # BigDecimal(1.2, 3, exception: true)
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  #   # good
         | 
| 17 | 
            +
                  # BigDecimal('1', 2)
         | 
| 18 | 
            +
                  # BigDecimal('1.2', 3, exception: true)
         | 
| 19 | 
            +
                  #
         | 
| 20 | 
            +
                  class BigDecimalWithNumericArgument < Cop
         | 
| 21 | 
            +
                    MSG = 'Convert numeric argument to string before passing to `BigDecimal`.'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
         | 
| 24 | 
            +
                      (send nil? :BigDecimal $numeric_type? ...)
         | 
| 25 | 
            +
                    PATTERN
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def on_send(node)
         | 
| 28 | 
            +
                      big_decimal_with_numeric_argument?(node) do |numeric|
         | 
| 29 | 
            +
                        add_offense(node, location: numeric.source_range)
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def autocorrect(node)
         | 
| 34 | 
            +
                      big_decimal_with_numeric_argument?(node) do |numeric|
         | 
| 35 | 
            +
                        lambda do |corrector|
         | 
| 36 | 
            +
                          corrector.wrap(numeric, "'", "'")
         | 
| 37 | 
            +
                        end
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,127 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop identifies places where inefficient `readlines` method
         | 
| 7 | 
            +
                  # can be replaced by `each_line` to avoid fully loading file content into memory.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # @example
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  #   # bad
         | 
| 12 | 
            +
                  #   File.readlines('testfile').each { |l| puts l }
         | 
| 13 | 
            +
                  #   IO.readlines('testfile', chomp: true).each { |l| puts l }
         | 
| 14 | 
            +
                  #
         | 
| 15 | 
            +
                  #   conn.readlines(10).map { |l| l.size }
         | 
| 16 | 
            +
                  #   file.readlines.find { |l| l.start_with?('#') }
         | 
| 17 | 
            +
                  #   file.readlines.each { |l| puts l }
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  #   # good
         | 
| 20 | 
            +
                  #   File.open('testfile', 'r').each_line { |l| puts l }
         | 
| 21 | 
            +
                  #   IO.open('testfile').each_line(chomp: true) { |l| puts l }
         | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  #   conn.each_line(10).map { |l| l.size }
         | 
| 24 | 
            +
                  #   file.each_line.find { |l| l.start_with?('#') }
         | 
| 25 | 
            +
                  #   file.each_line { |l| puts l }
         | 
| 26 | 
            +
                  #
         | 
| 27 | 
            +
                  class IoReadlines < Cop
         | 
| 28 | 
            +
                    include RangeHelp
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    MSG = 'Use `%<good>s` instead of `%<bad>s`.'
         | 
| 31 | 
            +
                    ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def_node_matcher :readlines_on_class?, <<~PATTERN
         | 
| 34 | 
            +
                      $(send $(send (const nil? {:IO :File}) :readlines ...) #enumerable_method?)
         | 
| 35 | 
            +
                    PATTERN
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def_node_matcher :readlines_on_instance?, <<~PATTERN
         | 
| 38 | 
            +
                      $(send $(send ${nil? !const_type?} :readlines ...) #enumerable_method? ...)
         | 
| 39 | 
            +
                    PATTERN
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    def on_send(node)
         | 
| 42 | 
            +
                      readlines_on_class?(node) do |enumerable_call, readlines_call|
         | 
| 43 | 
            +
                        offense(node, enumerable_call, readlines_call)
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      readlines_on_instance?(node) do |enumerable_call, readlines_call, _|
         | 
| 47 | 
            +
                        offense(node, enumerable_call, readlines_call)
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def autocorrect(node)
         | 
| 52 | 
            +
                      readlines_on_instance?(node) do |enumerable_call, readlines_call, receiver|
         | 
| 53 | 
            +
                        # We cannot safely correct `.readlines` method called on IO/File classes
         | 
| 54 | 
            +
                        # due to its signature and we are not sure with implicit receiver
         | 
| 55 | 
            +
                        # if it is called in the context of some instance or mentioned class.
         | 
| 56 | 
            +
                        return if receiver.nil?
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                        lambda do |corrector|
         | 
| 59 | 
            +
                          range = correction_range(enumerable_call, readlines_call)
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                          if readlines_call.arguments?
         | 
| 62 | 
            +
                            call_args = build_call_args(readlines_call.arguments)
         | 
| 63 | 
            +
                            replacement = "each_line(#{call_args})"
         | 
| 64 | 
            +
                          else
         | 
| 65 | 
            +
                            replacement = 'each_line'
         | 
| 66 | 
            +
                          end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                          corrector.replace(range, replacement)
         | 
| 69 | 
            +
                        end
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    private
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    def enumerable_method?(node)
         | 
| 76 | 
            +
                      ENUMERABLE_METHODS.include?(node.to_sym)
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    def offense(node, enumerable_call, readlines_call)
         | 
| 80 | 
            +
                      range = offense_range(enumerable_call, readlines_call)
         | 
| 81 | 
            +
                      good_method = build_good_method(enumerable_call)
         | 
| 82 | 
            +
                      bad_method = build_bad_method(enumerable_call)
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                      add_offense(
         | 
| 85 | 
            +
                        node,
         | 
| 86 | 
            +
                        location: range,
         | 
| 87 | 
            +
                        message: format(MSG, good: good_method, bad: bad_method)
         | 
| 88 | 
            +
                      )
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    def offense_range(enumerable_call, readlines_call)
         | 
| 92 | 
            +
                      readlines_pos = readlines_call.loc.selector.begin_pos
         | 
| 93 | 
            +
                      enumerable_pos = enumerable_call.loc.selector.end_pos
         | 
| 94 | 
            +
                      range_between(readlines_pos, enumerable_pos)
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    def build_good_method(enumerable_call)
         | 
| 98 | 
            +
                      if enumerable_call.method?(:each)
         | 
| 99 | 
            +
                        'each_line'
         | 
| 100 | 
            +
                      else
         | 
| 101 | 
            +
                        "each_line.#{enumerable_call.method_name}"
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    def build_bad_method(enumerable_call)
         | 
| 106 | 
            +
                      "readlines.#{enumerable_call.method_name}"
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    def correction_range(enumerable_call, readlines_call)
         | 
| 110 | 
            +
                      begin_pos = readlines_call.loc.selector.begin_pos
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                      end_pos = if enumerable_call.method?(:each)
         | 
| 113 | 
            +
                                  enumerable_call.loc.expression.end_pos
         | 
| 114 | 
            +
                                else
         | 
| 115 | 
            +
                                  enumerable_call.loc.dot.begin_pos
         | 
| 116 | 
            +
                                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      range_between(begin_pos, end_pos)
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    def build_call_args(call_args_node)
         | 
| 122 | 
            +
                      call_args_node.map(&:source).join(', ')
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
            end
         | 
| @@ -3,18 +3,19 @@ | |
| 3 3 | 
             
            module RuboCop
         | 
| 4 4 | 
             
              module Cop
         | 
| 5 5 | 
             
                module Performance
         | 
| 6 | 
            -
                  # This cop identifies uses of `Range#include?`, which iterates over each
         | 
| 6 | 
            +
                  # This cop identifies uses of `Range#include?` and `Range#member?`, which iterates over each
         | 
| 7 7 | 
             
                  # item in a `Range` to see if a specified item is there. In contrast,
         | 
| 8 8 | 
             
                  # `Range#cover?` simply compares the target item with the beginning and
         | 
| 9 9 | 
             
                  # end points of the `Range`. In a great majority of cases, this is what
         | 
| 10 10 | 
             
                  # is wanted.
         | 
| 11 11 | 
             
                  #
         | 
| 12 | 
            -
                  # This cop is `Safe: false` by default because `Range#include?` and
         | 
| 12 | 
            +
                  # This cop is `Safe: false` by default because `Range#include?` (or `Range#member?`) and
         | 
| 13 13 | 
             
                  # `Range#cover?` are not equivalent behaviour.
         | 
| 14 14 | 
             
                  #
         | 
| 15 15 | 
             
                  # @example
         | 
| 16 16 | 
             
                  #   # bad
         | 
| 17 17 | 
             
                  #   ('a'..'z').include?('b') # => true
         | 
| 18 | 
            +
                  #   ('a'..'z').member?('b')  # => true
         | 
| 18 19 | 
             
                  #
         | 
| 19 20 | 
             
                  #   # good
         | 
| 20 21 | 
             
                  #   ('a'..'z').cover?('b') # => true
         | 
| @@ -24,7 +25,7 @@ module RuboCop | |
| 24 25 | 
             
                  #
         | 
| 25 26 | 
             
                  #   ('a'..'z').cover?('yellow') # => true
         | 
| 26 27 | 
             
                  class RangeInclude < Cop
         | 
| 27 | 
            -
                    MSG = 'Use `Range#cover?` instead of `Range | 
| 28 | 
            +
                    MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
         | 
| 28 29 |  | 
| 29 30 | 
             
                    # TODO: If we traced out assignments of variables to their uses, we
         | 
| 30 31 | 
             
                    # might pick up on a few more instances of this issue
         | 
| @@ -32,13 +33,14 @@ module RuboCop | |
| 32 33 | 
             
                    # (We don't even catch it if the Range is in double parens)
         | 
| 33 34 |  | 
| 34 35 | 
             
                    def_node_matcher :range_include, <<~PATTERN
         | 
| 35 | 
            -
                      (send {irange erange (begin {irange erange})} :include? ...)
         | 
| 36 | 
            +
                      (send {irange erange (begin {irange erange})} ${:include? :member?} ...)
         | 
| 36 37 | 
             
                    PATTERN
         | 
| 37 38 |  | 
| 38 39 | 
             
                    def on_send(node)
         | 
| 39 | 
            -
                       | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 40 | 
            +
                      range_include(node) do |bad_method|
         | 
| 41 | 
            +
                        message = format(MSG, bad_method: bad_method)
         | 
| 42 | 
            +
                        add_offense(node, location: :selector, message: message)
         | 
| 43 | 
            +
                      end
         | 
| 42 44 | 
             
                    end
         | 
| 43 45 |  | 
| 44 46 | 
             
                    def autocorrect(node)
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop identifies places where `sort { |a, b| a <=> b }`
         | 
| 7 | 
            +
                  # can be replaced with `sort`.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # @example
         | 
| 10 | 
            +
                  #   # bad
         | 
| 11 | 
            +
                  #   array.sort { |a, b| a <=> b }
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  #   # good
         | 
| 14 | 
            +
                  #   array.sort
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  class RedundantSortBlock < Cop
         | 
| 17 | 
            +
                    include SortBlock
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    MSG = 'Use `sort` instead of `%<bad_method>s`.'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def on_block(node)
         | 
| 22 | 
            +
                      sort_with_block?(node) do |send, var_a, var_b, body|
         | 
| 23 | 
            +
                        replaceable_body?(body, var_a, var_b) do
         | 
| 24 | 
            +
                          range = sort_range(send, node)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                          add_offense(
         | 
| 27 | 
            +
                            node,
         | 
| 28 | 
            +
                            location: range,
         | 
| 29 | 
            +
                            message: message(var_a, var_b)
         | 
| 30 | 
            +
                          )
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def autocorrect(node)
         | 
| 36 | 
            +
                      sort_with_block?(node) do |send, _var_a, _var_b, _body|
         | 
| 37 | 
            +
                        lambda do |corrector|
         | 
| 38 | 
            +
                          range = sort_range(send, node)
         | 
| 39 | 
            +
                          corrector.replace(range, 'sort')
         | 
| 40 | 
            +
                        end
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    private
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    def message(var_a, var_b)
         | 
| 47 | 
            +
                      bad_method = "sort { |#{var_a}, #{var_b}| #{var_a} <=> #{var_b} }"
         | 
| 48 | 
            +
                      format(MSG, bad_method: bad_method)
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,137 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RuboCop
         | 
| 4 | 
            +
              module Cop
         | 
| 5 | 
            +
                module Performance
         | 
| 6 | 
            +
                  # This cop checks for redundant `String#chars`.
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # @example
         | 
| 9 | 
            +
                  #   # bad
         | 
| 10 | 
            +
                  #   str.chars[0..2]
         | 
| 11 | 
            +
                  #   str.chars.slice(0..2)
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  #   # good
         | 
| 14 | 
            +
                  #   str[0..2].chars
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  #   # bad
         | 
| 17 | 
            +
                  #   str.chars.first
         | 
| 18 | 
            +
                  #   str.chars.first(2)
         | 
| 19 | 
            +
                  #   str.chars.last
         | 
| 20 | 
            +
                  #   str.chars.last(2)
         | 
| 21 | 
            +
                  #
         | 
| 22 | 
            +
                  #   # good
         | 
| 23 | 
            +
                  #   str[0]
         | 
| 24 | 
            +
                  #   str[0...2].chars
         | 
| 25 | 
            +
                  #   str[-1]
         | 
| 26 | 
            +
                  #   str[-2..-1].chars
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  #   # bad
         | 
| 29 | 
            +
                  #   str.chars.take(2)
         | 
| 30 | 
            +
                  #   str.chars.drop(2)
         | 
| 31 | 
            +
                  #   str.chars.length
         | 
| 32 | 
            +
                  #   str.chars.size
         | 
| 33 | 
            +
                  #   str.chars.empty?
         | 
| 34 | 
            +
                  #
         | 
| 35 | 
            +
                  #   # good
         | 
| 36 | 
            +
                  #   str[0...2].chars
         | 
| 37 | 
            +
                  #   str[2..-1].chars
         | 
| 38 | 
            +
                  #   str.length
         | 
| 39 | 
            +
                  #   str.size
         | 
| 40 | 
            +
                  #   str.empty?
         | 
| 41 | 
            +
                  #
         | 
| 42 | 
            +
                  class RedundantStringChars < Cop
         | 
| 43 | 
            +
                    include RangeHelp
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
         | 
| 46 | 
            +
                    REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def_node_matcher :redundant_chars_call?, <<~PATTERN
         | 
| 49 | 
            +
                      (send $(send _ :chars) $#replaceable_method? $...)
         | 
| 50 | 
            +
                    PATTERN
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def on_send(node)
         | 
| 53 | 
            +
                      redundant_chars_call?(node) do |receiver, method, args|
         | 
| 54 | 
            +
                        range = offense_range(receiver, node)
         | 
| 55 | 
            +
                        message = build_message(method, args)
         | 
| 56 | 
            +
                        add_offense(node, location: range, message: message)
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def autocorrect(node)
         | 
| 61 | 
            +
                      redundant_chars_call?(node) do |receiver, method, args|
         | 
| 62 | 
            +
                        range = correction_range(receiver, node)
         | 
| 63 | 
            +
                        replacement = build_good_method(method, args)
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                        lambda do |corrector|
         | 
| 66 | 
            +
                          corrector.replace(range, replacement)
         | 
| 67 | 
            +
                        end
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    private
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def replaceable_method?(method_name)
         | 
| 74 | 
            +
                      REPLACEABLE_METHODS.include?(method_name)
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    def offense_range(receiver, node)
         | 
| 78 | 
            +
                      range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    def correction_range(receiver, node)
         | 
| 82 | 
            +
                      range_between(receiver.loc.dot.begin_pos, node.loc.expression.end_pos)
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    def build_message(method, args)
         | 
| 86 | 
            +
                      good_method = build_good_method(method, args)
         | 
| 87 | 
            +
                      bad_method = build_bad_method(method, args)
         | 
| 88 | 
            +
                      format(MSG, good_method: good_method, bad_method: bad_method)
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
         | 
| 92 | 
            +
                    def build_good_method(method, args)
         | 
| 93 | 
            +
                      case method
         | 
| 94 | 
            +
                      when :[], :slice
         | 
| 95 | 
            +
                        "[#{build_call_args(args)}].chars"
         | 
| 96 | 
            +
                      when :first
         | 
| 97 | 
            +
                        if args.any?
         | 
| 98 | 
            +
                          "[0...#{args.first.source}].chars"
         | 
| 99 | 
            +
                        else
         | 
| 100 | 
            +
                          '[0]'
         | 
| 101 | 
            +
                        end
         | 
| 102 | 
            +
                      when :last
         | 
| 103 | 
            +
                        if args.any?
         | 
| 104 | 
            +
                          "[-#{args.first.source}..-1].chars"
         | 
| 105 | 
            +
                        else
         | 
| 106 | 
            +
                          '[-1]'
         | 
| 107 | 
            +
                        end
         | 
| 108 | 
            +
                      when :take
         | 
| 109 | 
            +
                        "[0...#{args.first.source}].chars"
         | 
| 110 | 
            +
                      when :drop
         | 
| 111 | 
            +
                        "[#{args.first.source}..-1].chars"
         | 
| 112 | 
            +
                      else
         | 
| 113 | 
            +
                        ".#{method}"
         | 
| 114 | 
            +
                      end
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
                    # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                    def build_bad_method(method, args)
         | 
| 119 | 
            +
                      case method
         | 
| 120 | 
            +
                      when :[]
         | 
| 121 | 
            +
                        "chars[#{build_call_args(args)}]"
         | 
| 122 | 
            +
                      else
         | 
| 123 | 
            +
                        if args.any?
         | 
| 124 | 
            +
                          "chars.#{method}(#{build_call_args(args)})"
         | 
| 125 | 
            +
                        else
         | 
| 126 | 
            +
                          "chars.#{method}"
         | 
| 127 | 
            +
                        end
         | 
| 128 | 
            +
                      end
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    def build_call_args(call_args_node)
         | 
| 132 | 
            +
                      call_args_node.map(&:source).join(', ')
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
            end
         | 
| @@ -0,0 +1,78 @@ | |
| 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 < Cop
         | 
| 20 | 
            +
                    include RangeHelp
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def_node_matcher :reverse_first_candidate?, <<~PATTERN
         | 
| 25 | 
            +
                      (send $(send _ :reverse) :first (int _)?)
         | 
| 26 | 
            +
                    PATTERN
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def on_send(node)
         | 
| 29 | 
            +
                      reverse_first_candidate?(node) do |receiver|
         | 
| 30 | 
            +
                        range = correction_range(receiver, node)
         | 
| 31 | 
            +
                        message = build_message(node)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                        add_offense(node, location: range, message: message)
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def autocorrect(node)
         | 
| 38 | 
            +
                      reverse_first_candidate?(node) do |receiver|
         | 
| 39 | 
            +
                        range = correction_range(receiver, node)
         | 
| 40 | 
            +
                        replacement = build_good_method(node)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        lambda do |corrector|
         | 
| 43 | 
            +
                          corrector.replace(range, replacement)
         | 
| 44 | 
            +
                        end
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    private
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def correction_range(receiver, node)
         | 
| 51 | 
            +
                      range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    def build_message(node)
         | 
| 55 | 
            +
                      good_method = build_good_method(node)
         | 
| 56 | 
            +
                      bad_method = build_bad_method(node)
         | 
| 57 | 
            +
                      format(MSG, good_method: good_method, bad_method: bad_method)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def build_good_method(node)
         | 
| 61 | 
            +
                      if node.arguments?
         | 
| 62 | 
            +
                        "last(#{node.arguments.first.source}).reverse"
         | 
| 63 | 
            +
                      else
         | 
| 64 | 
            +
                        'last'
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    def build_bad_method(node)
         | 
| 69 | 
            +
                      if node.arguments?
         | 
| 70 | 
            +
                        "reverse.first(#{node.arguments.first.source})"
         | 
| 71 | 
            +
                      else
         | 
| 72 | 
            +
                        'reverse.first'
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         | 
| @@ -9,15 +9,27 @@ 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 }
         | 
| @@ -26,8 +38,30 @@ module RuboCop | |
| 26 38 | 
             
                  class Size < Cop
         | 
| 27 39 | 
             
                    MSG = 'Use `size` instead of `count`.'
         | 
| 28 40 |  | 
| 41 | 
            +
                    def_node_matcher :array?, <<~PATTERN
         | 
| 42 | 
            +
                      {
         | 
| 43 | 
            +
                        [!nil? array_type?]
         | 
| 44 | 
            +
                        (send _ :to_a)
         | 
| 45 | 
            +
                        (send (const nil? :Array) :[] _)
         | 
| 46 | 
            +
                        (send nil? :Array _)
         | 
| 47 | 
            +
                      }
         | 
| 48 | 
            +
                    PATTERN
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def_node_matcher :hash?, <<~PATTERN
         | 
| 51 | 
            +
                      {
         | 
| 52 | 
            +
                        [!nil? hash_type?]
         | 
| 53 | 
            +
                        (send _ :to_h)
         | 
| 54 | 
            +
                        (send (const nil? :Hash) :[] _)
         | 
| 55 | 
            +
                        (send nil? :Hash _)
         | 
| 56 | 
            +
                      }
         | 
| 57 | 
            +
                    PATTERN
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def_node_matcher :count?, <<~PATTERN
         | 
| 60 | 
            +
                      (send {#array? #hash?} :count)
         | 
| 61 | 
            +
                    PATTERN
         | 
| 62 | 
            +
             | 
| 29 63 | 
             
                    def on_send(node)
         | 
| 30 | 
            -
                      return  | 
| 64 | 
            +
                      return if node.parent&.block_type? || !count?(node)
         | 
| 31 65 |  | 
| 32 66 | 
             
                      add_offense(node, location: :selector)
         | 
| 33 67 | 
             
                    end
         | 
| @@ -35,42 +69,6 @@ module RuboCop | |
| 35 69 | 
             
                    def autocorrect(node)
         | 
| 36 70 | 
             
                      ->(corrector) { corrector.replace(node.loc.selector, 'size') }
         | 
| 37 71 | 
             
                    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
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                    def eligible_receiver?(node)
         | 
| 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
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                    def hash?(node)
         | 
| 67 | 
            -
                      return true if node.hash_type?
         | 
| 68 | 
            -
                      return false unless node.send_type?
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                      _, constant = *node.receiver
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                      constant == :Hash || node.method?(:to_h)
         | 
| 73 | 
            -
                    end
         | 
| 74 72 | 
             
                  end
         | 
| 75 73 | 
             
                end
         | 
| 76 74 | 
             
              end
         | 
| @@ -0,0 +1,54 @@ | |
| 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 < Cop
         | 
| 17 | 
            +
                    include SortBlock
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def on_block(node)
         | 
| 22 | 
            +
                      sort_with_block?(node) do |send, var_a, var_b, body|
         | 
| 23 | 
            +
                        replaceable_body?(body, var_b, var_a) do
         | 
| 24 | 
            +
                          range = sort_range(send, node)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                          add_offense(
         | 
| 27 | 
            +
                            node,
         | 
| 28 | 
            +
                            location: range,
         | 
| 29 | 
            +
                            message: message(var_a, var_b)
         | 
| 30 | 
            +
                          )
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def autocorrect(node)
         | 
| 36 | 
            +
                      sort_with_block?(node) do |send, _var_a, _var_b, _body|
         | 
| 37 | 
            +
                        lambda do |corrector|
         | 
| 38 | 
            +
                          range = sort_range(send, node)
         | 
| 39 | 
            +
                          replacement = 'sort.reverse'
         | 
| 40 | 
            +
                          corrector.replace(range, replacement)
         | 
| 41 | 
            +
                        end
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    private
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def message(var_a, var_b)
         | 
| 48 | 
            +
                      bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
         | 
| 49 | 
            +
                      format(MSG, bad_method: bad_method)
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,70 @@ | |
| 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 < Cop
         | 
| 22 | 
            +
                    MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    PREFERRED_METHODS = {
         | 
| 25 | 
            +
                      gsub: :squeeze,
         | 
| 26 | 
            +
                      gsub!: :squeeze!
         | 
| 27 | 
            +
                    }.freeze
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def_node_matcher :squeeze_candidate?, <<~PATTERN
         | 
| 30 | 
            +
                      (send
         | 
| 31 | 
            +
                        $!nil? ${:gsub :gsub!}
         | 
| 32 | 
            +
                        (regexp
         | 
| 33 | 
            +
                          (str $#repeating_literal?)
         | 
| 34 | 
            +
                          (regopt))
         | 
| 35 | 
            +
                        (str $_))
         | 
| 36 | 
            +
                    PATTERN
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def on_send(node)
         | 
| 39 | 
            +
                      squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
         | 
| 40 | 
            +
                        regexp_str = regexp_str[0..-2] # delete '+' from the end
         | 
| 41 | 
            +
                        regexp_str = interpret_string_escapes(regexp_str)
         | 
| 42 | 
            +
                        return unless replace_str == regexp_str
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                        good_method = PREFERRED_METHODS[bad_method]
         | 
| 45 | 
            +
                        message = format(MSG, current: bad_method, prefer: good_method)
         | 
| 46 | 
            +
                        add_offense(node, location: :selector, message: message)
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def autocorrect(node)
         | 
| 51 | 
            +
                      squeeze_candidate?(node) do |receiver, bad_method, _regexp_str, replace_str|
         | 
| 52 | 
            +
                        lambda do |corrector|
         | 
| 53 | 
            +
                          good_method = PREFERRED_METHODS[bad_method]
         | 
| 54 | 
            +
                          string_literal = to_string_literal(replace_str)
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                          new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
         | 
| 57 | 
            +
                          corrector.replace(node.source_range, new_code)
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    private
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    def repeating_literal?(regex_str)
         | 
| 65 | 
            +
                      regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 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
         | 
| 7 | 
            +
                  # `String#include?` would suffice.
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  # @example
         | 
| 10 | 
            +
                  #   # bad
         | 
| 11 | 
            +
                  #   'abc'.match?(/ab/)
         | 
| 12 | 
            +
                  #   /ab/.match?('abc')
         | 
| 13 | 
            +
                  #   'abc' =~ /ab/
         | 
| 14 | 
            +
                  #   /ab/ =~ 'abc'
         | 
| 15 | 
            +
                  #   'abc'.match(/ab/)
         | 
| 16 | 
            +
                  #   /ab/.match('abc')
         | 
| 17 | 
            +
                  #
         | 
| 18 | 
            +
                  #   # good
         | 
| 19 | 
            +
                  #   'abc'.include?('ab')
         | 
| 20 | 
            +
                  class StringInclude < Cop
         | 
| 21 | 
            +
                    MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def_node_matcher :redundant_regex?, <<~PATTERN
         | 
| 24 | 
            +
                      {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
         | 
| 25 | 
            +
                       (send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
         | 
| 26 | 
            +
                       (match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
         | 
| 27 | 
            +
                    PATTERN
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def on_send(node)
         | 
| 30 | 
            +
                      return unless redundant_regex?(node)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      add_offense(node)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                    alias on_match_with_lvasgn on_send
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def autocorrect(node)
         | 
| 37 | 
            +
                      redundant_regex?(node) do |receiver, regex_str|
         | 
| 38 | 
            +
                        receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
         | 
| 39 | 
            +
                        regex_str = interpret_string_escapes(regex_str)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                        lambda do |corrector|
         | 
| 42 | 
            +
                          new_source = receiver.source + '.include?(' +
         | 
| 43 | 
            +
                                       to_string_literal(regex_str) + ')'
         | 
| 44 | 
            +
                          corrector.replace(node.source_range, new_source)
         | 
| 45 | 
            +
                        end
         | 
| 46 | 
            +
                      end
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    private
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def literal?(regex_str)
         | 
| 52 | 
            +
                      regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -1,7 +1,10 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative 'mixin/regexp_metacharacter'
         | 
| 4 | 
            +
            require_relative 'mixin/sort_block'
         | 
| 4 5 |  | 
| 6 | 
            +
            require_relative 'performance/ancestors_include'
         | 
| 7 | 
            +
            require_relative 'performance/big_decimal_with_numeric_argument'
         | 
| 5 8 | 
             
            require_relative 'performance/bind_call'
         | 
| 6 9 | 
             
            require_relative 'performance/caller'
         | 
| 7 10 | 
             
            require_relative 'performance/case_when_splat'
         | 
| @@ -18,13 +21,20 @@ require_relative 'performance/flat_map' | |
| 18 21 | 
             
            require_relative 'performance/inefficient_hash_search'
         | 
| 19 22 | 
             
            require_relative 'performance/open_struct'
         | 
| 20 23 | 
             
            require_relative 'performance/range_include'
         | 
| 24 | 
            +
            require_relative 'performance/io_readlines'
         | 
| 21 25 | 
             
            require_relative 'performance/redundant_block_call'
         | 
| 22 26 | 
             
            require_relative 'performance/redundant_match'
         | 
| 23 27 | 
             
            require_relative 'performance/redundant_merge'
         | 
| 28 | 
            +
            require_relative 'performance/redundant_sort_block'
         | 
| 29 | 
            +
            require_relative 'performance/redundant_string_chars'
         | 
| 24 30 | 
             
            require_relative 'performance/regexp_match'
         | 
| 25 31 | 
             
            require_relative 'performance/reverse_each'
         | 
| 32 | 
            +
            require_relative 'performance/reverse_first'
         | 
| 26 33 | 
             
            require_relative 'performance/size'
         | 
| 34 | 
            +
            require_relative 'performance/sort_reverse'
         | 
| 35 | 
            +
            require_relative 'performance/squeeze'
         | 
| 27 36 | 
             
            require_relative 'performance/start_with'
         | 
| 37 | 
            +
            require_relative 'performance/string_include'
         | 
| 28 38 | 
             
            require_relative 'performance/string_replacement'
         | 
| 29 39 | 
             
            require_relative 'performance/times_map'
         | 
| 30 40 | 
             
            require_relative 'performance/unfreeze_string'
         | 
    
        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. | 
| 4 | 
            +
              version: 1.7.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: 2020- | 
| 13 | 
            +
            date: 2020-07-07 00:00:00.000000000 Z
         | 
| 14 14 | 
             
            dependencies:
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 16 | 
             
              name: rubocop
         | 
| @@ -18,14 +18,14 @@ dependencies: | |
| 18 18 | 
             
                requirements:
         | 
| 19 19 | 
             
                - - ">="
         | 
| 20 20 | 
             
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            -
                    version: 0. | 
| 21 | 
            +
                    version: 0.82.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. | 
| 28 | 
            +
                    version: 0.82.0
         | 
| 29 29 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 30 30 | 
             
              name: simplecov
         | 
| 31 31 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -55,6 +55,9 @@ files: | |
| 55 55 | 
             
            - config/default.yml
         | 
| 56 56 | 
             
            - lib/rubocop-performance.rb
         | 
| 57 57 | 
             
            - lib/rubocop/cop/mixin/regexp_metacharacter.rb
         | 
| 58 | 
            +
            - lib/rubocop/cop/mixin/sort_block.rb
         | 
| 59 | 
            +
            - lib/rubocop/cop/performance/ancestors_include.rb
         | 
| 60 | 
            +
            - lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb
         | 
| 58 61 | 
             
            - lib/rubocop/cop/performance/bind_call.rb
         | 
| 59 62 | 
             
            - lib/rubocop/cop/performance/caller.rb
         | 
| 60 63 | 
             
            - lib/rubocop/cop/performance/case_when_splat.rb
         | 
| @@ -70,15 +73,22 @@ files: | |
| 70 73 | 
             
            - lib/rubocop/cop/performance/fixed_size.rb
         | 
| 71 74 | 
             
            - lib/rubocop/cop/performance/flat_map.rb
         | 
| 72 75 | 
             
            - lib/rubocop/cop/performance/inefficient_hash_search.rb
         | 
| 76 | 
            +
            - lib/rubocop/cop/performance/io_readlines.rb
         | 
| 73 77 | 
             
            - lib/rubocop/cop/performance/open_struct.rb
         | 
| 74 78 | 
             
            - lib/rubocop/cop/performance/range_include.rb
         | 
| 75 79 | 
             
            - lib/rubocop/cop/performance/redundant_block_call.rb
         | 
| 76 80 | 
             
            - lib/rubocop/cop/performance/redundant_match.rb
         | 
| 77 81 | 
             
            - lib/rubocop/cop/performance/redundant_merge.rb
         | 
| 82 | 
            +
            - lib/rubocop/cop/performance/redundant_sort_block.rb
         | 
| 83 | 
            +
            - lib/rubocop/cop/performance/redundant_string_chars.rb
         | 
| 78 84 | 
             
            - lib/rubocop/cop/performance/regexp_match.rb
         | 
| 79 85 | 
             
            - lib/rubocop/cop/performance/reverse_each.rb
         | 
| 86 | 
            +
            - lib/rubocop/cop/performance/reverse_first.rb
         | 
| 80 87 | 
             
            - lib/rubocop/cop/performance/size.rb
         | 
| 88 | 
            +
            - lib/rubocop/cop/performance/sort_reverse.rb
         | 
| 89 | 
            +
            - lib/rubocop/cop/performance/squeeze.rb
         | 
| 81 90 | 
             
            - lib/rubocop/cop/performance/start_with.rb
         | 
| 91 | 
            +
            - lib/rubocop/cop/performance/string_include.rb
         | 
| 82 92 | 
             
            - lib/rubocop/cop/performance/string_replacement.rb
         | 
| 83 93 | 
             
            - lib/rubocop/cop/performance/times_map.rb
         | 
| 84 94 | 
             
            - lib/rubocop/cop/performance/unfreeze_string.rb
         | 
| @@ -111,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 111 121 | 
             
                - !ruby/object:Gem::Version
         | 
| 112 122 | 
             
                  version: '0'
         | 
| 113 123 | 
             
            requirements: []
         | 
| 114 | 
            -
            rubygems_version: 3.1. | 
| 124 | 
            +
            rubygems_version: 3.1.4
         | 
| 115 125 | 
             
            signing_key: 
         | 
| 116 126 | 
             
            specification_version: 4
         | 
| 117 127 | 
             
            summary: Automatic performance checking tool for Ruby code.
         |