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:
commit
16ee849c43
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
@ -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(range(node)) do |corrector|
|
||||
subclass_source = subclass ? subclass.source : 'self'
|
||||
|
||||
add_offense(node, location: range)
|
||||
corrector.replace(node, "#{subclass_source} <= #{superclass.source}")
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
ancestors_include_candidate?(node) do |subclass, superclass|
|
||||
lambda do |corrector|
|
||||
subclass_source = subclass ? subclass.source : 'self'
|
||||
private
|
||||
|
||||
corrector.replace(node, "#{subclass_source} <= #{superclass.source}")
|
||||
end
|
||||
end
|
||||
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
|
||||
@ -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,18 +26,11 @@ 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|
|
||||
corrector.wrap(numeric, "'", "'")
|
||||
end
|
||||
add_offense(numeric.source_range) do |corrector|
|
||||
corrector.wrap(numeric, "'", "'")
|
||||
end
|
||||
end
|
||||
|
||||
@ -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|
|
||||
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)
|
||||
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)
|
||||
call_args = ", #{call_args}" unless call_args.empty?
|
||||
message = message(bind_arg.source, call_args)
|
||||
|
||||
replacement_method = "bind_call(#{bind_arg.source}#{call_args})"
|
||||
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
|
||||
@ -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
|
||||
@ -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,25 +67,31 @@ 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)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(when_node)
|
||||
lambda do |corrector|
|
||||
if needs_reorder?(when_node)
|
||||
reorder_condition(corrector, when_node)
|
||||
else
|
||||
inline_fix_branch(corrector, when_node)
|
||||
add_offense(range(condition), message: message) do |corrector|
|
||||
autocorrect(corrector, condition.parent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def range(node)
|
||||
node.parent.loc.keyword.join(node.source_range)
|
||||
end
|
||||
|
||||
def replacement(conditions)
|
||||
reordered = conditions.partition(&:splat_type?).reverse
|
||||
reordered.flatten.map(&:source).join(', ')
|
||||
@ -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)
|
||||
)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
return unless (parts = take_method_apart(node))
|
||||
|
||||
receiver, method, arg, variable = parts
|
||||
|
||||
correction(node, receiver, method, arg, variable)
|
||||
message = format(MSG, good: good_method, bad: node.source)
|
||||
add_offense(node, message: message) do |corrector|
|
||||
correction(corrector, node, method, arg, variable)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@ -84,14 +78,12 @@ module RuboCop
|
||||
[receiver, method, arg, variable]
|
||||
end
|
||||
|
||||
def correction(node, _receiver, method, arg, variable)
|
||||
lambda do |corrector|
|
||||
corrector.insert_before(node.loc.expression, '!') if method == :!=
|
||||
def correction(corrector, node, method, arg, variable)
|
||||
corrector.insert_before(node.loc.expression, '!') if method == :!=
|
||||
|
||||
replacement = build_good_method(arg, variable)
|
||||
replacement = build_good_method(arg, variable)
|
||||
|
||||
corrector.replace(node.loc.expression, replacement)
|
||||
end
|
||||
corrector.replace(node.loc.expression, replacement)
|
||||
end
|
||||
|
||||
def build_good_method(arg, variable)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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}] }"
|
||||
else
|
||||
"#{send.method_name}_by(&:#{method})"
|
||||
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(range, replacement)
|
||||
end
|
||||
corrector.replace(compare_range(send, node),
|
||||
replacement)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -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,29 +58,25 @@ 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
|
||||
corrector.remove(range)
|
||||
corrector.replace(selector_loc, 'count')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eligible_node?(node)
|
||||
!(node.parent && node.parent.block_type?)
|
||||
end
|
||||
@ -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 replace_string.blank?
|
||||
return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
|
||||
return unless replace_string.blank?
|
||||
|
||||
good_method = PREFERRED_METHODS[bad_method]
|
||||
good_method = PREFERRED_METHODS[bad_method]
|
||||
|
||||
message = format(MSG, current: bad_method, prefer: good_method)
|
||||
message = format(MSG, current: bad_method, prefer: good_method)
|
||||
|
||||
add_offense(node, location: :selector, message: message)
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
def autocorrect(node)
|
||||
delete_prefix_candidate?(node) do |receiver, bad_method, regexp_str, _|
|
||||
lambda do |corrector|
|
||||
good_method = PREFERRED_METHODS[bad_method]
|
||||
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})"
|
||||
|
||||
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
|
||||
@ -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 replace_string.blank?
|
||||
return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
|
||||
return unless replace_string.blank?
|
||||
|
||||
good_method = PREFERRED_METHODS[bad_method]
|
||||
good_method = PREFERRED_METHODS[bad_method]
|
||||
|
||||
message = format(MSG, current: bad_method, prefer: good_method)
|
||||
message = format(MSG, current: bad_method, prefer: good_method)
|
||||
|
||||
add_offense(node, location: :selector, message: message)
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
def autocorrect(node)
|
||||
delete_suffix_candidate?(node) do |receiver, bad_method, regexp_str, _|
|
||||
lambda do |corrector|
|
||||
good_method = PREFERRED_METHODS[bad_method]
|
||||
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})"
|
||||
|
||||
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
|
||||
@ -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)
|
||||
@ -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?
|
||||
@ -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) + ')'
|
||||
corrector.replace(node.source_range, new_source)
|
||||
end
|
||||
new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
|
||||
|
||||
corrector.replace(node.source_range, new_source)
|
||||
end
|
||||
end
|
||||
alias on_match_with_lvasgn on_send
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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,19 +47,16 @@ module RuboCop
|
||||
inefficient_include?(node) do |receiver|
|
||||
return if receiver.nil?
|
||||
|
||||
add_offense(node)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
# Replace `keys.include?` or `values.include?` with the appropriate
|
||||
# `key?`/`value?` method.
|
||||
corrector.replace(
|
||||
node.loc.expression,
|
||||
"#{autocorrect_hash_expression(node)}."\
|
||||
"#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
|
||||
)
|
||||
message = message(node)
|
||||
add_offense(node, message: message) do |corrector|
|
||||
# Replace `keys.include?` or `values.include?` with the appropriate
|
||||
# `key?`/`value?` method.
|
||||
corrector.replace(
|
||||
node.loc.expression,
|
||||
"#{autocorrect_hash_expression(node)}."\
|
||||
"#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -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,34 +40,16 @@ 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)
|
||||
end
|
||||
end
|
||||
enumerable_call, readlines_call, receiver = *captured_values
|
||||
|
||||
def autocorrect(node)
|
||||
readlines_on_instance?(node) do |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?
|
||||
range = offense_range(enumerable_call, readlines_call)
|
||||
good_method = build_good_method(enumerable_call)
|
||||
bad_method = build_bad_method(enumerable_call)
|
||||
|
||||
lambda do |corrector|
|
||||
range = correction_range(enumerable_call, readlines_call)
|
||||
|
||||
if readlines_call.arguments?
|
||||
call_args = build_call_args(readlines_call.arguments)
|
||||
replacement = "each_line(#{call_args})"
|
||||
else
|
||||
replacement = 'each_line'
|
||||
end
|
||||
|
||||
corrector.replace(range, replacement)
|
||||
end
|
||||
add_offense(range, message: format(MSG, good: good_method, bad: bad_method)) do |corrector|
|
||||
autocorrect(corrector, enumerable_call, readlines_call, receiver)
|
||||
end
|
||||
end
|
||||
|
||||
@ -76,16 +59,22 @@ module RuboCop
|
||||
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)
|
||||
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?
|
||||
|
||||
add_offense(
|
||||
node,
|
||||
location: range,
|
||||
message: format(MSG, good: good_method, bad: bad_method)
|
||||
)
|
||||
range = correction_range(enumerable_call, readlines_call)
|
||||
|
||||
if readlines_call.arguments?
|
||||
call_args = build_call_args(readlines_call.arguments)
|
||||
replacement = "each_line(#{call_args})"
|
||||
else
|
||||
replacement = 'each_line'
|
||||
end
|
||||
|
||||
corrector.replace(range, replacement)
|
||||
end
|
||||
|
||||
def offense_range(enumerable_call, readlines_call)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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
|
||||
@ -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
|
||||
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")
|
||||
|
||||
def autocorrect(node)
|
||||
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)
|
||||
else
|
||||
correct_single_element(node, new_source)
|
||||
if node.parent && pairs.size > 1
|
||||
correct_multiple_elements(corrector, node, node.parent, new_source)
|
||||
else
|
||||
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)
|
||||
@ -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|
|
||||
replaceable_body?(body, var_a, var_b) do
|
||||
range = sort_range(send, node)
|
||||
return unless (send, var_a, var_b, body = sort_with_block?(node))
|
||||
|
||||
add_offense(
|
||||
node,
|
||||
location: range,
|
||||
message: message(var_a, var_b)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
replaceable_body?(body, var_a, var_b) do
|
||||
range = sort_range(send, node)
|
||||
|
||||
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
|
||||
@ -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,21 +51,16 @@ module RuboCop
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
redundant_chars_call?(node) do |receiver, method, args|
|
||||
range = offense_range(receiver, node)
|
||||
message = build_message(method, args)
|
||||
add_offense(node, location: range, message: message)
|
||||
end
|
||||
end
|
||||
return unless (receiver, method, args = redundant_chars_call?(node))
|
||||
|
||||
def autocorrect(node)
|
||||
redundant_chars_call?(node) do |receiver, method, args|
|
||||
range = offense_range(receiver, node)
|
||||
message = build_message(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
|
||||
corrector.replace(range, replacement)
|
||||
end
|
||||
end
|
||||
|
||||
@ -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,27 +143,28 @@ module RuboCop
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
if match_method?(node) || match_with_int_arg_method?(node)
|
||||
corrector.replace(node.loc.selector, 'match?')
|
||||
elsif match_operator?(node) || match_threequals?(node)
|
||||
recv, oper, arg = *node
|
||||
correct_operator(corrector, recv, arg, oper)
|
||||
elsif match_with_lvasgn?(node)
|
||||
recv, arg = *node
|
||||
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)
|
||||
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)
|
||||
recv, oper, arg = *node
|
||||
correct_operator(corrector, recv, arg, oper)
|
||||
elsif match_with_lvasgn?(node)
|
||||
recv, arg = *node
|
||||
correct_operator(corrector, recv, arg)
|
||||
end
|
||||
end
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
add_offense(range, message: message) do |corrector|
|
||||
replacement = build_good_method(node)
|
||||
|
||||
def autocorrect(node)
|
||||
reverse_first_candidate?(node) do |receiver|
|
||||
range = correction_range(receiver, node)
|
||||
replacement = build_good_method(node)
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.replace(range, replacement)
|
||||
end
|
||||
end
|
||||
@ -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
|
||||
@ -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,21 +24,11 @@ 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
|
||||
add_offense(range, message: message(var_a, var_b)) do |corrector|
|
||||
replacement = 'sort.reverse'
|
||||
|
||||
def autocorrect(node)
|
||||
sort_with_block?(node) do |send, _var_a, _var_b, _body|
|
||||
lambda do |corrector|
|
||||
range = sort_range(send, node)
|
||||
replacement = 'sort.reverse'
|
||||
corrector.replace(range, replacement)
|
||||
corrector.replace(range, replacement)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -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
|
||||
@ -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) + ')'
|
||||
corrector.replace(node.source_range, new_source)
|
||||
end
|
||||
new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
|
||||
|
||||
corrector.replace(node.source_range, new_source)
|
||||
end
|
||||
end
|
||||
alias on_match_with_lvasgn on_send
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -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) + ')'
|
||||
corrector.replace(node.source_range, new_source)
|
||||
end
|
||||
new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
|
||||
|
||||
corrector.replace(node.source_range, new_source)
|
||||
end
|
||||
end
|
||||
alias on_match_with_lvasgn on_send
|
||||
|
||||
private
|
||||
|
||||
@ -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,34 +43,38 @@ 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
|
||||
corrector.replace(node.loc.selector, replacement_method)
|
||||
corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type?
|
||||
|
||||
remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def accept_second_param?(second_param)
|
||||
second_source, = *second_param
|
||||
second_source.length > 1
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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'
|
||||
@ -3,7 +3,7 @@
|
||||
module RuboCop
|
||||
module Performance
|
||||
module Version
|
||||
STRING = '1.7.1'
|
||||
STRING = '1.8.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user