Merge pull request #8632 from Homebrew/dependabot/bundler/Library/Homebrew/rubocop-performance-1.8.0

build(deps): bump rubocop-performance from 1.7.1 to 1.8.0 in /Library/Homebrew
This commit is contained in:
Mike McQuaid 2020-09-11 11:58:49 +01:00 committed by GitHub
commit 16ee849c43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 735 additions and 553 deletions

View File

@ -45,11 +45,10 @@ jobs:
- name: Set up Homebrew official command taps
run: |
# Setup taps needed for 'brew tests' and 'brew man'
brew tap homebrew/bundle
cd "$(brew --repo)"
if [ "$RUNNER_OS" = "macOS" ]; then
brew update-reset Library/Taps/homebrew/homebrew-cask Library/Taps/homebrew/homebrew-services
brew update-reset Library/Taps/homebrew/homebrew-bundle Library/Taps/homebrew/homebrew-cask Library/Taps/homebrew/homebrew-services
else
brew update-reset Library/Taps/homebrew/homebrew-services
fi

View File

@ -45,8 +45,9 @@ SimpleCov.start do
else
command_name "#{command_name} (#{$PROCESS_ID})"
excludes = ["test", "vendor"]
subdirs = Dir.chdir(SimpleCov.root) { Dir.glob("*") }
.reject { |d| d.end_with?(".rb") || ["test", "vendor"].include?(d) }
.reject { |d| d.end_with?(".rb") || excludes.include?(d) }
.map { |d| "#{d}/**/*.rb" }.join(",")
# Not using this during integration tests makes the tests 4x times faster

View File

@ -109,8 +109,8 @@ GEM
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.3.0)
parser (>= 2.7.1.4)
rubocop-performance (1.7.1)
rubocop (>= 0.82.0)
rubocop-performance (1.8.0)
rubocop (>= 0.87.0)
rubocop-rspec (1.43.2)
rubocop (~> 0.87)
ruby-macho (2.2.0)

View File

@ -85,9 +85,10 @@ module Cask
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch
def uninstall_launchctl(*services, command: nil, **_)
booleans = [false, true]
services.each do |service|
ohai "Removing launchctl service #{service}"
[false, true].each do |with_sudo|
booleans.each do |with_sudo|
plist_status = command.run(
"/bin/launchctl",
args: ["list", service],

View File

@ -204,7 +204,8 @@ module Cask
end
add_error "at least one name stanza is required" if cask.name.empty?
# TODO: specific DSL knowledge should not be spread around in various files like this
installable_artifacts = cask.artifacts.reject { |k| [:uninstall, :zap].include?(k) }
rejected_artifacts = [:uninstall, :zap]
installable_artifacts = cask.artifacts.reject { |k| rejected_artifacts.include?(k) }
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
end

View File

@ -893,8 +893,9 @@ module Homebrew
bin_names += dir.children.map(&:basename).map(&:to_s)
end
shell_commands = ["system", "shell_output", "pipe_output"]
bin_names.each do |name|
["system", "shell_output", "pipe_output"].each do |cmd|
shell_commands.each do |cmd|
if text.to_s.match?(/test do.*#{cmd}[(\s]+['"]#{Regexp.escape(name)}[\s'"]/m)
problem %Q(fully scope test #{cmd} calls, e.g. #{cmd} "\#{bin}/#{name}")
end

View File

@ -453,13 +453,14 @@ module Homebrew
end
end
any_cellars = ["any", "any_skip_relocation"]
bottles_hash.each do |formula_name, bottle_hash|
ohai formula_name
bottle = BottleSpecification.new
bottle.root_url bottle_hash["bottle"]["root_url"]
cellar = bottle_hash["bottle"]["cellar"]
cellar = cellar.to_sym if ["any", "any_skip_relocation"].include?(cellar)
cellar = cellar.to_sym if any_cellars.include?(cellar)
bottle.cellar cellar
bottle.prefix bottle_hash["bottle"]["prefix"]
bottle.rebuild bottle_hash["bottle"]["rebuild"]
@ -478,14 +479,14 @@ module Homebrew
update_or_add = "update"
if args.keep_old?
mismatches = []
valid_keys = %w[root_url prefix cellar rebuild sha1 sha256]
bottle_block_contents = s.inreplace_string[/ bottle do(.+?)end\n/m, 1]
bottle_block_contents.lines.each do |line|
line = line.strip
next if line.empty?
key, old_value_original, _, tag = line.split " ", 4
valid_key = %w[root_url prefix cellar rebuild sha1 sha256].include? key
next unless valid_key
next unless valid_keys.include?(key)
old_value = old_value_original.to_s.delete "'\""
old_value = old_value.to_s.delete ":" if key != "root_url"

View File

@ -306,6 +306,11 @@ module Homebrew
method_name
end
CUSTOM_IMPLEMENTATIONS = %w[
HOMEBREW_MAKE_JOBS
HOMEBREW_CASK_OPTS
].freeze
ENVS.each do |env, hash|
method_name = env_method_name(env, hash)
env = env.to_s
@ -316,7 +321,7 @@ module Homebrew
end
elsif hash[:default].present?
# Needs a custom implementation.
next if ["HOMEBREW_MAKE_JOBS", "HOMEBREW_CASK_OPTS"].include?(env)
next if CUSTOM_IMPLEMENTATIONS.include?(env)
define_method(method_name) do
ENV[env].presence || hash.fetch(:default).to_s

View File

@ -154,7 +154,9 @@ module FormulaCellarChecks
# Emacs itself can do what it wants
return if name == "emacs"
elisps = (share/"emacs/site-lisp").children.select { |file| %w[.el .elc].include? file.extname }
elisps = (share/"emacs/site-lisp").children.select do |file|
Keg::ELISP_EXTENSIONS.include? file.extname
end
return if elisps.empty?
<<~EOS

View File

@ -122,6 +122,10 @@ class Keg
mime-info pixmaps sounds postgresql
].freeze
ELISP_EXTENSIONS = %w[.el .elc].freeze
PYC_EXTENSIONS = %w[.pyc .pyo].freeze
LIBTOOL_EXTENSIONS = %w[.la .lai].freeze
# Given an array of kegs, this method will try to find some other kegs
# that depend on them. If it does, it returns:
#
@ -423,7 +427,7 @@ class Keg
def elisp_installed?
return false unless (path/"share/emacs/site-lisp"/name).exist?
(path/"share/emacs/site-lisp"/name).children.any? { |f| %w[.el .elc].include? f.extname }
(path/"share/emacs/site-lisp"/name).children.any? { |f| ELISP_EXTENSIONS.include? f.extname }
end
def version
@ -559,7 +563,7 @@ class Keg
end
def delete_pyc_files!
find { |pn| pn.delete if %w[.pyc .pyo].include?(pn.extname) }
find { |pn| pn.delete if PYC_EXTENSIONS.include?(pn.extname) }
find { |pn| FileUtils.rm_rf pn if pn.basename.to_s == "__pycache__" }
end
@ -652,7 +656,7 @@ class Keg
# Don't link pyc or pyo files because Python overwrites these
# cached object files and next time brew wants to link, the
# file is in the way.
Find.prune if %w[.pyc .pyo].include?(src.extname) && src.to_s.include?("/site-packages/")
Find.prune if PYC_EXTENSIONS.include?(src.extname) && src.to_s.include?("/site-packages/")
case yield src.relative_path_from(root)
when :skip_file, nil

View File

@ -170,7 +170,7 @@ class Keg
libtool_files = []
path.find do |pn|
next if pn.symlink? || pn.directory? || ![".la", ".lai"].include?(pn.extname)
next if pn.symlink? || pn.directory? || !Keg::LIBTOOL_EXTENSIONS.include?(pn.extname)
libtool_files << pn
end

View File

@ -37,6 +37,8 @@ module RuboCop
# This cop audits deprecate! reason
class DeprecateDisableReason < FormulaCop
PUNCTUATION_MARKS = %w[. ! ?].freeze
def audit_formula(_node, _class_node, _parent_class_node, body_node)
[:deprecate!, :disable!].each do |method|
node = find_node_method_by_name(body_node, method)
@ -53,7 +55,7 @@ module RuboCop
problem "Do not start the reason with `it`" if reason_string.start_with?("it ")
problem "Do not end the reason with a punctuation mark" if %w[. ! ?].include?(reason_string[-1])
problem "Do not end the reason with a punctuation mark" if PUNCTUATION_MARKS.include?(reason_string[-1])
end
next if reason_found
@ -73,7 +75,7 @@ module RuboCop
lambda do |corrector|
reason = string_content(node)
reason = reason[3..] if reason.start_with?("it ")
reason.chop! if %w[. ! ?].include?(reason[-1])
reason.chop! if PUNCTUATION_MARKS.include?(reason[-1])
corrector.replace(node.source_range, "\"#{reason}\"")
end
end

View File

@ -178,6 +178,7 @@ module Homebrew
json = json_result!(result)
# Convert to same format as RuboCop offenses.
severity_hash = { "style" => "refactor", "info" => "convention" }
json.group_by { |v| v["file"] }
.map do |k, v|
{
@ -188,7 +189,7 @@ module Homebrew
o["cop_name"] = "SC#{o.delete("code")}"
level = o.delete("level")
o["severity"] = { "style" => "refactor", "info" => "convention" }.fetch(level, level)
o["severity"] = severity_hash.fetch(level, level)
line = o.delete("line")
column = o.delete("column")

View File

@ -433,9 +433,10 @@ module GitHub
result = open_graphql(query, scopes: ["user:email"])
reviews = result["repository"]["pullRequest"]["reviews"]["nodes"]
valid_associations = %w[MEMBER OWNER]
reviews.map do |r|
next if commit.present? && commit != r["commit"]["oid"]
next unless %w[MEMBER OWNER].include? r["authorAssociation"]
next unless valid_associations.include? r["authorAssociation"]
email = if r["author"]["email"].blank?
"#{r["author"]["databaseId"]}+#{r["author"]["login"]}@users.noreply.github.com"

View File

@ -73,11 +73,11 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-ast-0.3.0/lib
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.10.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-0.90.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.7.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.8.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-1.43.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.5895-universal-darwin-19/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.5895/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.5898-universal-darwin-19/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.5898/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.0.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/spoom-1.0.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tapioca-0.4.4/lib"

View File

@ -49,6 +49,13 @@ Performance/ChainArrayAllocation:
Enabled: false
VersionAdded: '0.59'
Performance/CollectionLiteralInLoop:
Description: 'Extract Array and Hash literals outside of loops into local variables or constants.'
Enabled: true
VersionAdded: '1.8'
# Min number of elements to consider an offense
MinSize: 1
Performance/CompareWithBlock:
Description: 'Use `sort_by(&:foo)` instead of `sort { |a, b| a.foo <=> b.foo }`.'
Enabled: true
@ -56,16 +63,14 @@ Performance/CompareWithBlock:
Performance/Count:
Description: >-
Use `count` instead of `select...size`, `reject...size`,
`select...count`, `reject...count`, `select...length`,
and `reject...length`.
Use `count` instead of `{select,find_all,filter,reject}...{size,count,length}`.
# This cop has known compatibility issues with `ActiveRecord` and other
# frameworks. ActiveRecord's `count` ignores the block that is passed to it.
# For more information, see the documentation in the cop itself.
SafeAutoCorrect: false
Enabled: true
VersionAdded: '0.31'
VersionChanged: '1.5'
VersionChanged: '1.8'
Performance/DeletePrefix:
Description: 'Use `delete_prefix` instead of `gsub`.'
@ -81,8 +86,8 @@ Performance/DeleteSuffix:
Performance/Detect:
Description: >-
Use `detect` instead of `select.first`, `find_all.first`,
`select.last`, and `find_all.last`.
Use `detect` instead of `select.first`, `find_all.first`, `filter.first`,
`select.last`, `find_all.last`, and `filter.last`.
Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code'
# This cop has known compatibility issues with `ActiveRecord` and other
# frameworks. `ActiveRecord` does not implement a `detect` method and `find`
@ -91,7 +96,7 @@ Performance/Detect:
SafeAutoCorrect: false
Enabled: true
VersionAdded: '0.30'
VersionChanged: '1.5'
VersionChanged: '1.8'
Performance/DoubleStartEndWith:
Description: >-
@ -261,6 +266,12 @@ Performance/StringReplacement:
Enabled: true
VersionAdded: '0.33'
Performance/Sum:
Description: 'Use `sum` instead of a custom array summation.'
Reference: 'https://blog.bigbinary.com/2016/11/02/ruby-2-4-introduces-enumerable-sum.html'
Enabled: 'pending'
VersionAdded: '1.8'
Performance/TimesMap:
Description: 'Checks for .times.map calls.'
AutoCorrect: false

View File

@ -13,8 +13,9 @@ module RuboCop
# # good
# A <= B
#
class AncestorsInclude < Cop
class AncestorsInclude < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `<=` instead of `ancestors.include?`.'
@ -23,23 +24,23 @@ module RuboCop
PATTERN
def on_send(node)
return unless ancestors_include_candidate?(node)
return unless (subclass, superclass = ancestors_include_candidate?(node))
return if subclass && !subclass.const_type?
location_of_ancestors = node.children[0].loc.selector.begin_pos
end_location = node.loc.selector.end_pos
range = range_between(location_of_ancestors, end_location)
add_offense(node, location: range)
end
def autocorrect(node)
ancestors_include_candidate?(node) do |subclass, superclass|
lambda do |corrector|
add_offense(range(node)) do |corrector|
subclass_source = subclass ? subclass.source : 'self'
corrector.replace(node, "#{subclass_source} <= #{superclass.source}")
end
end
private
def range(node)
location_of_ancestors = node.children[0].loc.selector.begin_pos
end_location = node.loc.selector.end_pos
range_between(location_of_ancestors, end_location)
end
end
end

View File

@ -16,7 +16,9 @@ module RuboCop
# BigDecimal('1', 2)
# BigDecimal('1.2', 3, exception: true)
#
class BigDecimalWithNumericArgument < Cop
class BigDecimalWithNumericArgument < Base
extend AutoCorrector
MSG = 'Convert numeric argument to string before passing to `BigDecimal`.'
def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
@ -24,20 +26,13 @@ module RuboCop
PATTERN
def on_send(node)
big_decimal_with_numeric_argument?(node) do |numeric|
next if numeric.float_type? && specifies_precision?(node)
return unless (numeric = big_decimal_with_numeric_argument?(node))
return if numeric.float_type? && specifies_precision?(node)
add_offense(node, location: numeric.source_range)
end
end
def autocorrect(node)
big_decimal_with_numeric_argument?(node) do |numeric|
lambda do |corrector|
add_offense(numeric.source_range) do |corrector|
corrector.wrap(numeric, "'", "'")
end
end
end
private

View File

@ -19,8 +19,9 @@ module RuboCop
# # good
# umethod.bind_call(obj, foo, bar)
#
class BindCall < Cop
class BindCall < Base
include RangeHelp
extend AutoCorrector
extend TargetRubyVersion
minimum_target_ruby_version 2.7
@ -37,28 +38,17 @@ module RuboCop
PATTERN
def on_send(node)
bind_with_call_method?(node) do |receiver, bind_arg, call_args_node|
return unless (receiver, bind_arg, call_args_node = bind_with_call_method?(node))
range = correction_range(receiver, node)
call_args = build_call_args(call_args_node)
message = message(bind_arg.source, call_args)
add_offense(node, location: range, message: message)
end
end
def autocorrect(node)
receiver, bind_arg, call_args_node = bind_with_call_method?(node)
range = correction_range(receiver, node)
call_args = build_call_args(call_args_node)
add_offense(range, message: message) do |corrector|
call_args = ", #{call_args}" unless call_args.empty?
replacement_method = "bind_call(#{bind_arg.source}#{call_args})"
lambda do |corrector|
corrector.replace(range, replacement_method)
end
end

View File

@ -18,7 +18,7 @@ module RuboCop
# caller(1..1).first
# caller_locations(2..2).first
# caller_locations(1..1).first
class Caller < Cop
class Caller < Base
MSG_BRACE = 'Use `%<method>s(%<n>d..%<n>d).first`' \
' instead of `%<method>s[%<m>d]`.'
MSG_FIRST = 'Use `%<method>s(%<n>d..%<n>d).first`' \
@ -41,7 +41,8 @@ module RuboCop
def on_send(node)
return unless caller_with_scope_method?(node)
add_offense(node)
message = message(node)
add_offense(node, message: message)
end
private

View File

@ -53,9 +53,10 @@ module RuboCop
# when 5
# baz
# end
class CaseWhenSplat < Cop
class CaseWhenSplat < Base
include Alignment
include RangeHelp
extend AutoCorrector
MSG = 'Reordering `when` conditions with a splat to the end ' \
'of the `when` branches can improve performance.'
@ -66,24 +67,30 @@ module RuboCop
when_conditions = case_node.when_branches.flat_map(&:conditions)
splat_offenses(when_conditions).reverse_each do |condition|
range = condition.parent.loc.keyword.join(condition.source_range)
next if ignored_node?(condition.parent)
ignore_node(condition.parent)
variable, = *condition
message = variable.array_type? ? ARRAY_MSG : MSG
add_offense(condition.parent, location: range, message: message)
add_offense(range(condition), message: message) do |corrector|
autocorrect(corrector, condition.parent)
end
end
end
def autocorrect(when_node)
lambda do |corrector|
private
def autocorrect(corrector, when_node)
if needs_reorder?(when_node)
reorder_condition(corrector, when_node)
else
inline_fix_branch(corrector, when_node)
end
end
end
private
def range(node)
node.parent.loc.keyword.join(node.source_range)
end
def replacement(conditions)
reordered = conditions.partition(&:splat_type?).reverse

View File

@ -19,7 +19,9 @@ module RuboCop
# # good
# str.casecmp('ABC').zero?
# 'abc'.casecmp(str).zero?
class Casecmp < Cop
class Casecmp < Base
extend AutoCorrector
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
CASE_METHODS = %i[downcase upcase].freeze
@ -48,21 +50,13 @@ module RuboCop
return unless downcase_eq(node) || eq_downcase(node)
return unless (parts = take_method_apart(node))
_, _, arg, variable = parts
_receiver, method, arg, variable = parts
good_method = build_good_method(arg, variable)
add_offense(
node,
message: format(MSG, good: good_method, bad: node.source)
)
message = format(MSG, good: good_method, bad: node.source)
add_offense(node, message: message) do |corrector|
correction(corrector, node, method, arg, variable)
end
def autocorrect(node)
return unless (parts = take_method_apart(node))
receiver, method, arg, variable = parts
correction(node, receiver, method, arg, variable)
end
private
@ -84,15 +78,13 @@ module RuboCop
[receiver, method, arg, variable]
end
def correction(node, _receiver, method, arg, variable)
lambda do |corrector|
def correction(corrector, node, method, arg, variable)
corrector.insert_before(node.loc.expression, '!') if method == :!=
replacement = build_good_method(arg, variable)
corrector.replace(node.loc.expression, replacement)
end
end
def build_good_method(arg, variable)
# We want resulting call to be parenthesized

View File

@ -20,7 +20,7 @@ module RuboCop
# array.flatten!
# array.map! { |x| x.downcase }
# array
class ChainArrayAllocation < Cop
class ChainArrayAllocation < Base
include RangeHelp
# These methods return a new array but only sometimes. They must be
@ -61,15 +61,9 @@ module RuboCop
def on_send(node)
flat_map_candidate?(node) do |fm, sm, _|
range = range_between(
node.loc.dot.begin_pos,
node.source_range.end_pos
)
add_offense(
node,
location: range,
message: format(MSG, method: fm, second_method: sm)
)
range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
add_offense(range, message: format(MSG, method: fm, second_method: sm))
end
end
end

View File

@ -0,0 +1,140 @@
# frozen_string_literal: true
require 'set'
module RuboCop
module Cop
module Performance
# This cop identifies places where Array and Hash literals are used
# within loops. It is better to extract them into a local variable or constant
# to avoid unnecessary allocations on each iteration.
#
# You can set the minimum number of elements to consider
# an offense with `MinSize`.
#
# @example
# # bad
# users.select do |user|
# %i[superadmin admin].include?(user.role)
# end
#
# # good
# admin_roles = %i[superadmin admin]
# users.select do |user|
# admin_roles.include?(user.role)
# end
#
# # good
# ADMIN_ROLES = %i[superadmin admin]
# ...
# users.select do |user|
# ADMIN_ROLES.include?(user.role)
# end
#
class CollectionLiteralInLoop < Base
MSG = 'Avoid immutable %<literal_class>s literals in loops. '\
'It is better to extract it into a local variable or a constant.'
POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
ENUMERABLE_METHOD_NAMES = (Enumerable.instance_methods + [:each]).to_set.freeze
NONMUTATING_ARRAY_METHODS = %i[& * + - <=> == [] all? any? assoc at
bsearch bsearch_index collect combination
compact count cycle deconstruct difference dig
drop drop_while each each_index empty? eql?
fetch filter find_index first flatten hash
include? index inspect intersection join
last length map max min minmax none? one? pack
permutation product rassoc reject
repeated_combination repeated_permutation reverse
reverse_each rindex rotate sample select shuffle
size slice sort sum take take_while
to_a to_ary to_h to_s transpose union uniq
values_at zip |].freeze
ARRAY_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_ARRAY_METHODS).to_set.freeze
NONMUTATING_HASH_METHODS = %i[< <= == > >= [] any? assoc compact dig
each each_key each_pair each_value empty?
eql? fetch fetch_values filter flatten has_key?
has_value? hash include? inspect invert key key?
keys? length member? merge rassoc rehash reject
select size slice to_a to_h to_hash to_proc to_s
transform_keys transform_values value? values values_at].freeze
HASH_METHODS = (ENUMERABLE_METHOD_NAMES | NONMUTATING_HASH_METHODS).to_set.freeze
def_node_matcher :kernel_loop?, <<~PATTERN
(block
(send {nil? (const nil? :Kernel)} :loop)
...)
PATTERN
def_node_matcher :enumerable_loop?, <<~PATTERN
(block
(send $_ #enumerable_method? ...)
...)
PATTERN
def on_send(node)
receiver, method, = *node.children
return unless check_literal?(receiver, method) && parent_is_loop?(receiver)
message = format(MSG, literal_class: literal_class(receiver))
add_offense(receiver, message: message)
end
private
def check_literal?(node, method)
!node.nil? &&
nonmutable_method_of_array_or_hash?(node, method) &&
node.children.size >= min_size &&
node.recursive_basic_literal?
end
def nonmutable_method_of_array_or_hash?(node, method)
(node.array_type? && ARRAY_METHODS.include?(method)) ||
(node.hash_type? && HASH_METHODS.include?(method))
end
def parent_is_loop?(node)
node.each_ancestor.any? { |ancestor| loop?(ancestor, node) }
end
def loop?(ancestor, node)
keyword_loop?(ancestor.type) ||
kernel_loop?(ancestor) ||
node_within_enumerable_loop?(node, ancestor)
end
def keyword_loop?(type)
LOOP_TYPES.include?(type)
end
def node_within_enumerable_loop?(node, ancestor)
enumerable_loop?(ancestor) do |receiver|
receiver != node && !receiver.descendants.include?(node)
end
end
def literal_class(node)
if node.array_type?
'Array'
elsif node.hash_type?
'Hash'
end
end
def enumerable_method?(method_name)
ENUMERABLE_METHOD_NAMES.include?(method_name)
end
def min_size
Integer(cop_config['MinSize'] || 1)
end
end
end
end
end

View File

@ -23,8 +23,9 @@ module RuboCop
# array.max_by(&:foo)
# array.min_by(&:foo)
# array.sort_by { |a| a[:foo] }
class CompareWithBlock < Cop
class CompareWithBlock < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<compare_method>s_by%<instead>s` instead of ' \
'`%<compare_method>s { |%<var_a>s, %<var_b>s| %<str_a>s ' \
@ -51,27 +52,15 @@ module RuboCop
range = compare_range(send, node)
add_offense(
node,
location: range,
message: message(send, method, var_a, var_b, args_a)
)
end
end
end
def autocorrect(node)
lambda do |corrector|
send, var_a, var_b, body = compare?(node)
method, arg, = replaceable_body?(body, var_a, var_b)
replacement =
if method == :[]
"#{send.method_name}_by { |a| a[#{arg.first.source}] }"
add_offense(range, message: message(send, method, var_a, var_b, args_a)) do |corrector|
replacement = if method == :[]
"#{send.method_name}_by { |a| a[#{args_a.first.source}] }"
else
"#{send.method_name}_by(&:#{method})"
end
corrector.replace(compare_range(send, node),
replacement)
corrector.replace(range, replacement)
end
end
end
end

View File

@ -4,7 +4,7 @@ module RuboCop
module Cop
module Performance
# This cop is used to identify usages of `count` on an `Enumerable` that
# follow calls to `select` or `reject`. Querying logic can instead be
# follow calls to `select`, `find_all`, `filter` or `reject`. Querying logic can instead be
# passed to the `count` call.
#
# @example
@ -37,15 +37,16 @@ module RuboCop
# becomes:
#
# `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`
class Count < Cop
class Count < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
def_node_matcher :count_candidate?, <<~PATTERN
{
(send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
(send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
(send (block $(send _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
(send $(send _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
}
PATTERN
@ -57,28 +58,24 @@ module RuboCop
selector_node.loc.selector.begin_pos
end
add_offense(node,
location: range,
message: format(MSG, selector: selector,
counter: counter))
add_offense(range, message: format(MSG, selector: selector, counter: counter)) do |corrector|
autocorrect(corrector, node, selector_node, selector)
end
end
end
def autocorrect(node)
selector_node, selector, _counter = count_candidate?(node)
private
def autocorrect(corrector, node, selector_node, selector)
selector_loc = selector_node.loc.selector
return if selector == :reject
range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
lambda do |corrector|
corrector.remove(range)
corrector.replace(selector_loc, 'count')
end
end
private
def eligible_node?(node)
!(node.parent && node.parent.block_type?)

View File

@ -43,9 +43,10 @@ module RuboCop
# str.sub(/^prefix/, '')
# str.sub!(/^prefix/, '')
#
class DeletePrefix < Cop
extend TargetRubyVersion
class DeletePrefix < Base
include RegexpMetacharacter
extend AutoCorrector
extend TargetRubyVersion
minimum_target_ruby_version 2.5
@ -63,31 +64,21 @@ module RuboCop
PATTERN
def on_send(node)
delete_prefix_candidate?(node) do |_, bad_method, _, replace_string|
return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
return unless replace_string.blank?
good_method = PREFERRED_METHODS[bad_method]
message = format(MSG, current: bad_method, prefer: good_method)
add_offense(node, location: :selector, message: message)
end
end
def autocorrect(node)
delete_prefix_candidate?(node) do |receiver, bad_method, regexp_str, _|
lambda do |corrector|
good_method = PREFERRED_METHODS[bad_method]
add_offense(node.loc.selector, message: message) do |corrector|
regexp_str = drop_start_metacharacter(regexp_str)
regexp_str = interpret_string_escapes(regexp_str)
string_literal = to_string_literal(regexp_str)
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
# TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped.
# https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
corrector.replace(node.source_range, new_code)
end
corrector.replace(node, new_code)
end
end
end

View File

@ -43,9 +43,10 @@ module RuboCop
# str.sub(/suffix$/, '')
# str.sub!(/suffix$/, '')
#
class DeleteSuffix < Cop
extend TargetRubyVersion
class DeleteSuffix < Base
include RegexpMetacharacter
extend AutoCorrector
extend TargetRubyVersion
minimum_target_ruby_version 2.5
@ -63,31 +64,21 @@ module RuboCop
PATTERN
def on_send(node)
delete_suffix_candidate?(node) do |_, bad_method, _, replace_string|
return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
return unless replace_string.blank?
good_method = PREFERRED_METHODS[bad_method]
message = format(MSG, current: bad_method, prefer: good_method)
add_offense(node, location: :selector, message: message)
end
end
def autocorrect(node)
delete_suffix_candidate?(node) do |receiver, bad_method, regexp_str, _|
lambda do |corrector|
good_method = PREFERRED_METHODS[bad_method]
add_offense(node.loc.selector, message: message) do |corrector|
regexp_str = drop_end_metacharacter(regexp_str)
regexp_str = interpret_string_escapes(regexp_str)
string_literal = to_string_literal(regexp_str)
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
# TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped.
# https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
corrector.replace(node.source_range, new_code)
end
corrector.replace(node, new_code)
end
end
end

View File

@ -4,7 +4,7 @@ module RuboCop
module Cop
module Performance
# This cop is used to identify usages of
# `select.first`, `select.last`, `find_all.first`, and `find_all.last`
# `select.first`, `select.last`, `find_all.first`, `find_all.last`, `filter.first`, and `filter.last`
# and change them to use `detect` instead.
#
# @example
@ -13,6 +13,8 @@ module RuboCop
# [].select { |item| true }.last
# [].find_all { |item| true }.first
# [].find_all { |item| true }.last
# [].filter { |item| true }.first
# [].filter { |item| true }.last
#
# # good
# [].detect { |item| true }
@ -22,7 +24,9 @@ module RuboCop
# `ActiveRecord` does not implement a `detect` method and `find` has its
# own meaning. Correcting ActiveRecord methods with this cop should be
# considered unsafe.
class Detect < Cop
class Detect < Base
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of ' \
'`%<first_method>s.%<second_method>s`.'
REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
@ -30,8 +34,8 @@ module RuboCop
def_node_matcher :detect_candidate?, <<~PATTERN
{
(send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
(send $(send _ {:select :find_all} ...) ${:first :last} $...)
(send $(block (send _ {:select :find_all :filter}) ...) ${:first :last} $...)
(send $(send _ {:select :find_all :filter} ...) ${:first :last} $...)
}
PATTERN
@ -47,25 +51,6 @@ module RuboCop
end
end
def autocorrect(node)
receiver, first_method = *node
replacement = if first_method == :last
"reverse.#{preferred_method}"
else
preferred_method
end
first_range = receiver.source_range.end.join(node.loc.selector)
receiver, _args, _body = *receiver if receiver.block_type?
lambda do |corrector|
corrector.remove(first_range)
corrector.replace(receiver.loc.selector, replacement)
end
end
private
def accept_first_call?(receiver, body)
@ -86,12 +71,30 @@ module RuboCop
first_method: first_method,
second_method: second_method)
add_offense(node, location: range, message: formatted_message)
add_offense(range, message: formatted_message) do |corrector|
autocorrect(corrector, node)
end
end
def autocorrect(corrector, node)
receiver, first_method = *node
replacement = if first_method == :last
"reverse.#{preferred_method}"
else
preferred_method
end
first_range = receiver.source_range.end.join(node.loc.selector)
receiver, _args, _body = *receiver if receiver.block_type?
corrector.remove(first_range)
corrector.replace(receiver.loc.selector, replacement)
end
def preferred_method
config.for_cop('Style/CollectionMethods') \
['PreferredMethods']['detect'] || 'detect'
config.for_cop('Style/CollectionMethods')['PreferredMethods']['detect'] || 'detect'
end
def lazy?(node)

View File

@ -17,39 +17,34 @@ module RuboCop
# str.start_with?("a", Some::CONST)
# str.start_with?("a", "b", "c")
# str.end_with?(var1, var2)
class DoubleStartEndWith < Cop
class DoubleStartEndWith < Base
extend AutoCorrector
MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
'instead of `%<original_code>s`.'
def on_or(node)
receiver,
method,
first_call_args,
second_call_args = process_source(node)
receiver, method, first_call_args, second_call_args = process_source(node)
return unless receiver && second_call_args.all?(&:pure?)
combined_args = combine_args(first_call_args, second_call_args)
add_offense_for_double_call(node, receiver, method, combined_args)
end
def autocorrect(node)
_receiver, _method,
first_call_args, second_call_args = process_source(node)
combined_args = combine_args(first_call_args, second_call_args)
first_argument = first_call_args.first.loc.expression
last_argument = second_call_args.last.loc.expression
range = first_argument.join(last_argument)
lambda do |corrector|
corrector.replace(range, combined_args)
add_offense(node, message: message(node, receiver, method, combined_args)) do |corrector|
autocorrect(corrector, first_call_args, second_call_args, combined_args)
end
end
private
def autocorrect(corrector, first_call_args, second_call_args, combined_args)
first_argument = first_call_args.first.loc.expression
last_argument = second_call_args.last.loc.expression
range = first_argument.join(last_argument)
corrector.replace(range, combined_args)
end
def process_source(node)
if check_for_active_support_aliases?
check_with_active_support_aliases(node)
@ -58,17 +53,14 @@ module RuboCop
end
end
def combine_args(first_call_args, second_call_args)
(first_call_args + second_call_args).map(&:source).join(', ')
def message(node, receiver, method, combined_args)
format(
MSG, receiver: receiver.source, method: method, combined_args: combined_args, original_code: node.source
)
end
def add_offense_for_double_call(node, receiver, method, combined_args)
msg = format(MSG, receiver: receiver.source,
method: method,
combined_args: combined_args,
original_code: node.source)
add_offense(node, message: msg)
def combine_args(first_call_args, second_call_args)
(first_call_args + second_call_args).map(&:source).join(', ')
end
def check_for_active_support_aliases?

View File

@ -41,8 +41,9 @@ module RuboCop
# 'abc'.match(/bc$/)
# /bc$/.match('abc')
#
class EndWith < Cop
class EndWith < Base
include RegexpMetacharacter
extend AutoCorrector
MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
'the end of the string.'
@ -54,25 +55,19 @@ module RuboCop
PATTERN
def on_send(node)
return unless redundant_regex?(node)
return unless (receiver, regex_str = redundant_regex?(node))
add_offense(node)
end
alias on_match_with_lvasgn on_send
def autocorrect(node)
redundant_regex?(node) do |receiver, regex_str|
add_offense(node) do |corrector|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
regex_str = drop_end_metacharacter(regex_str)
regex_str = interpret_string_escapes(regex_str)
lambda do |corrector|
new_source = receiver.source + '.end_with?(' +
to_string_literal(regex_str) + ')'
new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
corrector.replace(node.source_range, new_source)
end
end
end
alias on_match_with_lvasgn on_send
end
end
end

View File

@ -45,7 +45,7 @@ module RuboCop
# waldo = { a: corge, b: grault }
# waldo.size
#
class FixedSize < Cop
class FixedSize < Base
MSG = 'Do not compute the size of statically sized objects.'
def_node_matcher :counter, <<~MATCHER

View File

@ -14,8 +14,9 @@ module RuboCop
# [1, 2, 3, 4].flat_map { |e| [e, e] }
# [1, 2, 3, 4].map { |e| [e, e] }.flatten
# [1, 2, 3, 4].collect { |e| [e, e] }.flatten
class FlatMap < Cop
class FlatMap < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `flat_map` instead of `%<method>s...%<flatten>s`.'
FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
@ -44,25 +45,11 @@ module RuboCop
end
end
def autocorrect(node)
map_node, _first_method, _flatten, params = flat_map_candidate?(node)
flatten_level, = *params.first
return unless flatten_level
range = range_between(node.loc.dot.begin_pos,
node.source_range.end_pos)
lambda do |corrector|
corrector.remove(range)
corrector.replace(map_node.loc.selector, 'flat_map')
end
end
private
def offense_for_levels(node, map_node, first_method, flatten)
message = MSG + FLATTEN_MULTIPLE_LEVELS
register_offense(node, map_node, first_method, flatten, message)
end
@ -71,13 +58,24 @@ module RuboCop
end
def register_offense(node, map_node, first_method, flatten, message)
range = range_between(map_node.loc.selector.begin_pos,
node.loc.expression.end_pos)
range = range_between(map_node.loc.selector.begin_pos, node.loc.expression.end_pos)
message = format(message, method: first_method, flatten: flatten)
add_offense(node,
location: range,
message: format(message, method: first_method,
flatten: flatten))
add_offense(range, message: message) do |corrector|
autocorrect(corrector, node)
end
end
def autocorrect(corrector, node)
map_node, _first_method, _flatten, params = flat_map_candidate?(node)
flatten_level, = *params.first
return unless flatten_level
range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
corrector.remove(range)
corrector.replace(map_node.loc.selector, 'flat_map')
end
end
end

View File

@ -36,7 +36,9 @@ module RuboCop
# { a: 1, b: 2 }.has_value?('garbage')
# h = { a: 1, b: 2 }; h.value?(nil)
#
class InefficientHashSearch < Cop
class InefficientHashSearch < Base
extend AutoCorrector
def_node_matcher :inefficient_include?, <<~PATTERN
(send (send $_ {:keys :values}) :include? _)
PATTERN
@ -45,12 +47,8 @@ module RuboCop
inefficient_include?(node) do |receiver|
return if receiver.nil?
add_offense(node)
end
end
def autocorrect(node)
lambda do |corrector|
message = message(node)
add_offense(node, message: message) do |corrector|
# Replace `keys.include?` or `values.include?` with the appropriate
# `key?`/`value?` method.
corrector.replace(
@ -60,6 +58,7 @@ module RuboCop
)
end
end
end
private

View File

@ -24,8 +24,9 @@ module RuboCop
# file.each_line.find { |l| l.start_with?('#') }
# file.each_line { |l| puts l }
#
class IoReadlines < Cop
class IoReadlines < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
@ -39,23 +40,31 @@ module RuboCop
PATTERN
def on_send(node)
readlines_on_class?(node) do |enumerable_call, readlines_call|
offense(node, enumerable_call, readlines_call)
end
return unless (captured_values = readlines_on_class?(node) || readlines_on_instance?(node))
readlines_on_instance?(node) do |enumerable_call, readlines_call, _|
offense(node, enumerable_call, readlines_call)
enumerable_call, readlines_call, receiver = *captured_values
range = offense_range(enumerable_call, readlines_call)
good_method = build_good_method(enumerable_call)
bad_method = build_bad_method(enumerable_call)
add_offense(range, message: format(MSG, good: good_method, bad: bad_method)) do |corrector|
autocorrect(corrector, enumerable_call, readlines_call, receiver)
end
end
def autocorrect(node)
readlines_on_instance?(node) do |enumerable_call, readlines_call, receiver|
private
def enumerable_method?(node)
ENUMERABLE_METHODS.include?(node.to_sym)
end
def autocorrect(corrector, enumerable_call, readlines_call, receiver)
# We cannot safely correct `.readlines` method called on IO/File classes
# due to its signature and we are not sure with implicit receiver
# if it is called in the context of some instance or mentioned class.
return if receiver.nil?
lambda do |corrector|
range = correction_range(enumerable_call, readlines_call)
if readlines_call.arguments?
@ -67,26 +76,6 @@ module RuboCop
corrector.replace(range, replacement)
end
end
end
private
def enumerable_method?(node)
ENUMERABLE_METHODS.include?(node.to_sym)
end
def offense(node, enumerable_call, readlines_call)
range = offense_range(enumerable_call, readlines_call)
good_method = build_good_method(enumerable_call)
bad_method = build_bad_method(enumerable_call)
add_offense(
node,
location: range,
message: format(MSG, good: good_method, bad: bad_method)
)
end
def offense_range(enumerable_call, readlines_call)
readlines_pos = readlines_call.loc.selector.begin_pos

View File

@ -27,7 +27,7 @@ module RuboCop
# end
# end
#
class OpenStruct < Cop
class OpenStruct < Base
MSG = 'Consider using `Struct` over `OpenStruct` ' \
'to optimize the performance.'
@ -37,7 +37,7 @@ module RuboCop
def on_send(node)
open_struct(node) do
add_offense(node, location: :selector)
add_offense(node.loc.selector)
end
end
end

View File

@ -24,7 +24,9 @@ module RuboCop
# # the desired result:
#
# ('a'..'z').cover?('yellow') # => true
class RangeInclude < Cop
class RangeInclude < Base
extend AutoCorrector
MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
# TODO: If we traced out assignments of variables to their uses, we
@ -39,12 +41,11 @@ module RuboCop
def on_send(node)
range_include(node) do |bad_method|
message = format(MSG, bad_method: bad_method)
add_offense(node, location: :selector, message: message)
end
end
def autocorrect(node)
->(corrector) { corrector.replace(node.loc.selector, 'cover?') }
add_offense(node.loc.selector, message: message) do |corrector|
corrector.replace(node.loc.selector, 'cover?')
end
end
end
end
end

View File

@ -22,7 +22,9 @@ module RuboCop
# def another
# yield 1, 2, 3
# end
class RedundantBlockCall < Cop
class RedundantBlockCall < Base
extend AutoCorrector
MSG = 'Use `yield` instead of `%<argname>s.call`.'
YIELD = 'yield'
OPEN_PAREN = '('
@ -47,13 +49,17 @@ module RuboCop
next unless body
calls_to_report(argname, body).each do |blockcall|
add_offense(blockcall, message: format(MSG, argname: argname))
add_offense(blockcall, message: format(MSG, argname: argname)) do |corrector|
autocorrect(corrector, blockcall)
end
end
end
end
private
# offenses are registered on the `block.call` nodes
def autocorrect(node)
def autocorrect(corrector, node)
_receiver, _method, *args = *node
new_source = String.new(YIELD)
unless args.empty?
@ -67,10 +73,9 @@ module RuboCop
end
new_source << CLOSE_PAREN if parentheses?(node) && !args.empty?
->(corrector) { corrector.replace(node.source_range, new_source) }
end
private
corrector.replace(node.source_range, new_source)
end
def calls_to_report(argname, body)
return [] if blockarg_assigned?(body, argname)

View File

@ -17,7 +17,9 @@ module RuboCop
# # good
# method(str =~ /regex/)
# return value unless regex =~ 'str'
class RedundantMatch < Cop
class RedundantMatch < Base
extend AutoCorrector
MSG = 'Use `=~` in places where the `MatchData` returned by ' \
'`#match` will not be used.'
@ -37,18 +39,21 @@ module RuboCop
(!node.value_used? || only_truthiness_matters?(node)) &&
!(node.parent && node.parent.block_type?)
add_offense(node)
add_offense(node) do |corrector|
autocorrect(corrector, node)
end
end
def autocorrect(node)
private
def autocorrect(corrector, node)
# Regexp#match can take a second argument, but this cop doesn't
# register an offense in that case
return unless node.first_argument.regexp_type?
new_source =
node.receiver.source + ' =~ ' + node.first_argument.source
new_source = "#{node.receiver.source} =~ #{node.first_argument.source}"
->(corrector) { corrector.replace(node.source_range, new_source) }
corrector.replace(node.source_range, new_source)
end
end
end

View File

@ -24,7 +24,9 @@ module RuboCop
# # good
# hash[:a] = 1
# hash[:b] = 2
class RedundantMerge < Cop
class RedundantMerge < Base
extend AutoCorrector
AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
@ -44,18 +46,17 @@ module RuboCop
def on_send(node)
each_redundant_merge(node) do |redundant_merge_node|
add_offense(redundant_merge_node)
end
end
def autocorrect(node)
message = message(node)
add_offense(redundant_merge_node, message: message) do |corrector|
redundant_merge_candidate(node) do |receiver, pairs|
new_source = to_assignments(receiver, pairs).join("\n")
if node.parent && pairs.size > 1
correct_multiple_elements(node, node.parent, new_source)
correct_multiple_elements(corrector, node, node.parent, new_source)
else
correct_single_element(node, new_source)
correct_single_element(corrector, node, new_source)
end
end
end
end
end
@ -98,7 +99,7 @@ module RuboCop
!EachWithObjectInspector.new(node, receiver).value_used?
end
def correct_multiple_elements(node, parent, new_source)
def correct_multiple_elements(corrector, node, parent, new_source)
if modifier_flow_control?(parent)
new_source = rewrite_with_modifier(node, parent, new_source)
node = parent
@ -107,11 +108,11 @@ module RuboCop
new_source.gsub!(/\n/, padding)
end
->(corrector) { corrector.replace(node.source_range, new_source) }
corrector.replace(node.source_range, new_source)
end
def correct_single_element(node, new_source)
->(corrector) { corrector.replace(node.source_range, new_source) }
def correct_single_element(corrector, node, new_source)
corrector.replace(node.source_range, new_source)
end
def to_assignments(receiver, pairs)

View File

@ -13,29 +13,19 @@ module RuboCop
# # good
# array.sort
#
class RedundantSortBlock < Cop
class RedundantSortBlock < Base
include SortBlock
extend AutoCorrector
MSG = 'Use `sort` instead of `%<bad_method>s`.'
def on_block(node)
sort_with_block?(node) do |send, var_a, var_b, body|
return unless (send, var_a, var_b, body = sort_with_block?(node))
replaceable_body?(body, var_a, var_b) do
range = sort_range(send, node)
add_offense(
node,
location: range,
message: message(var_a, var_b)
)
end
end
end
def autocorrect(node)
sort_with_block?(node) do |send, _var_a, _var_b, _body|
lambda do |corrector|
range = sort_range(send, node)
add_offense(range, message: message(var_a, var_b)) do |corrector|
corrector.replace(range, 'sort')
end
end

View File

@ -39,8 +39,9 @@ module RuboCop
# str.size
# str.empty?
#
class RedundantStringChars < Cop
class RedundantStringChars < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
@ -50,23 +51,18 @@ module RuboCop
PATTERN
def on_send(node)
redundant_chars_call?(node) do |receiver, method, args|
return unless (receiver, method, args = redundant_chars_call?(node))
range = offense_range(receiver, node)
message = build_message(method, args)
add_offense(node, location: range, message: message)
end
end
def autocorrect(node)
redundant_chars_call?(node) do |receiver, method, args|
add_offense(range, message: message) do |corrector|
range = correction_range(receiver, node)
replacement = build_good_method(method, args)
lambda do |corrector|
corrector.replace(range, replacement)
end
end
end
private

View File

@ -72,7 +72,9 @@ module RuboCop
# do_something($~)
# end
# end
class RegexpMatch < Cop
class RegexpMatch < Base
extend AutoCorrector
# Constants are included in this list because it is unlikely that
# someone will store `nil` as a constant and then use it for comparison
TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
@ -141,8 +143,20 @@ module RuboCop
end
end
def autocorrect(node)
lambda do |corrector|
private
def check_condition(cond)
match_node?(cond) do
return if last_match_used?(cond)
message = message(cond)
add_offense(cond, message: message) do |corrector|
autocorrect(corrector, cond)
end
end
end
def autocorrect(corrector, node)
if match_method?(node) || match_with_int_arg_method?(node)
corrector.replace(node.loc.selector, 'match?')
elsif match_operator?(node) || match_threequals?(node)
@ -153,17 +167,6 @@ module RuboCop
correct_operator(corrector, recv, arg)
end
end
end
private
def check_condition(cond)
match_node?(cond) do
return if last_match_used?(cond)
add_offense(cond)
end
end
def message(node)
format(MSG, current: node.loc.selector.source)
@ -231,10 +234,7 @@ module RuboCop
def scope_root(node)
node.each_ancestor.find do |ancestor|
ancestor.def_type? ||
ancestor.defs_type? ||
ancestor.class_type? ||
ancestor.module_type?
ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type?
end
end

View File

@ -12,8 +12,9 @@ module RuboCop
#
# # good
# [].reverse_each
class ReverseEach < Cop
class ReverseEach < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `reverse_each` instead of `reverse.each`.'
UNDERSCORE = '_'
@ -29,13 +30,16 @@ module RuboCop
range = range_between(location_of_reverse, end_location)
add_offense(node, location: range)
add_offense(range) do |corrector|
corrector.replace(replacement_range(node), UNDERSCORE)
end
end
end
def autocorrect(node)
range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
->(corrector) { corrector.replace(range, UNDERSCORE) }
private
def replacement_range(node)
range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
end
end
end

View File

@ -16,8 +16,9 @@ module RuboCop
# array.last(5).reverse
# array.last
#
class ReverseFirst < Cop
class ReverseFirst < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
@ -30,16 +31,9 @@ module RuboCop
range = correction_range(receiver, node)
message = build_message(node)
add_offense(node, location: range, message: message)
end
end
def autocorrect(node)
reverse_first_candidate?(node) do |receiver|
range = correction_range(receiver, node)
add_offense(range, message: message) do |corrector|
replacement = build_good_method(node)
lambda do |corrector|
corrector.replace(range, replacement)
end
end

View File

@ -35,7 +35,9 @@ module RuboCop
# [1, 2, 3].count { |e| e > 2 }
# TODO: Add advanced detection of variables that could
# have been assigned to an array or a hash.
class Size < Cop
class Size < Base
extend AutoCorrector
MSG = 'Use `size` instead of `count`.'
def_node_matcher :array?, <<~PATTERN
@ -63,11 +65,9 @@ module RuboCop
def on_send(node)
return if node.parent&.block_type? || !count?(node)
add_offense(node, location: :selector)
end
def autocorrect(node)
->(corrector) { corrector.replace(node.loc.selector, 'size') }
add_offense(node.loc.selector) do |corrector|
corrector.replace(node.loc.selector, 'size')
end
end
end
end

View File

@ -13,8 +13,9 @@ module RuboCop
# # good
# array.sort.reverse
#
class SortReverse < Cop
class SortReverse < Base
include SortBlock
extend AutoCorrector
MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
@ -23,24 +24,14 @@ module RuboCop
replaceable_body?(body, var_b, var_a) do
range = sort_range(send, node)
add_offense(
node,
location: range,
message: message(var_a, var_b)
)
end
end
end
def autocorrect(node)
sort_with_block?(node) do |send, _var_a, _var_b, _body|
lambda do |corrector|
range = sort_range(send, node)
add_offense(range, message: message(var_a, var_b)) do |corrector|
replacement = 'sort.reverse'
corrector.replace(range, replacement)
end
end
end
end
private

View File

@ -18,7 +18,9 @@ module RuboCop
# str.squeeze('a')
# str.squeeze!('a')
#
class Squeeze < Cop
class Squeeze < Base
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
PREFERRED_METHODS = {
@ -36,24 +38,18 @@ module RuboCop
PATTERN
def on_send(node)
squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
regexp_str = regexp_str[0..-2] # delete '+' from the end
regexp_str = interpret_string_escapes(regexp_str)
return unless replace_str == regexp_str
good_method = PREFERRED_METHODS[bad_method]
message = format(MSG, current: bad_method, prefer: good_method)
add_offense(node, location: :selector, message: message)
end
end
def autocorrect(node)
squeeze_candidate?(node) do |receiver, bad_method, _regexp_str, replace_str|
lambda do |corrector|
good_method = PREFERRED_METHODS[bad_method]
add_offense(node.loc.selector, message: message) do |corrector|
string_literal = to_string_literal(replace_str)
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
corrector.replace(node.source_range, new_code)
end
end

View File

@ -41,8 +41,9 @@ module RuboCop
# 'abc'.match(/^ab/)
# /^ab/.match('abc')
#
class StartWith < Cop
class StartWith < Base
include RegexpMetacharacter
extend AutoCorrector
MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
'the beginning of the string.'
@ -54,25 +55,19 @@ module RuboCop
PATTERN
def on_send(node)
return unless redundant_regex?(node)
return unless (receiver, regex_str = redundant_regex?(node))
add_offense(node)
end
alias on_match_with_lvasgn on_send
def autocorrect(node)
redundant_regex?(node) do |receiver, regex_str|
add_offense(node) do |corrector|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
regex_str = drop_start_metacharacter(regex_str)
regex_str = interpret_string_escapes(regex_str)
lambda do |corrector|
new_source = receiver.source + '.start_with?(' +
to_string_literal(regex_str) + ')'
new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
corrector.replace(node.source_range, new_source)
end
end
end
alias on_match_with_lvasgn on_send
end
end
end

View File

@ -19,7 +19,9 @@ module RuboCop
#
# # good
# 'abc'.include?('ab')
class StringInclude < Cop
class StringInclude < Base
extend AutoCorrector
MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
def_node_matcher :redundant_regex?, <<~PATTERN
@ -29,24 +31,18 @@ module RuboCop
PATTERN
def on_send(node)
return unless redundant_regex?(node)
return unless (receiver, regex_str = redundant_regex?(node))
add_offense(node)
end
alias on_match_with_lvasgn on_send
def autocorrect(node)
redundant_regex?(node) do |receiver, regex_str|
add_offense(node) do |corrector|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
regex_str = interpret_string_escapes(regex_str)
lambda do |corrector|
new_source = receiver.source + '.include?(' +
to_string_literal(regex_str) + ')'
new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
corrector.replace(node.source_range, new_source)
end
end
end
alias on_match_with_lvasgn on_send
private

View File

@ -18,8 +18,9 @@ module RuboCop
# 'abc'.gsub(/a+/, 'd')
# 'abc'.tr('b', 'd')
# 'a b c'.delete(' ')
class StringReplacement < Cop
class StringReplacement < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
@ -42,33 +43,37 @@ module RuboCop
end
end
def autocorrect(node)
private
def offense(node, first_param, second_param)
first_source, = first_source(first_param)
first_source = interpret_string_escapes(first_source) unless first_param.str_type?
second_source, = *second_param
message = message(node, first_source, second_source)
add_offense(range(node), message: message) do |corrector|
autocorrect(corrector, node)
end
end
def autocorrect(corrector, node)
_string, _method, first_param, second_param = *node
first_source, = first_source(first_param)
second_source, = *second_param
first_source = interpret_string_escapes(first_source) unless first_param.str_type?
replacement_method =
replacement_method(node, first_source, second_source)
replace_method(node, first_source, second_source, first_param,
replacement_method)
replace_method(corrector, node, first_source, second_source, first_param)
end
def replace_method(node, first, second, first_param, replacement)
lambda do |corrector|
corrector.replace(node.loc.selector, replacement)
unless first_param.str_type?
corrector.replace(first_param.source_range,
to_string_literal(first))
end
def replace_method(corrector, node, first_source, second_source, first_param)
replacement_method = replacement_method(node, first_source, second_source)
remove_second_param(corrector, node, first_param) if second.empty? && first.length == 1
end
end
corrector.replace(node.loc.selector, replacement_method)
corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type?
private
remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1
end
def accept_second_param?(second_param)
second_source, = *second_param
@ -92,15 +97,6 @@ module RuboCop
first_source.length != 1
end
def offense(node, first_param, second_param)
first_source, = first_source(first_param)
first_source = interpret_string_escapes(first_source) unless first_param.str_type?
second_source, = *second_param
message = message(node, first_source, second_source)
add_offense(node, location: range(node), message: message)
end
def first_source(first_param)
case first_param.type
when :regexp

View File

@ -0,0 +1,129 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# This cop identifies places where custom code finding the sum of elements
# in some Enumerable object can be replaced by `Enumerable#sum` method.
#
# @example
# # bad
# [1, 2, 3].inject(:+)
# [1, 2, 3].reduce(10, :+)
# [1, 2, 3].reduce { |acc, elem| acc + elem }
#
# # good
# [1, 2, 3].sum
# [1, 2, 3].sum(10)
# [1, 2, 3].sum
#
class Sum < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
def_node_matcher :sum_candidate?, <<~PATTERN
(send _ ${:inject :reduce} $_init ? (sym :+))
PATTERN
def_node_matcher :sum_with_block_candidate?, <<~PATTERN
(block
$(send _ {:inject :reduce} $_init ?)
(args (arg $_acc) (arg $_elem))
$send)
PATTERN
def_node_matcher :acc_plus_elem?, <<~PATTERN
(send (lvar %1) :+ (lvar %2))
PATTERN
alias elem_plus_acc? acc_plus_elem?
def on_send(node)
sum_candidate?(node) do |method, init|
range = sum_method_range(node)
message = build_method_message(method, init)
add_offense(range, message: message) do |corrector|
autocorrect(corrector, init, range)
end
end
end
def on_block(node)
sum_with_block_candidate?(node) do |send, init, var_acc, var_elem, body|
if acc_plus_elem?(body, var_acc, var_elem) || elem_plus_acc?(body, var_elem, var_acc)
range = sum_block_range(send, node)
message = build_block_message(send, init, var_acc, var_elem, body)
add_offense(range, message: message) do |corrector|
autocorrect(corrector, init, range)
end
end
end
end
private
def autocorrect(corrector, init, range)
return if init.empty?
replacement = build_good_method(init)
corrector.replace(range, replacement)
end
def sum_method_range(node)
range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
end
def sum_block_range(send, node)
range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
end
def build_method_message(method, init)
good_method = build_good_method(init)
bad_method = build_method_bad_method(init, method)
format(MSG, good_method: good_method, bad_method: bad_method)
end
def build_block_message(send, init, var_acc, var_elem, body)
good_method = build_good_method(init)
bad_method = build_block_bad_method(send.method_name, init, var_acc, var_elem, body)
format(MSG, good_method: good_method, bad_method: bad_method)
end
def build_good_method(init)
good_method = 'sum'
unless init.empty?
init = init.first
good_method += "(#{init.source})" if init.source.to_i != 0
end
good_method
end
def build_method_bad_method(init, method)
bad_method = "#{method}("
unless init.empty?
init = init.first
bad_method += "#{init.source}, "
end
bad_method += ':+)'
bad_method
end
def build_block_bad_method(method, init, var_acc, var_elem, body)
bad_method = method.to_s
unless init.empty?
init = init.first
bad_method += "(#{init.source})"
end
bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
bad_method
end
end
end
end
end

View File

@ -17,7 +17,9 @@ module RuboCop
# Array.new(9) do |i|
# i.to_s
# end
class TimesMap < Cop
class TimesMap < Base
extend AutoCorrector
MESSAGE = 'Use `Array.new(%<count>s)` with a block ' \
'instead of `.times.%<map_or_collect>s`'
MESSAGE_ONLY_IF = 'only if `%<count>s` is always 0 or more'
@ -30,35 +32,26 @@ module RuboCop
check(node)
end
def autocorrect(node)
map_or_collect, count = times_map_call(node)
replacement =
"Array.new(#{count.source}" \
"#{map_or_collect.arguments.map { |arg| ", #{arg.source}" }.join})"
lambda do |corrector|
corrector.replace(map_or_collect.loc.expression, replacement)
end
end
private
def check(node)
times_map_call(node) do |map_or_collect, count|
add_offense(node, message: message(map_or_collect, count))
add_offense(node, message: message(map_or_collect, count)) do |corrector|
replacement = "Array.new(#{count.source}" \
"#{map_or_collect.arguments.map { |arg| ", #{arg.source}" }.join})"
corrector.replace(map_or_collect.loc.expression, replacement)
end
end
end
def message(map_or_collect, count)
template = if count.literal?
MESSAGE + '.'
"#{MESSAGE}."
else
"#{MESSAGE} #{MESSAGE_ONLY_IF}."
end
format(template,
count: count.source,
map_or_collect: map_or_collect.method_name)
format(template, count: count.source, map_or_collect: map_or_collect.method_name)
end
def_node_matcher :times_map_call, <<~PATTERN

View File

@ -23,7 +23,7 @@ module RuboCop
# # good
# +'something'
# +''
class UnfreezeString < Cop
class UnfreezeString < Base
MSG = 'Use unary plus to get an unfrozen string literal.'
def_node_matcher :dup_string?, <<~PATTERN

View File

@ -13,7 +13,9 @@ module RuboCop
# # good
# URI::DEFAULT_PARSER
#
class UriDefaultParser < Cop
class UriDefaultParser < Base
extend AutoCorrector
MSG = 'Use `%<double_colon>sURI::DEFAULT_PARSER` instead of ' \
'`%<double_colon>sURI::Parser.new`.'
@ -28,17 +30,9 @@ module RuboCop
double_colon = captured_value ? '::' : ''
message = format(MSG, double_colon: double_colon)
add_offense(node, message: message)
end
end
def autocorrect(node)
lambda do |corrector|
double_colon = uri_parser_new?(node) ? '::' : ''
corrector.replace(
node.loc.expression, "#{double_colon}URI::DEFAULT_PARSER"
)
add_offense(node, message: message) do |corrector|
corrector.replace(node.loc.expression, "#{double_colon}URI::DEFAULT_PARSER")
end
end
end
end

View File

@ -9,6 +9,7 @@ require_relative 'performance/bind_call'
require_relative 'performance/caller'
require_relative 'performance/case_when_splat'
require_relative 'performance/casecmp'
require_relative 'performance/collection_literal_in_loop'
require_relative 'performance/compare_with_block'
require_relative 'performance/count'
require_relative 'performance/delete_prefix'
@ -36,6 +37,7 @@ require_relative 'performance/squeeze'
require_relative 'performance/start_with'
require_relative 'performance/string_include'
require_relative 'performance/string_replacement'
require_relative 'performance/sum'
require_relative 'performance/times_map'
require_relative 'performance/unfreeze_string'
require_relative 'performance/uri_default_parser'