Merge branch 'master' into mlh-outdated-packages

This commit is contained in:
Elizabeth Tackett 2020-08-19 09:39:26 -05:00
commit d6f2a1c6fa
157 changed files with 1491 additions and 933 deletions

View File

@ -43,3 +43,5 @@ RSpec/MultipleExpectations:
Max: 26 Max: 26
RSpec/NestedGroups: RSpec/NestedGroups:
Max: 5 Max: 5
RSpec/MultipleMemoizedHelpers:
Max: 12

View File

@ -3,6 +3,8 @@
require "English" require "English"
SimpleCov.enable_for_subprocesses true
SimpleCov.start do SimpleCov.start do
coverage_dir File.expand_path("../test/coverage", File.realpath(__FILE__)) coverage_dir File.expand_path("../test/coverage", File.realpath(__FILE__))
root File.expand_path("..", File.realpath(__FILE__)) root File.expand_path("..", File.realpath(__FILE__))
@ -12,9 +14,21 @@ SimpleCov.start do
# tests to be dropped. This causes random fluctuations in test coverage. # tests to be dropped. This causes random fluctuations in test coverage.
merge_timeout 86400 merge_timeout 86400
at_fork do |pid|
# This needs a unique name so it won't be ovewritten
command_name "#{SimpleCov.command_name} (#{pid})"
# be quiet, the parent process will be in charge of output and checking coverage totals
print_error_status = false
end
if ENV["HOMEBREW_INTEGRATION_TEST"] if ENV["HOMEBREW_INTEGRATION_TEST"]
# This needs a unique name so it won't be ovewritten
command_name "#{ENV["HOMEBREW_INTEGRATION_TEST"]} (#{$PROCESS_ID})" command_name "#{ENV["HOMEBREW_INTEGRATION_TEST"]} (#{$PROCESS_ID})"
# be quiet, the parent process will be in charge of output and checking coverage totals
print_error_status = false
at_exit do at_exit do
exit_code = $ERROR_INFO.nil? ? 0 : $ERROR_INFO.status exit_code = $ERROR_INFO.nil? ? 0 : $ERROR_INFO.status
$stdout.reopen("/dev/null") $stdout.reopen("/dev/null")

View File

@ -10,7 +10,7 @@ GEM
ast (2.4.1) ast (2.4.1)
bindata (2.4.8) bindata (2.4.8)
byebug (11.1.3) byebug (11.1.3)
codecov (0.2.5) codecov (0.2.6)
colorize colorize
json json
simplecov simplecov
@ -112,11 +112,11 @@ GEM
parser (>= 2.7.1.4) parser (>= 2.7.1.4)
rubocop-performance (1.7.1) rubocop-performance (1.7.1)
rubocop (>= 0.82.0) rubocop (>= 0.82.0)
rubocop-rspec (1.42.0) rubocop-rspec (1.43.1)
rubocop (>= 0.87.0) rubocop (~> 0.87)
ruby-macho (2.2.0) ruby-macho (2.2.0)
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
simplecov (0.18.5) simplecov (0.19.0)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov-html (0.12.2) simplecov-html (0.12.2)

View File

@ -6,12 +6,18 @@ case "$HOMEBREW_SYSTEM" in
esac esac
# Force UTF-8 to avoid encoding issues for users with broken locale settings. # Force UTF-8 to avoid encoding issues for users with broken locale settings.
if [[ "$(locale charmap 2>/dev/null)" != "UTF-8" ]] if [[ -n "$HOMEBREW_MACOS" ]]
then then
if [[ -n "$HOMEBREW_MACOS" ]] if [[ "$(locale charmap)" != "UTF-8" ]]
then then
export LC_ALL="en_US.UTF-8" export LC_ALL="en_US.UTF-8"
else fi
else
if ! command -v locale >/dev/null
then
export LC_ALL=C
elif [[ "$(locale charmap)" != "UTF-8" ]]
then
locales=$(locale -a) locales=$(locale -a)
c_utf_regex='\bC\.(utf8|UTF-8)\b' c_utf_regex='\bC\.(utf8|UTF-8)\b'
en_us_regex='\ben_US\.(utf8|UTF-8)\b' en_us_regex='\ben_US\.(utf8|UTF-8)\b'

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
# Settings for the build environment.
#
# @api private
class BuildEnvironment class BuildEnvironment
def initialize(*settings) def initialize(*settings)
@settings = Set.new(*settings) @settings = Set.new(*settings)
@ -23,19 +26,15 @@ class BuildEnvironment
@settings.include? :userpaths @settings.include? :userpaths
end end
# DSL for specifying build environment settings.
module DSL module DSL
def env(*settings) def env(*settings)
@env ||= BuildEnvironment.new @env ||= BuildEnvironment.new
@env.merge(settings) @env.merge(settings)
end end
end end
end
module Homebrew KEYS = %w[
module_function
def build_env_keys(env)
%w[
CC CXX LD OBJC OBJCXX CC CXX LD OBJC OBJCXX
HOMEBREW_CC HOMEBREW_CXX HOMEBREW_CC HOMEBREW_CXX
CFLAGS CXXFLAGS CPPFLAGS LDFLAGS SDKROOT MAKEFLAGS CFLAGS CXXFLAGS CPPFLAGS LDFLAGS SDKROOT MAKEFLAGS
@ -47,11 +46,15 @@ module Homebrew
MAKE GIT CPP MAKE GIT CPP
ACLOCAL_PATH PATH CPATH ACLOCAL_PATH PATH CPATH
LD_LIBRARY_PATH LD_RUN_PATH LD_PRELOAD LIBRARY_PATH LD_LIBRARY_PATH LD_RUN_PATH LD_PRELOAD LIBRARY_PATH
].select { |key| env.key?(key) } ].freeze
private_constant :KEYS
def self.keys(env)
KEYS & env.keys
end end
def dump_build_env(env, f = $stdout) def self.dump(env, f = $stdout)
keys = build_env_keys(env) keys = self.keys(env)
keys -= %w[CC CXX OBJC OBJCXX] if env["CC"] == env["HOMEBREW_CC"] keys -= %w[CC CXX OBJC OBJCXX] if env["CC"] == env["HOMEBREW_CC"]
keys.each do |key| keys.each do |key|

View File

@ -18,6 +18,10 @@ module Cask
def to_a def to_a
[true] [true]
end end
def summarize
"true"
end
end end
end end
end end

View File

@ -47,6 +47,16 @@ module Cask
"dr" => "doctor", "dr" => "doctor",
}.freeze }.freeze
DEPRECATED_COMMANDS = {
Cmd::Cache => "brew --cache --cask",
Cmd::Doctor => "brew doctor --verbose",
Cmd::Home => "brew home",
Cmd::List => "brew list --cask",
Cmd::Outdated => "brew outdated --cask",
Cmd::Reinstall => "brew reinstall",
Cmd::Upgrade => "brew upgrade --cask",
}.freeze
def self.description def self.description
max_command_len = Cmd.commands.map(&:length).max max_command_len = Cmd.commands.map(&:length).max
@ -212,6 +222,11 @@ module Cask
detect_external_command(*argv) || detect_external_command(*argv) ||
[args.remaining.empty? ? NullCommand : UnknownSubcommand.new(args.remaining.first), argv] [args.remaining.empty? ? NullCommand : UnknownSubcommand.new(args.remaining.first), argv]
# TODO: enable for next major/minor release
# if (replacement = DEPRECATED_COMMANDS[command])
# odeprecated "brew cask #{command.command_name}", replacement
# end
if args.help? if args.help?
puts command.help puts command.help
else else

View File

@ -28,9 +28,9 @@ module Cask
switch "--online", switch "--online",
description: "Run additional, slower style checks that require a network connection" description: "Run additional, slower style checks that require a network connection"
switch "--new-cask", switch "--new-cask",
description: "Run various additional style checks to determine if a new cask is eligible description: "Run various additional style checks to determine if a new cask is eligible " \
for Homebrew. This should be used when creating new casks and implies "for Homebrew. This should be used when creating new casks and implies " \
`--strict` and `--online`" "`--strict` and `--online`"
end end
end end

View File

@ -8,8 +8,6 @@ module Cask
end end
def run def run
# odeprecated "brew cask home", "brew home"
if casks.none? if casks.none?
odebug "Opening project homepage" odebug "Opening project homepage"
self.class.open_url "https://brew.sh/" self.class.open_url "https://brew.sh/"

View File

@ -6,9 +6,16 @@ require "formula"
require "cask/cask_loader" require "cask/cask_loader"
require "set" require "set"
CLEANUP_DEFAULT_DAYS = 30 module Homebrew
# Helper class for cleaning up the Homebrew cache.
#
# @api private
class Cleanup
CLEANUP_DEFAULT_DAYS = 30
private_constant :CLEANUP_DEFAULT_DAYS
module CleanupRefinement # `Pathname` refinement with helper functions for cleaning up files.
module CleanupRefinement
refine Pathname do refine Pathname do
def incomplete? def incomplete?
extname.end_with?(".incomplete") extname.end_with?(".incomplete")
@ -120,12 +127,10 @@ module CleanupRefinement
false false
end end
end end
end end
using CleanupRefinement using CleanupRefinement
module Homebrew
class Cleanup
extend Predicable extend Predicable
PERIODIC_CLEAN_FILE = (HOMEBREW_CACHE/".cleaned").freeze PERIODIC_CLEAN_FILE = (HOMEBREW_CACHE/".cleaned").freeze

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cli/named_args"
require "ostruct" require "ostruct"
module Homebrew module Homebrew
@ -19,7 +20,7 @@ module Homebrew
# Can set these because they will be overwritten by freeze_named_args! # Can set these because they will be overwritten by freeze_named_args!
# (whereas other values below will only be overwritten if passed). # (whereas other values below will only be overwritten if passed).
self[:named_args] = [] self[:named_args] = NamedArgs.new
self[:remaining] = [] self[:remaining] = []
end end
@ -28,18 +29,12 @@ module Homebrew
end end
def freeze_named_args!(named_args) def freeze_named_args!(named_args)
# Reset cache values reliant on named_args self[:named_args] = NamedArgs.new(
@formulae = nil *named_args.freeze,
@formulae_and_casks = nil override_spec: spec(nil),
@resolved_formulae = nil force_bottle: force_bottle?,
@resolved_formulae_casks = nil flags: flags_only,
@formulae_paths = nil )
@casks = nil
@loaded_casks = nil
@kegs = nil
@kegs_casks = nil
self[:named_args] = named_args.freeze
end end
def freeze_processed_options!(processed_options) def freeze_processed_options!(processed_options)
@ -54,7 +49,7 @@ module Homebrew
end end
def named def named
named_args || [] named_args || NamedArgs.new
end end
def no_named? def no_named?
@ -62,102 +57,39 @@ module Homebrew
end end
def formulae def formulae
require "formula" named.to_formulae
@formulae ||= (downcased_unique_named - casks).map do |name|
Formulary.factory(name, spec, force_bottle: force_bottle?, flags: flags_only)
end.uniq(&:name).freeze
end end
def formulae_and_casks def formulae_and_casks
@formulae_and_casks ||= begin named.to_formulae_and_casks
formulae_and_casks = []
downcased_unique_named.each do |name|
formulae_and_casks << Formulary.factory(name, spec)
rescue FormulaUnavailableError
begin
formulae_and_casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
formulae_and_casks.freeze
end
end end
def resolved_formulae def resolved_formulae
require "formula" named.to_resolved_formulae
@resolved_formulae ||= (downcased_unique_named - casks).map do |name|
Formulary.resolve(name, spec: spec(nil), force_bottle: force_bottle?, flags: flags_only)
end.uniq(&:name).freeze
end end
def resolved_formulae_casks def resolved_formulae_casks
@resolved_formulae_casks ||= begin named.to_resolved_formulae_to_casks
resolved_formulae = []
casks = []
downcased_unique_named.each do |name|
resolved_formulae << Formulary.resolve(name, spec: spec(nil),
force_bottle: force_bottle?, flags: flags_only)
rescue FormulaUnavailableError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
[resolved_formulae.freeze, casks.freeze].freeze
end
end end
def formulae_paths def formulae_paths
@formulae_paths ||= (downcased_unique_named - casks).map do |name| named.to_formulae_paths
Formulary.path(name)
end.uniq.freeze
end end
def casks def casks
@casks ||= downcased_unique_named.grep(HOMEBREW_CASK_TAP_CASK_REGEX) named.homebrew_tap_cask_names
.freeze
end end
def loaded_casks def loaded_casks
@loaded_casks ||= downcased_unique_named.map(&Cask::CaskLoader.method(:load)).freeze named.to_casks
end end
def kegs def kegs
@kegs ||= downcased_unique_named.map do |name| named.to_kegs
resolve_keg name
rescue NoSuchKegError => e
if (reason = Homebrew::MissingFormula.suggest_command(name, "uninstall"))
$stderr.puts reason
end
raise e
end.freeze
end end
def kegs_casks def kegs_casks
@kegs_casks ||= begin named.to_kegs_to_casks
kegs = []
casks = []
downcased_unique_named.each do |name|
kegs << resolve_keg(name)
rescue NoSuchKegError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No installed keg or cask with the name \"#{name}\""
end
end
[kegs.freeze, casks.freeze].freeze
end
end end
def build_stable? def build_stable?
@ -218,17 +150,6 @@ module Homebrew
@cli_args.freeze @cli_args.freeze
end end
def downcased_unique_named
# Only lowercase names, not paths, bottle filenames or URLs
named.map do |arg|
if arg.include?("/") || arg.end_with?(".tar.gz") || File.exist?(arg)
arg
else
arg.downcase
end
end.uniq
end
def spec(default = :stable) def spec(default = :stable)
if HEAD? if HEAD?
:head :head
@ -238,50 +159,6 @@ module Homebrew
default default
end end
end end
def resolve_keg(name)
require "keg"
require "formula"
require "missing_formula"
raise UsageError if name.blank?
rack = Formulary.to_rack(name.downcase)
dirs = rack.directory? ? rack.subdirs : []
raise NoSuchKegError, rack.basename if dirs.empty?
linked_keg_ref = HOMEBREW_LINKED_KEGS/rack.basename
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
else
f = if name.include?("/") || File.exist?(name)
Formulary.factory(name)
else
Formulary.from_rack(rack)
end
unless (prefix = f.installed_prefix).directory?
raise MultipleVersionsInstalledError, "#{rack.basename} has multiple installed versions"
end
Keg.new(prefix)
end
rescue FormulaUnavailableError
raise MultipleVersionsInstalledError, <<~EOS
Multiple kegs installed to #{rack}
However we don't know which one you refer to.
Please delete (with rm -rf!) all but one and then try again.
EOS
end
end
end end
end end
end end

View File

@ -0,0 +1,180 @@
# frozen_string_literal: true
require "delegate"
module Homebrew
module CLI
class NamedArgs < SimpleDelegator
def initialize(*args, override_spec: nil, force_bottle: false, flags: [])
@args = args
@override_spec = override_spec
@force_bottle = force_bottle
@flags = flags
__setobj__(@args)
end
def to_formulae
@to_formulae ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.factory(name, spec, force_bottle: @force_bottle, flags: @flags)
end.uniq(&:name).freeze
end
def to_formulae_and_casks
@to_formulae_and_casks ||= begin
formulae_and_casks = []
downcased_unique_named.each do |name|
formulae_and_casks << Formulary.factory(name, spec)
puts "Treating #{name} as a formula. For the cask, use homebrew/cask/#{name}" if cask_exists_with_ref name
rescue FormulaUnavailableError
begin
formulae_and_casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
formulae_and_casks.freeze
end
end
def to_resolved_formulae
@to_resolved_formulae ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
end.uniq(&:name).freeze
end
def to_resolved_formulae_to_casks
@to_resolved_formulae_to_casks ||= begin
resolved_formulae = []
casks = []
downcased_unique_named.each do |name|
resolved_formulae << Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
puts "Treating #{name} as a formula. For the cask, use homebrew/cask/#{name}" if cask_exists_with_ref name
rescue FormulaUnavailableError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
end
end
[resolved_formulae.freeze, casks.freeze].freeze
end
end
def to_formulae_paths
@to_formulae_paths ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.path(name)
end.uniq.freeze
end
def to_casks
@to_casks ||= downcased_unique_named.map(&Cask::CaskLoader.method(:load)).freeze
end
def to_kegs
@to_kegs ||= downcased_unique_named.map do |name|
resolve_keg name
rescue NoSuchKegError => e
if (reason = Homebrew::MissingFormula.suggest_command(name, "uninstall"))
$stderr.puts reason
end
raise e
end.freeze
end
def to_kegs_to_casks
@to_kegs_to_casks ||= begin
kegs = []
casks = []
downcased_unique_named.each do |name|
kegs << resolve_keg(name)
puts "Treating #{name} as a keg. For the cask, use homebrew/cask/#{name}" if cask_exists_with_ref name
rescue NoSuchKegError, FormulaUnavailableError
begin
casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No installed keg or cask with the name \"#{name}\""
end
end
[kegs.freeze, casks.freeze].freeze
end
end
def homebrew_tap_cask_names
downcased_unique_named.grep(HOMEBREW_CASK_TAP_CASK_REGEX)
end
private
def downcased_unique_named
# Only lowercase names, not paths, bottle filenames or URLs
map do |arg|
if arg.include?("/") || arg.end_with?(".tar.gz") || File.exist?(arg)
arg
else
arg.downcase
end
end.uniq
end
def spec(default = :stable)
@override_spec || default
end
def resolve_keg(name)
raise UsageError if name.blank?
rack = Formulary.to_rack(name.downcase)
dirs = rack.directory? ? rack.subdirs : []
raise NoSuchKegError, rack.basename if dirs.empty?
linked_keg_ref = HOMEBREW_LINKED_KEGS/rack.basename
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
else
f = if name.include?("/") || File.exist?(name)
Formulary.factory(name)
else
Formulary.from_rack(rack)
end
unless (prefix = f.installed_prefix).directory?
raise MultipleVersionsInstalledError, "#{rack.basename} has multiple installed versions"
end
Keg.new(prefix)
end
rescue FormulaUnavailableError
raise MultipleVersionsInstalledError, <<~EOS
Multiple kegs installed to #{rack}
However we don't know which one you refer to.
Please delete (with rm -rf!) all but one and then try again.
EOS
end
end
def cask_exists_with_ref(ref)
Cask::CaskLoader.load ref
rescue Cask::CaskUnavailableError
false
end
end
end
end

View File

@ -43,9 +43,9 @@ module Homebrew
Utils::Shell.from_path(args.shell) Utils::Shell.from_path(args.shell)
end end
env_keys = build_env_keys(ENV) env_keys = BuildEnvironment.keys(ENV)
if shell.nil? if shell.nil?
dump_build_env ENV BuildEnvironment.dump ENV
else else
env_keys.each do |key| env_keys.each do |key|
puts Utils::Shell.export_value(key, ENV[key], shell) puts Utils::Shell.export_value(key, ENV[key], shell)

View File

@ -25,10 +25,7 @@ module Homebrew
end end
homepages = args.formulae_and_casks.map do |formula_or_cask| homepages = args.formulae_and_casks.map do |formula_or_cask|
disclaimer = disclaimers(formula_or_cask) puts "Opening homepage for #{name_of(formula_or_cask)}"
disclaimer = " (#{disclaimer})" if disclaimer.present?
puts "Opening homepage for #{name_of(formula_or_cask)}#{disclaimer}"
formula_or_cask.homepage formula_or_cask.homepage
end end
@ -42,15 +39,4 @@ module Homebrew
"Cask #{formula_or_cask.token}" "Cask #{formula_or_cask.token}"
end end
end end
def disclaimers(formula_or_cask)
return unless formula_or_cask.is_a? Formula
begin
cask = Cask::CaskLoader.load formula_or_cask.name
"for the cask, use #{cask.tap.name}/#{cask.token}"
rescue Cask::CaskUnavailableError
nil
end
end
end end

View File

@ -2,9 +2,11 @@
require "compilers" require "compilers"
# Combination of C++ standard library and compiler.
class CxxStdlib class CxxStdlib
include CompilerConstants include CompilerConstants
# Error for when a formula's dependency was built with a different C++ standard library.
class CompatibilityError < StandardError class CompatibilityError < StandardError
def initialize(formula, dep, stdlib) def initialize(formula, dep, stdlib)
super <<~EOS super <<~EOS
@ -17,8 +19,8 @@ class CxxStdlib
def self.create(type, compiler) def self.create(type, compiler)
raise ArgumentError, "Invalid C++ stdlib type: #{type}" if type && ![:libstdcxx, :libcxx].include?(type) raise ArgumentError, "Invalid C++ stdlib type: #{type}" if type && ![:libstdcxx, :libcxx].include?(type)
klass = (compiler.to_s =~ GNU_GCC_REGEXP) ? GnuStdlib : AppleStdlib apple_compiler = compiler.to_s.match?(GNU_GCC_REGEXP) ? false : true
klass.new(type, compiler) CxxStdlib.new(type, compiler, apple_compiler)
end end
def self.check_compatibility(formula, deps, keg, compiler) def self.check_compatibility(formula, deps, keg, compiler)
@ -35,9 +37,10 @@ class CxxStdlib
attr_reader :type, :compiler attr_reader :type, :compiler
def initialize(type, compiler) def initialize(type, compiler, apple_compiler)
@type = type @type = type
@compiler = compiler.to_sym @compiler = compiler.to_sym
@apple_compiler = apple_compiler
end end
# If either package doesn't use C++, all is well. # If either package doesn't use C++, all is well.
@ -72,15 +75,7 @@ class CxxStdlib
"#<#{self.class.name}: #{compiler} #{type}>" "#<#{self.class.name}: #{compiler} #{type}>"
end end
class AppleStdlib < CxxStdlib
def apple_compiler? def apple_compiler?
true @apple_compiler
end
end
class GnuStdlib < CxxStdlib
def apple_compiler?
false
end
end end
end end

View File

@ -11,7 +11,7 @@ module IRB
def start_within(binding) def start_within(binding)
unless @setup_done unless @setup_done
setup(nil) setup(nil, argv: [])
@setup_done = true @setup_done = true
end end

View File

@ -350,6 +350,11 @@ module Homebrew
"LGPL-3.0" => ["LGPL-3.0-only", "LGPL-3.0-or-later"], "LGPL-3.0" => ["LGPL-3.0-only", "LGPL-3.0-or-later"],
}.freeze }.freeze
PERMITTED_FORMULA_LICENSE_MISMATCHES = {
"cmockery" => "0.1.2",
"scw@1" => "1.20",
}.freeze
def audit_license def audit_license
if formula.license.present? if formula.license.present?
non_standard_licenses = formula.license.map do |license| non_standard_licenses = formula.license.map do |license|
@ -380,12 +385,13 @@ module Homebrew
return unless @online return unless @online
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*})
return if user.blank? return if user.blank?
github_license = GitHub.get_repo_license(user, repo) github_license = GitHub.get_repo_license(user, repo)
return if github_license && (formula.license + ["NOASSERTION"]).include?(github_license) return if github_license && (formula.license + ["NOASSERTION"]).include?(github_license)
return if PERMITTED_LICENSE_MISMATCHES[github_license]&.any? { |license| formula.license.include? license } return if PERMITTED_LICENSE_MISMATCHES[github_license]&.any? { |license| formula.license.include? license }
return if PERMITTED_FORMULA_LICENSE_MISMATCHES[formula.name] == formula.version
problem "Formula license #{formula.license} does not match GitHub license #{Array(github_license)}." problem "Formula license #{formula.license} does not match GitHub license #{Array(github_license)}."

View File

@ -294,7 +294,10 @@ module Homebrew
"", "",
] ]
end end
new_contents = inreplace_pairs(formula.path, replacement_pairs.uniq.compact, args: args) new_contents = Utils::Inreplace.inreplace_pairs(formula.path,
replacement_pairs.uniq.compact,
read_only_run: read_only_run,
silent: args.quiet?)
new_formula_version = formula_version(formula, requested_spec, new_contents) new_formula_version = formula_version(formula, requested_spec, new_contents)
@ -462,34 +465,6 @@ module Homebrew
[remote_url, username] [remote_url, username]
end end
def inreplace_pairs(path, replacement_pairs, args:)
read_only_run = args.dry_run? && !args.write?
if read_only_run
str = path.open("r") { |f| Formulary.ensure_utf8_encoding(f).read }
contents = StringInreplaceExtension.new(str)
replacement_pairs.each do |old, new|
ohai "replace #{old.inspect} with #{new.inspect}" unless args.quiet?
raise "No old value for new value #{new}! Did you pass the wrong arguments?" unless old
contents.gsub!(old, new)
end
raise Utils::InreplaceError, path => contents.errors unless contents.errors.empty?
path.atomic_write(contents.inreplace_string) if args.write?
contents.inreplace_string
else
Utils::Inreplace.inreplace(path) do |s|
replacement_pairs.each do |old, new|
ohai "replace #{old.inspect} with #{new.inspect}" unless args.quiet?
raise "No old value for new value #{new}! Did you pass the wrong arguments?" unless old
s.gsub!(old, new)
end
end
path.open("r") { |f| Formulary.ensure_utf8_encoding(f).read }
end
end
def formula_version(formula, spec, contents = nil) def formula_version(formula, spec, contents = nil)
name = formula.name name = formula.name
path = formula.path path = formula.path

View File

@ -49,7 +49,7 @@ module Homebrew
return return
end end
ohai "#{prs.size} matching pull requests:" ohai "#{prs.count} matching pull #{"request".pluralize(prs.count)}:"
pr_urls = [] pr_urls = []
prs.each do |pr| prs.each do |pr|
puts "#{tap.full_name unless tap.core_tap?}##{pr["number"]}: #{pr["title"]}" puts "#{tap.full_name unless tap.core_tap?}##{pr["number"]}: #{pr["title"]}"

View File

@ -99,6 +99,15 @@ class DevelopmentTools
def subversion_handles_most_https_certificates? def subversion_handles_most_https_certificates?
true true
end end
def build_system_info
{
"os" => ENV["HOMEBREW_SYSTEM"],
"os_version" => OS_VERSION,
"cpu_family" => Hardware::CPU.family,
}
end
alias generic_build_system_info build_system_info
end end
end end

View File

@ -12,6 +12,9 @@ require "cask/caskroom"
require "cask/quarantine" require "cask/quarantine"
module Homebrew module Homebrew
# Module containing diagnostic checks.
#
# @api private
module Diagnostic module Diagnostic
def self.missing_deps(ff, hide = nil) def self.missing_deps(ff, hide = nil)
missing = {} missing = {}
@ -25,44 +28,7 @@ module Homebrew
missing missing
end end
class Volumes # Diagnostic checks.
def initialize
@volumes = get_mounts
end
def which(path)
vols = get_mounts path
# no volume found
return -1 if vols.empty?
vol_index = @volumes.index(vols[0])
# volume not found in volume list
return -1 if vol_index.nil?
vol_index
end
def get_mounts(path = nil)
vols = []
# get the volume of path, if path is nil returns all volumes
args = %w[/bin/df -P]
args << path if path
Utils.popen_read(*args) do |io|
io.each_line do |line|
case line.chomp
# regex matches: /dev/disk0s2 489562928 440803616 48247312 91% /
when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/
vols << Regexp.last_match(1)
end
end
end
vols
end
end
class Checks class Checks
def initialize(verbose = true) def initialize(verbose = true)
@verbose = verbose @verbose = verbose

View File

@ -369,7 +369,7 @@ class BuildError < RuntimeError
ohai "Configuration" ohai "Configuration"
SystemConfig.dump_verbose_config SystemConfig.dump_verbose_config
ohai "ENV" ohai "ENV"
Homebrew.dump_build_env(env) BuildEnvironment.dump env
puts puts
onoe "#{formula.full_name} #{formula.version} did not build" onoe "#{formula.full_name} #{formula.version} did not build"
unless (logs = Dir["#{formula.logs}/*"]).empty? unless (logs = Dir["#{formula.logs}/*"]).empty?

View File

@ -38,7 +38,7 @@ module Homebrew
f.close f.close
return if system f.path return if system f.path
<<~EOS.undent <<~EOS
The directory #{HOMEBREW_TEMP} does not permit executing The directory #{HOMEBREW_TEMP} does not permit executing
programs. It is likely mounted as "noexec". Please set HOMEBREW_TEMP programs. It is likely mounted as "noexec". Please set HOMEBREW_TEMP
in your #{shell_profile} to a different directory, for example: in your #{shell_profile} to a different directory, for example:

View File

@ -14,6 +14,12 @@ module Homebrew
"/system/bin/linker64", "/system/bin/linker64",
"/system/bin/linker", "/system/bin/linker",
].freeze ].freeze
private_constant :DYNAMIC_LINKERS
def perform_preinstall_checks(all_fatal: false, cc: nil)
generic_perform_preinstall_checks(all_fatal: all_fatal, cc: cc)
symlink_ld_so
end
def check_cpu def check_cpu
return if Hardware::CPU.intel? && Hardware::CPU.is_64_bit? return if Hardware::CPU.intel? && Hardware::CPU.is_64_bit?
@ -28,6 +34,7 @@ module Homebrew
end end
abort message abort message
end end
private_class_method :check_cpu
def symlink_ld_so def symlink_ld_so
brew_ld_so = HOMEBREW_PREFIX/"lib/ld.so" brew_ld_so = HOMEBREW_PREFIX/"lib/ld.so"
@ -42,10 +49,6 @@ module Homebrew
FileUtils.mkdir_p HOMEBREW_PREFIX/"lib" FileUtils.mkdir_p HOMEBREW_PREFIX/"lib"
FileUtils.ln_sf ld_so, brew_ld_so FileUtils.ln_sf ld_so, brew_ld_so
end end
private_class_method :symlink_ld_so
def perform_preinstall_checks(all_fatal: false, cc: nil)
generic_perform_preinstall_checks(all_fatal: all_fatal, cc: cc)
symlink_ld_so
end
end end
end end

View File

@ -56,5 +56,13 @@ class DevelopmentTools
brew install gcc brew install gcc
EOS EOS
end end
def build_system_info
build_info = {
"xcode" => MacOS::Xcode.version.to_s.presence,
"clt" => MacOS::CLT.version.to_s.presence,
}
generic_build_system_info.merge build_info
end
end end
end end

View File

@ -2,6 +2,44 @@
module Homebrew module Homebrew
module Diagnostic module Diagnostic
class Volumes
def initialize
@volumes = get_mounts
end
def which(path)
vols = get_mounts path
# no volume found
return -1 if vols.empty?
vol_index = @volumes.index(vols[0])
# volume not found in volume list
return -1 if vol_index.nil?
vol_index
end
def get_mounts(path = nil)
vols = []
# get the volume of path, if path is nil returns all volumes
args = %w[/bin/df -P]
args << path if path
Utils.popen_read(*args) do |io|
io.each_line do |line|
case line.chomp
# regex matches: /dev/disk0s2 489562928 440803616 48247312 91% /
when /^.+\s+[0-9]+\s+[0-9]+\s+[0-9]+\s+[0-9]{1,3}%\s+(.+)/
vols << Regexp.last_match(1)
end
end
end
vols
end
end
class Checks class Checks
undef fatal_build_from_source_checks, supported_configuration_checks, undef fatal_build_from_source_checks, supported_configuration_checks,
build_from_source_checks build_from_source_checks

View File

@ -2020,7 +2020,7 @@ class Formula
SystemConfig.dump_verbose_config(log) SystemConfig.dump_verbose_config(log)
log.puts log.puts
Homebrew.dump_build_env(env, log) BuildEnvironment.dump env, log
raise BuildError.new(self, cmd, args, env) raise BuildError.new(self, cmd, args, env)
end end

View File

@ -21,7 +21,6 @@ require "cmd/install"
require "find" require "find"
class FormulaInstaller class FormulaInstaller
include Homebrew::Install
include FormulaCellarChecks include FormulaCellarChecks
extend Predicable extend Predicable
@ -261,7 +260,9 @@ class FormulaInstaller
lock lock
start_time = Time.now start_time = Time.now
perform_build_from_source_checks if !formula.bottle_unneeded? && !pour_bottle? && DevelopmentTools.installed? if !formula.bottle_unneeded? && !pour_bottle? && DevelopmentTools.installed?
Homebrew::Install.perform_build_from_source_checks
end
# not in initialize so upgrade can unlink the active keg before calling this # not in initialize so upgrade can unlink the active keg before calling this
# function but after instantiating this class so that it can avoid having to # function but after instantiating this class so that it can avoid having to

View File

@ -64,6 +64,9 @@ class FormulaVersions
versions_seen = (map.keys + [f.pkg_version]).uniq.length versions_seen = (map.keys + [f.pkg_version]).uniq.length
end end
return map if versions_seen > MAX_VERSIONS_DEPTH return map if versions_seen > MAX_VERSIONS_DEPTH
rescue MacOSVersionError => e
odebug "#{e} in #{name} at revision #{rev}" if debug?
break
end end
map map
end end

View File

@ -6,9 +6,27 @@ require "hardware"
require "development_tools" require "development_tools"
module Homebrew module Homebrew
# Helper module for performing (pre-)install checks.
#
# @api private
module Install module Install
module_function module_function
def perform_preinstall_checks(all_fatal: false, cc: nil)
check_cpu
attempt_directory_creation
check_cc_argv(cc)
diagnostic_checks(:supported_configuration_checks, fatal: all_fatal)
diagnostic_checks(:fatal_preinstall_checks)
end
alias generic_perform_preinstall_checks perform_preinstall_checks
module_function :generic_perform_preinstall_checks
def perform_build_from_source_checks(all_fatal: false)
diagnostic_checks(:fatal_build_from_source_checks)
diagnostic_checks(:build_from_source_checks, fatal: all_fatal)
end
def check_cpu def check_cpu
return if Hardware::CPU.intel? && Hardware::CPU.is_64_bit? return if Hardware::CPU.intel? && Hardware::CPU.is_64_bit?
@ -24,6 +42,7 @@ module Homebrew
end end
abort message abort message
end end
private_class_method :check_cpu
def attempt_directory_creation def attempt_directory_creation
Keg::MUST_EXIST_DIRECTORIES.each do |dir| Keg::MUST_EXIST_DIRECTORIES.each do |dir|
@ -38,6 +57,7 @@ module Homebrew
nil nil
end end
end end
private_class_method :attempt_directory_creation
def check_cc_argv(cc) def check_cc_argv(cc)
return unless cc return unless cc
@ -48,21 +68,7 @@ module Homebrew
#{@checks.please_create_pull_requests} #{@checks.please_create_pull_requests}
EOS EOS
end end
private_class_method :check_cc_argv
def perform_preinstall_checks(all_fatal: false, cc: nil)
check_cpu
attempt_directory_creation
check_cc_argv(cc)
diagnostic_checks(:supported_configuration_checks, fatal: all_fatal)
diagnostic_checks(:fatal_preinstall_checks)
end
alias generic_perform_preinstall_checks perform_preinstall_checks
module_function :generic_perform_preinstall_checks
def perform_build_from_source_checks(all_fatal: false)
diagnostic_checks(:fatal_build_from_source_checks)
diagnostic_checks(:build_from_source_checks, fatal: all_fatal)
end
def diagnostic_checks(type, fatal: true) def diagnostic_checks(type, fatal: true)
@checks ||= Diagnostic::Checks.new @checks ||= Diagnostic::Checks.new
@ -80,6 +86,7 @@ module Homebrew
end end
exit 1 if failed && fatal exit 1 if failed && fatal
end end
private_class_method :diagnostic_checks
end end
end end

View File

@ -57,6 +57,10 @@ module OS
nil nil
end end
def sdk_path
nil
end
module Xcode module Xcode
module_function module_function

View File

@ -36,6 +36,7 @@ class Tab < OpenStruct
"stdlib" => stdlib, "stdlib" => stdlib,
"aliases" => formula.aliases, "aliases" => formula.aliases,
"runtime_dependencies" => Tab.runtime_deps_hash(runtime_deps), "runtime_dependencies" => Tab.runtime_deps_hash(runtime_deps),
"arch" => Hardware::CPU.arch,
"source" => { "source" => {
"path" => formula.specified_path.to_s, "path" => formula.specified_path.to_s,
"tap" => formula.tap&.name, "tap" => formula.tap&.name,
@ -47,6 +48,7 @@ class Tab < OpenStruct
"version_scheme" => formula.version_scheme, "version_scheme" => formula.version_scheme,
}, },
}, },
"built_on" => DevelopmentTools.build_system_info,
} }
new(attributes) new(attributes)
@ -198,6 +200,7 @@ class Tab < OpenStruct
"version_scheme" => 0, "version_scheme" => 0,
}, },
}, },
"built_on" => DevelopmentTools.generic_build_system_info,
} }
new(attributes) new(attributes)
@ -344,6 +347,7 @@ class Tab < OpenStruct
"aliases" => aliases, "aliases" => aliases,
"runtime_dependencies" => runtime_dependencies, "runtime_dependencies" => runtime_dependencies,
"source" => source, "source" => source,
"built_on" => built_on,
} }
JSON.generate(attributes, options) JSON.generate(attributes, options)

View File

@ -25,14 +25,9 @@ describe Cask::Cmd::Cache, :cask do
cache: Cask::Cache.path, **local_caffeine.url.specs cache: Cask::Cache.path, **local_caffeine.url.specs
).cached_location ).cached_location
expect do expect(described_class.cached_location(local_transmission))
described_class.run("local-transmission", "local-caffeine") .to eql transmission_location
end.to output("#{transmission_location}\n#{caffeine_location}\n").to_stdout expect(described_class.cached_location(local_caffeine))
end .to eql caffeine_location
it "properly handles Casks that are not present" do
expect {
described_class.run("notacask")
}.to raise_error(Cask::CaskUnavailableError)
end end
end end

View File

@ -120,7 +120,7 @@ describe Caveats do
"plist_test.plist" "plist_test.plist"
end end
end end
allow(ENV).to receive(:[]).with("TMUX").and_return(true) ENV["TMUX"] = "1"
allow(Homebrew).to receive(:_system).with("/usr/bin/pbpaste").and_return(false) allow(Homebrew).to receive(:_system).with("/usr/bin/pbpaste").and_return(false)
caveats = described_class.new(f).caveats caveats = described_class.new(f).caveats

View File

@ -5,9 +5,9 @@ require "cleanup"
require "cask/cache" require "cask/cache"
require "fileutils" require "fileutils"
using CleanupRefinement using Homebrew::Cleanup::CleanupRefinement
describe CleanupRefinement do describe Homebrew::Cleanup::CleanupRefinement do
describe "::prune?" do describe "::prune?" do
alias_matcher :be_pruned, :be_prune alias_matcher :be_pruned, :be_prune

View File

@ -0,0 +1,110 @@
# frozen_string_literal: true
require "cli/named_args"
describe Homebrew::CLI::NamedArgs do
let(:foo) do
formula "foo" do
url "https://brew.sh"
version "1.0"
end
end
let(:foo_keg) do
path = (HOMEBREW_CELLAR/"foo/1.0").resolved_path
mkdir_p path
Keg.new(path)
end
let(:bar) do
formula "bar" do
url "https://brew.sh"
version "1.0"
end
end
let(:bar_keg) do
path = (HOMEBREW_CELLAR/"bar/1.0").resolved_path
mkdir_p path
Keg.new(path)
end
let(:baz) do
Cask::CaskLoader.load(+<<~RUBY)
cask "baz" do
version "1.0"
end
RUBY
end
describe "#to_formulae" do
it "returns formulae" do
stub_formula_loader foo, call_original: true
stub_formula_loader bar
expect(described_class.new("foo", "bar").to_formulae).to eq [foo, bar]
end
end
describe "#to_formulae_and_casks" do
it "returns formulae and casks" do
stub_formula_loader foo, call_original: true
stub_cask_loader baz, call_original: true
expect(described_class.new("foo", "baz").to_formulae_and_casks).to eq [foo, baz]
end
end
describe "#to_resolved_formulae" do
it "returns resolved formulae" do
allow(Formulary).to receive(:resolve).and_return(foo, bar)
expect(described_class.new("foo", "bar").to_resolved_formulae).to eq [foo, bar]
end
end
describe "#to_resolved_formulae_to_casks" do
it "returns resolved formulae, as well as casks" do
allow(Formulary).to receive(:resolve).and_call_original
allow(Formulary).to receive(:resolve).with("foo", any_args).and_return foo
stub_cask_loader baz, call_original: true
resolved_formulae, casks = described_class.new("foo", "baz").to_resolved_formulae_to_casks
expect(resolved_formulae).to eq [foo]
expect(casks).to eq [baz]
end
end
describe "#to_casks" do
it "returns casks" do
stub_cask_loader baz
expect(described_class.new("baz").to_casks).to eq [baz]
end
end
describe "#to_kegs" do
it "returns kegs" do
named_args = described_class.new("foo", "bar")
allow(named_args).to receive(:resolve_keg).with("foo").and_return foo_keg
allow(named_args).to receive(:resolve_keg).with("bar").and_return bar_keg
expect(named_args.to_kegs).to eq [foo_keg, bar_keg]
end
end
describe "#to_kegs_to_casks" do
it "returns kegs, as well as casks" do
named_args = described_class.new("foo", "baz")
allow(named_args).to receive(:resolve_keg).and_call_original
allow(named_args).to receive(:resolve_keg).with("foo").and_return foo_keg
stub_cask_loader baz, call_original: true
kegs, casks = named_args.to_kegs_to_casks
expect(kegs).to eq [foo_keg]
expect(casks).to eq [baz]
end
end
end

View File

@ -305,4 +305,12 @@ describe Utils::Inreplace do
end end
}.to raise_error(Utils::InreplaceError) }.to raise_error(Utils::InreplaceError)
end end
describe "#inreplace_pairs" do
it "raises error if there is no old value" do
expect {
described_class.inreplace_pairs(file.path, [[nil, "f"]])
}.to raise_error(Utils::InreplaceError)
end
end
end end

View File

@ -36,6 +36,7 @@ $LOAD_PATH.push(File.expand_path("#{ENV["HOMEBREW_LIBRARY"]}/Homebrew/test/suppo
require_relative "../global" require_relative "../global"
require "test/support/no_seed_progress_formatter" require "test/support/no_seed_progress_formatter"
require "test/support/helper/cask"
require "test/support/helper/fixtures" require "test/support/helper/fixtures"
require "test/support/helper/formula" require "test/support/helper/formula"
require "test/support/helper/mktmpdir" require "test/support/helper/mktmpdir"
@ -86,6 +87,7 @@ RSpec.configure do |config|
config.include(RuboCop::RSpec::ExpectOffense) config.include(RuboCop::RSpec::ExpectOffense)
config.include(Test::Helper::Cask)
config.include(Test::Helper::Fixtures) config.include(Test::Helper::Fixtures)
config.include(Test::Helper::Formula) config.include(Test::Helper::Formula)
config.include(Test::Helper::MkTmpDir) config.include(Test::Helper::MkTmpDir)

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require "cask/cask_loader"
module Test
module Helper
module Cask
def stub_cask_loader(cask, ref = cask.token, call_original: false)
allow(::Cask::CaskLoader).to receive(:for).and_call_original if call_original
loader = ::Cask::CaskLoader::FromInstanceLoader.new cask
allow(::Cask::CaskLoader).to receive(:for).with(ref).and_return(loader)
end
end
end
end

View File

@ -11,7 +11,9 @@ module Test
# Use a stubbed {Formulary::FormulaLoader} to make a given formula be found # Use a stubbed {Formulary::FormulaLoader} to make a given formula be found
# when loading from {Formulary} with `ref`. # when loading from {Formulary} with `ref`.
def stub_formula_loader(formula, ref = formula.full_name) def stub_formula_loader(formula, ref = formula.full_name, call_original: false)
allow(Formulary).to receive(:loader_for).and_call_original if call_original
loader = double(get_formula: formula) loader = double(get_formula: formula)
allow(Formulary).to receive(:loader_for).with(ref, from: :keg).and_return(loader) allow(Formulary).to receive(:loader_for).with(ref, from: :keg).and_return(loader)
allow(Formulary).to receive(:loader_for).with(ref, from: nil).and_return(loader) allow(Formulary).to receive(:loader_for).with(ref, from: nil).and_return(loader)

View File

@ -11,6 +11,8 @@ module Utils
end end
module Inreplace module Inreplace
module_function
# Sometimes we have to change a bit before we install. Mostly we # Sometimes we have to change a bit before we install. Mostly we
# prefer a patch but if you need the `prefix` of this formula in the # prefer a patch but if you need the `prefix` of this formula in the
# patch you have to resort to `inreplace`, because in the patch # patch you have to resort to `inreplace`, because in the patch
@ -42,6 +44,23 @@ module Utils
raise InreplaceError, errors unless errors.empty? raise InreplaceError, errors unless errors.empty?
end end
module_function :inreplace
def inreplace_pairs(path, replacement_pairs, read_only_run: false, silent: false)
str = File.open(path, "rb", &:read)
contents = StringInreplaceExtension.new(str)
replacement_pairs.each do |old, new|
ohai "replace #{old.inspect} with #{new.inspect}" unless silent
unless old
contents.errors << "No old value for new value #{new}! Did you pass the wrong arguments?"
next
end
contents.gsub!(old, new)
end
raise InreplaceError, path => contents.errors unless contents.errors.empty?
Pathname(path).atomic_write(contents.inreplace_string) unless read_only_run
contents.inreplace_string
end
end end
end end

View File

@ -51,7 +51,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel-1.19.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel_tests-3.1.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel_tests-3.1.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parser-2.7.1.4/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parser-2.7.1.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rainbow-3.0.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rainbow-3.0.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.5866/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.5869/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parlour-4.0.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parlour-4.0.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/patchelf-1.2.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/patchelf-1.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.5.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.5.0/lib"
@ -74,9 +74,9 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.10
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-0.88.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-0.88.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.7.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-1.42.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-1.43.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.2.0/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.5866-universal-darwin-19/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.5869-universal-darwin-19/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.5866/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.5869/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.0.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.0.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tapioca-0.4.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tapioca-0.4.1/lib"

View File

@ -1,72 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Check that the first argument to the top level describe is a constant.
#
# @example
# # bad
# describe 'Do something' do
# end
#
# # good
# describe TestedClass do
# subject { described_class }
# end
#
# describe 'TestedClass::VERSION' do
# subject { Object.const_get(self.class.description) }
# end
#
# describe "A feature example", type: :feature do
# end
class DescribeClass < Cop
include RuboCop::RSpec::TopLevelDescribe
MSG = 'The first argument to describe should be '\
'the class or module being tested.'
def_node_matcher :valid_describe?, <<-PATTERN
{
(send #{RSPEC} :describe const ...)
(send #{RSPEC} :describe)
}
PATTERN
def_node_matcher :describe_with_rails_metadata?, <<-PATTERN
(send #{RSPEC} :describe !const ...
(hash <#rails_metadata? ...>)
)
PATTERN
def_node_matcher :rails_metadata?, <<-PATTERN
(pair
(sym :type)
(sym {
:channel :controller :helper :job :mailer :model :request
:routing :view :feature :system :mailbox
}
)
)
PATTERN
def on_top_level_describe(node, (described_value, _))
return if shared_group?(root_node)
return if valid_describe?(node)
return if describe_with_rails_metadata?(node)
return if string_constant_describe?(described_value)
add_offense(described_value)
end
private
def string_constant_describe?(described_value)
described_value.str_type? &&
described_value.value =~ /^((::)?[A-Z]\w*)+$/
end
end
end
end
end

View File

@ -1,90 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks if an example group does not include any tests.
#
# This cop is configurable using the `CustomIncludeMethods` option
#
# @example usage
#
# # bad
# describe Bacon do
# let(:bacon) { Bacon.new(chunkiness) }
# let(:chunkiness) { false }
#
# context 'extra chunky' do # flagged by rubocop
# let(:chunkiness) { true }
# end
#
# it 'is chunky' do
# expect(bacon.chunky?).to be_truthy
# end
# end
#
# # good
# describe Bacon do
# let(:bacon) { Bacon.new(chunkiness) }
# let(:chunkiness) { false }
#
# it 'is chunky' do
# expect(bacon.chunky?).to be_truthy
# end
# end
#
# @example configuration
#
# # .rubocop.yml
# # RSpec/EmptyExampleGroup:
# # CustomIncludeMethods:
# # - include_tests
#
# # spec_helper.rb
# RSpec.configure do |config|
# config.alias_it_behaves_like_to(:include_tests)
# end
#
# # bacon_spec.rb
# describe Bacon do
# let(:bacon) { Bacon.new(chunkiness) }
# let(:chunkiness) { false }
#
# context 'extra chunky' do # not flagged by rubocop
# let(:chunkiness) { true }
#
# include_tests 'shared tests'
# end
# end
#
class EmptyExampleGroup < Cop
MSG = 'Empty example group detected.'
def_node_search :contains_example?, <<-PATTERN
{
#{(Examples::ALL + Includes::ALL).send_pattern}
(send _ #custom_include? ...)
}
PATTERN
def on_block(node)
return unless example_group?(node) && !contains_example?(node)
add_offense(node.send_node)
end
private
def custom_include?(method_name)
custom_include_methods.include?(method_name)
end
def custom_include_methods
cop_config
.fetch('CustomIncludeMethods', [])
.map(&:to_sym)
end
end
end
end
end

View File

@ -1,47 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks that memoized helper names use the configured style.
#
# @example EnforcedStyle: snake_case (default)
# # bad
# let(:userName) { 'Adam' }
# subject(:userName) { 'Adam' }
#
# # good
# let(:user_name) { 'Adam' }
# subject(:user_name) { 'Adam' }
#
# @example EnforcedStyle: camelCase
# # bad
# let(:user_name) { 'Adam' }
# subject(:user_name) { 'Adam' }
#
# # good
# let(:userName) { 'Adam' }
# subject(:userName) { 'Adam' }
class VariableName < Cop
include ConfigurableNaming
include RuboCop::RSpec::Variable
MSG = 'Use %<style>s for variable names.'
def on_send(node)
variable_definition?(node) do |variable|
return if variable.dstr_type? || variable.dsym_type?
check_name(node, variable.value, variable.loc.expression)
end
end
private
def message(style)
format(MSG, style: style)
end
end
end
end
end

View File

@ -1,44 +0,0 @@
# frozen_string_literal: true
module RuboCop
module RSpec
# Helper methods for top level example group cops
module TopLevelGroup
extend RuboCop::NodePattern::Macros
include RuboCop::RSpec::Language
def_node_matcher :example_or_shared_group?,
(ExampleGroups::ALL + SharedGroups::ALL).block_pattern
def on_block(node)
return unless respond_to?(:on_top_level_group)
return unless top_level_group?(node)
on_top_level_group(node)
end
private
def top_level_group?(node)
top_level_groups.include?(node)
end
def top_level_groups
@top_level_groups ||=
top_level_nodes.select { |n| example_or_shared_group?(n) }
end
def top_level_nodes
if root_node.begin_type?
root_node.children
else
[root_node]
end
end
def root_node
processed_source.ast
end
end
end
end

View File

@ -74,7 +74,7 @@ RSpec/ContextWording:
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextWording StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextWording
RSpec/DescribeClass: RSpec/DescribeClass:
Description: Check that the first argument to the top level describe is a constant. Description: Check that the first argument to the top-level describe is a constant.
Enabled: true Enabled: true
VersionAdded: '1.0' VersionAdded: '1.0'
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeClass StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribeClass
@ -381,7 +381,7 @@ RSpec/MissingExampleGroupArgument:
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument
RSpec/MultipleDescribes: RSpec/MultipleDescribes:
Description: Checks for multiple top level describes. Description: Checks for multiple top-level example groups.
Enabled: true Enabled: true
VersionAdded: '1.0' VersionAdded: '1.0'
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleDescribes StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleDescribes
@ -394,6 +394,14 @@ RSpec/MultipleExpectations:
VersionChanged: '1.21' VersionChanged: '1.21'
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleExpectations StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleExpectations
RSpec/MultipleMemoizedHelpers:
Description: Checks if example groups contain too many `let` and `subject` calls.
Enabled: true
AllowSubject: true
Max: 5
VersionAdded: '1.43'
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MultipleMemoizedHelpers
RSpec/MultipleSubjects: RSpec/MultipleSubjects:
Description: Checks if an example group defines `subject` multiple times. Description: Checks if an example group defines `subject` multiple times.
Enabled: true Enabled: true
@ -558,7 +566,9 @@ RSpec/VariableName:
SupportedStyles: SupportedStyles:
- snake_case - snake_case
- camelCase - camelCase
IgnoredPatterns: []
VersionAdded: '1.40' VersionAdded: '1.40'
VersionChanged: '1.43'
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:

View File

@ -19,11 +19,12 @@ require_relative 'rubocop/rspec/example_group'
require_relative 'rubocop/rspec/example' require_relative 'rubocop/rspec/example'
require_relative 'rubocop/rspec/hook' require_relative 'rubocop/rspec/hook'
require_relative 'rubocop/rspec/variable' require_relative 'rubocop/rspec/variable'
require_relative 'rubocop/cop/rspec/base'
require_relative 'rubocop/cop/rspec/cop' require_relative 'rubocop/cop/rspec/cop'
require_relative 'rubocop/rspec/align_let_brace' require_relative 'rubocop/rspec/align_let_brace'
require_relative 'rubocop/rspec/factory_bot' require_relative 'rubocop/rspec/factory_bot'
require_relative 'rubocop/rspec/final_end_location' require_relative 'rubocop/rspec/final_end_location'
require_relative 'rubocop/rspec/blank_line_separation' require_relative 'rubocop/rspec/empty_line_separation'
require_relative 'rubocop/rspec/corrector/move_node' require_relative 'rubocop/rspec/corrector/move_node'
RuboCop::RSpec::Inject.defaults! RuboCop::RSpec::Inject.defaults!

View File

@ -17,7 +17,7 @@ module RuboCop
# let(:baz) { bar } # let(:baz) { bar }
# let(:a) { b } # let(:a) { b }
# #
class AlignLeftLetBrace < Cop class AlignLeftLetBrace < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Align left let brace' MSG = 'Align left let brace'

View File

@ -17,7 +17,7 @@ module RuboCop
# let(:baz) { bar } # let(:baz) { bar }
# let(:a) { b } # let(:a) { b }
# #
class AlignRightLetBrace < Cop class AlignRightLetBrace < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Align right let brace' MSG = 'Align right let brace'

View File

@ -22,7 +22,7 @@ module RuboCop
# allow(my_instance).to receive(:foo) # allow(my_instance).to receive(:foo)
# end # end
# end # end
class AnyInstance < Cop class AnyInstance < Base
MSG = 'Avoid stubbing using `%<method>s`.' MSG = 'Avoid stubbing using `%<method>s`.'
def_node_matcher :disallowed_stub, <<-PATTERN def_node_matcher :disallowed_stub, <<-PATTERN

View File

@ -25,7 +25,7 @@ module RuboCop
# some_method # some_method
# test.run # test.run
# end # end
class AroundBlock < Cop class AroundBlock < Base
MSG_NO_ARG = 'Test object should be passed to around block.' MSG_NO_ARG = 'Test object should be passed to around block.'
MSG_UNUSED_ARG = 'You should call `%<arg>s.call` '\ MSG_UNUSED_ARG = 'You should call `%<arg>s.call` '\
'or `%<arg>s.run`.' 'or `%<arg>s.run`.'

View File

@ -17,7 +17,7 @@ module RuboCop
# # Patterns: # # Patterns:
# # - '_test.rb$' # # - '_test.rb$'
# # - '(?:^|/)test/' # # - '(?:^|/)test/'
class Cop < ::RuboCop::Cop::Base class Base < ::RuboCop::Cop::Base
include RuboCop::RSpec::Language include RuboCop::RSpec::Language
include RuboCop::RSpec::Language::NodePattern include RuboCop::RSpec::Language::NodePattern
@ -30,8 +30,8 @@ module RuboCop
) )
# Invoke the original inherited hook so our cops are recognized # Invoke the original inherited hook so our cops are recognized
def self.inherited(subclass) def self.inherited(subclass) # rubocop:disable Lint/MissingSuper
RuboCop::Cop::Cop.inherited(subclass) RuboCop::Cop::Base.inherited(subclass)
end end
def relevant_file?(file) def relevant_file?(file)
@ -41,7 +41,7 @@ module RuboCop
private private
def relevant_rubocop_rspec_file?(file) def relevant_rubocop_rspec_file?(file)
rspec_pattern =~ file rspec_pattern.match?(file)
end end
def rspec_pattern def rspec_pattern

View File

@ -19,7 +19,7 @@ module RuboCop
# expect(foo).to be 1.0 # expect(foo).to be 1.0
# expect(foo).to be(true) # expect(foo).to be(true)
# #
class Be < Cop class Be < Base
MSG = 'Don\'t use `be` without an argument.' MSG = 'Don\'t use `be` without an argument.'
def_node_matcher :be_without_args, <<-PATTERN def_node_matcher :be_without_args, <<-PATTERN

View File

@ -35,7 +35,7 @@ module RuboCop
# necessarily the same type as `b` since the `#==` operator can # necessarily the same type as `b` since the `#==` operator can
# coerce objects for comparison. # coerce objects for comparison.
# #
class BeEql < Cop class BeEql < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Prefer `be` over `eql`.' MSG = 'Prefer `be` over `eql`.'

View File

@ -23,7 +23,7 @@ module RuboCop
# before(:each) { Widget.create } # before(:each) { Widget.create }
# after(:each) { Widget.delete_all } # after(:each) { Widget.delete_all }
# end # end
class BeforeAfterAll < Cop class BeforeAfterAll < Base
MSG = 'Beware of using `%<hook>s` as it may cause state to leak '\ MSG = 'Beware of using `%<hook>s` as it may cause state to leak '\
'between tests. If you are using `rspec-rails`, and '\ 'between tests. If you are using `rspec-rails`, and '\
'`use_transactional_fixtures` is enabled, then records created '\ '`use_transactional_fixtures` is enabled, then records created '\

View File

@ -23,7 +23,7 @@ module RuboCop
# expect(page).to have_current_path("/callback") # expect(page).to have_current_path("/callback")
# expect(page).to have_current_path(/widgets/) # expect(page).to have_current_path(/widgets/)
# #
class CurrentPathExpectation < Cop class CurrentPathExpectation < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Do not set an RSpec expectation on `current_path` in ' \ MSG = 'Do not set an RSpec expectation on `current_path` in ' \

View File

@ -40,7 +40,7 @@ module RuboCop
# # ... # # ...
# end # end
# end # end
class FeatureMethods < Cop class FeatureMethods < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Use `%<replacement>s` instead of `%<method>s`.' MSG = 'Use `%<replacement>s` instead of `%<method>s`.'
@ -55,15 +55,18 @@ module RuboCop
feature: :describe feature: :describe
}.freeze }.freeze
def_node_matcher :capybara_speak,
SelectorSet.new(MAP.keys).node_pattern_union
def_node_matcher :spec?, <<-PATTERN def_node_matcher :spec?, <<-PATTERN
(block (block
(send #{RSPEC} {:describe :feature} ...) (send #rspec? {:describe :feature} ...)
...) ...)
PATTERN PATTERN
def_node_matcher :feature_method, <<-PATTERN def_node_matcher :feature_method, <<-PATTERN
(block (block
$(send #{RSPEC} ${#{MAP.keys.map(&:inspect).join(' ')}} ...) $(send #rspec? $#capybara_speak ...)
...) ...)
PATTERN PATTERN

View File

@ -26,7 +26,7 @@ module RuboCop
# expect(page).to have_css('.foo', visible: :all) # expect(page).to have_css('.foo', visible: :all)
# expect(page).to have_link('my link', visible: :hidden) # expect(page).to have_link('my link', visible: :hidden)
# #
class VisibilityMatcher < Cop class VisibilityMatcher < Base
MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.' MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
MSG_TRUE = 'Use `:visible` instead of `true`.' MSG_TRUE = 'Use `:visible` instead of `true`.'
CAPYBARA_MATCHER_METHODS = %i[ CAPYBARA_MATCHER_METHODS = %i[

View File

@ -23,13 +23,13 @@ module RuboCop
# describe '.foo_bar' do # describe '.foo_bar' do
# # ... # # ...
# end # end
class ContextMethod < Cop class ContextMethod < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Use `describe` for testing methods.' MSG = 'Use `describe` for testing methods.'
def_node_matcher :context_method, <<-PATTERN def_node_matcher :context_method, <<-PATTERN
(block (send #{RSPEC} :context $(str #method_name?) ...) ...) (block (send #rspec? :context $(str #method_name?) ...) ...)
PATTERN PATTERN
def on_block(node) def on_block(node)

View File

@ -34,11 +34,11 @@ module RuboCop
# context 'when the display name is not present' do # context 'when the display name is not present' do
# # ... # # ...
# end # end
class ContextWording < Cop class ContextWording < Base
MSG = 'Start context description with %<prefixes>s.' MSG = 'Start context description with %<prefixes>s.'
def_node_matcher :context_wording, <<-PATTERN def_node_matcher :context_wording, <<-PATTERN
(block (send #{RSPEC} { :context :shared_context } $(str #bad_prefix?) ...) ...) (block (send #rspec? { :context :shared_context } $(str #bad_prefix?) ...) ...)
PATTERN PATTERN
def on_block(node) def on_block(node)
@ -51,7 +51,7 @@ module RuboCop
private private
def bad_prefix?(description) def bad_prefix?(description)
!prefixes.include?(description.split.first) !prefixes.include?(description.split(/\b/).first)
end end
def joined_prefixes def joined_prefixes

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# @deprecated Use ::RuboCop::Cop::RSpec::Base instead
Cop = Base
end
end
end

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Check that the first argument to the top-level describe is a constant.
#
# @example
# # bad
# describe 'Do something' do
# end
#
# # good
# describe TestedClass do
# subject { described_class }
# end
#
# describe 'TestedClass::VERSION' do
# subject { Object.const_get(self.class.description) }
# end
#
# describe "A feature example", type: :feature do
# end
class DescribeClass < Base
include RuboCop::RSpec::TopLevelGroup
MSG = 'The first argument to describe should be '\
'the class or module being tested.'
def_node_matcher :rails_metadata?, <<-PATTERN
(pair
(sym :type)
(sym { :channel :controller :helper :job :mailer :model :request
:routing :view :feature :system :mailbox })
)
PATTERN
def_node_matcher :example_group_with_rails_metadata?, <<~PATTERN
(send #rspec? :describe ... (hash <#rails_metadata? ...>))
PATTERN
def_node_matcher :not_a_const_described, <<~PATTERN
(send #rspec? :describe $[!const !#string_constant?] ...)
PATTERN
def on_top_level_group(top_level_node)
return if example_group_with_rails_metadata?(top_level_node.send_node)
not_a_const_described(top_level_node.send_node) do |described|
add_offense(described)
end
end
private
def string_constant?(described)
described.str_type? &&
described.value.match?(/^(?:(?:::)?[A-Z]\w*)+$/)
end
end
end
end
end

View File

@ -16,17 +16,25 @@ module RuboCop
# #
# describe MyClass, '.my_class_method' do # describe MyClass, '.my_class_method' do
# end # end
class DescribeMethod < Cop class DescribeMethod < Base
include RuboCop::RSpec::TopLevelDescribe include RuboCop::RSpec::TopLevelGroup
MSG = 'The second argument to describe should be the method '\ MSG = 'The second argument to describe should be the method '\
"being tested. '#instance' or '.class'." "being tested. '#instance' or '.class'."
def on_top_level_describe(_node, (_, second_arg)) def_node_matcher :second_argument, <<~PATTERN
return unless second_arg&.str_type? (block
return if second_arg.str_content.start_with?('#', '.') (send #rspec? :describe _first_argument $(str _) ...) ...
)
PATTERN
add_offense(second_arg) def on_top_level_group(node)
second_argument = second_argument(node)
return unless second_argument
return if second_argument.str_content.start_with?('#', '.')
add_offense(second_argument)
end end
end end
end end

View File

@ -17,11 +17,11 @@ module RuboCop
# end # end
# #
# @see https://github.com/rspec/rspec-core/issues/1610 # @see https://github.com/rspec/rspec-core/issues/1610
class DescribeSymbol < Cop class DescribeSymbol < Base
MSG = 'Avoid describing symbols.' MSG = 'Avoid describing symbols.'
def_node_matcher :describe_symbol?, <<-PATTERN def_node_matcher :describe_symbol?, <<-PATTERN
(send #{RSPEC} :describe $sym ...) (send #rspec? :describe $sym ...)
PATTERN PATTERN
def on_send(node) def on_send(node)

View File

@ -54,7 +54,7 @@ module RuboCop
# end # end
# end # end
# #
class DescribedClass < Cop class DescribedClass < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle
@ -142,7 +142,7 @@ module RuboCop
if style == :described_class if style == :described_class
offensive_described_class?(node) offensive_described_class?(node)
else else
node.send_type? && node.method_name == :described_class node.send_type? && node.method?(:described_class)
end end
end end

View File

@ -19,7 +19,7 @@ module RuboCop
# end # end
# #
# @see https://github.com/rubocop-hq/rubocop-rspec/issues/735 # @see https://github.com/rubocop-hq/rubocop-rspec/issues/735
class DescribedClassModuleWrapping < Cop class DescribedClassModuleWrapping < Base
MSG = 'Avoid opening modules and defining specs within them.' MSG = 'Avoid opening modules and defining specs within them.'
def_node_search :find_rspec_blocks, def_node_search :find_rspec_blocks,

View File

@ -41,7 +41,7 @@ module RuboCop
# describe 'display name presence' do # describe 'display name presence' do
# # ... # # ...
# end # end
class Dialect < Cop class Dialect < Base
extend AutoCorrector extend AutoCorrector
include MethodPreference include MethodPreference

View File

@ -0,0 +1,174 @@
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks if an example group does not include any tests.
#
# This cop is configurable using the `CustomIncludeMethods` option
#
# @example usage
#
# # bad
# describe Bacon do
# let(:bacon) { Bacon.new(chunkiness) }
# let(:chunkiness) { false }
#
# context 'extra chunky' do # flagged by rubocop
# let(:chunkiness) { true }
# end
#
# it 'is chunky' do
# expect(bacon.chunky?).to be_truthy
# end
# end
#
# # good
# describe Bacon do
# let(:bacon) { Bacon.new(chunkiness) }
# let(:chunkiness) { false }
#
# it 'is chunky' do
# expect(bacon.chunky?).to be_truthy
# end
# end
#
# @example configuration
#
# # .rubocop.yml
# # RSpec/EmptyExampleGroup:
# # CustomIncludeMethods:
# # - include_tests
#
# # spec_helper.rb
# RSpec.configure do |config|
# config.alias_it_behaves_like_to(:include_tests)
# end
#
# # bacon_spec.rb
# describe Bacon do
# let(:bacon) { Bacon.new(chunkiness) }
# let(:chunkiness) { false }
#
# context 'extra chunky' do # not flagged by rubocop
# let(:chunkiness) { true }
#
# include_tests 'shared tests'
# end
# end
#
class EmptyExampleGroup < Base
MSG = 'Empty example group detected.'
# @!method example_group_body(node)
# Match example group blocks and yield their body
#
# @example source that matches
# describe 'example group' do
# it { is_expected.to be }
# end
#
# @param node [RuboCop::AST::Node]
# @yield [RuboCop::AST::Node] example group body
def_node_matcher :example_group_body, <<~PATTERN
(block #{ExampleGroups::ALL.send_pattern} args $_)
PATTERN
# @!method example_or_group_or_include?(node)
# Match examples, example groups and includes
#
# @example source that matches
# it { is_expected.to fly }
# describe('non-empty example groups too') { }
# it_behaves_like 'an animal'
# it_behaves_like('a cat') { let(:food) { 'milk' } }
# it_has_root_access
#
# @param node [RuboCop::AST::Node]
# @return [Array<RuboCop::AST::Node>] matching nodes
def_node_matcher :example_or_group_or_include?, <<~PATTERN
{
#{Examples::ALL.block_pattern}
#{ExampleGroups::ALL.block_pattern}
#{Includes::ALL.send_pattern}
#{Includes::ALL.block_pattern}
(send nil? #custom_include? ...)
}
PATTERN
# @!method examples_inside_block?(node)
# Match examples defined inside a block which is not a hook
#
# @example source that matches
# %w(r g b).each do |color|
# it { is_expected.to have_color(color) }
# end
#
# @example source that does not match
# before do
# it { is_expected.to fall_into_oblivion }
# end
#
# @param node [RuboCop::AST::Node]
# @return [Array<RuboCop::AST::Node>] matching nodes
def_node_matcher :examples_inside_block?, <<~PATTERN
(block !#{Hooks::ALL.send_pattern} _ #examples?)
PATTERN
# @!method examples_directly_or_in_block?(node)
# Match examples or examples inside blocks
#
# @example source that matches
# it { expect(drink).to be_cold }
# context('when winter') { it { expect(drink).to be_hot } }
# (1..5).each { |divisor| it { is_expected.to divide_by(divisor) } }
#
# @param node [RuboCop::AST::Node]
# @return [Array<RuboCop::AST::Node>] matching nodes
def_node_matcher :examples_directly_or_in_block?, <<~PATTERN
{
#example_or_group_or_include?
#examples_inside_block?
}
PATTERN
# @!method examples?(node)
# Matches examples defined in scopes where they could run
#
# @example source that matches
# it { expect(myself).to be_run }
# describe { it { i_run_as_well } }
#
# @example source that does not match
# before { it { whatever here wont run anyway } }
#
# @param node [RuboCop::AST::Node]
# @return [Array<RuboCop::AST::Node>] matching nodes
def_node_matcher :examples?, <<~PATTERN
{
#examples_directly_or_in_block?
(begin <#examples_directly_or_in_block? ...>)
}
PATTERN
def on_block(node)
example_group_body(node) do |body|
add_offense(node.send_node) unless examples?(body)
end
end
private
def custom_include?(method_name)
custom_include_methods.include?(method_name)
end
def custom_include_methods
cop_config
.fetch('CustomIncludeMethods', [])
.map(&:to_sym)
end
end
end
end
end

View File

@ -22,7 +22,7 @@ module RuboCop
# create_feed # create_feed
# end # end
# after(:all) { cleanup_feed } # after(:all) { cleanup_feed }
class EmptyHook < Cop class EmptyHook < Base
extend AutoCorrector extend AutoCorrector
include RuboCop::Cop::RangeHelp include RuboCop::Cop::RangeHelp

View File

@ -41,22 +41,18 @@ module RuboCop
# it { two } # it { two }
# end # end
# #
class EmptyLineAfterExample < Cop class EmptyLineAfterExample < Base
extend AutoCorrector extend AutoCorrector
include RuboCop::RSpec::BlankLineSeparation include RuboCop::RSpec::EmptyLineSeparation
MSG = 'Add an empty line after `%<example>s`.' MSG = 'Add an empty line after `%<example>s`.'
def on_block(node) def on_block(node)
return unless example?(node) return unless example?(node)
return if last_child?(node)
return if allowed_one_liner?(node) return if allowed_one_liner?(node)
missing_separating_line(node) do |location| missing_separating_line_offense(node) do |method|
msg = format(MSG, example: node.method_name) format(MSG, example: method)
add_offense(location, message: msg) do |corrector|
corrector.insert_after(location.end, "\n")
end
end end
end end

View File

@ -23,21 +23,17 @@ module RuboCop
# end # end
# end # end
# #
class EmptyLineAfterExampleGroup < Cop class EmptyLineAfterExampleGroup < Base
extend AutoCorrector extend AutoCorrector
include RuboCop::RSpec::BlankLineSeparation include RuboCop::RSpec::EmptyLineSeparation
MSG = 'Add an empty line after `%<example_group>s`.' MSG = 'Add an empty line after `%<example_group>s`.'
def on_block(node) def on_block(node)
return unless example_group?(node) return unless example_group?(node)
return if last_child?(node)
missing_separating_line(node) do |location| missing_separating_line_offense(node) do |method|
msg = format(MSG, example_group: node.method_name) format(MSG, example_group: method)
add_offense(location, message: msg) do |corrector|
corrector.insert_after(location.end, "\n")
end
end end
end end
end end

View File

@ -16,24 +16,21 @@ module RuboCop
# let(:something) { other } # let(:something) { other }
# #
# it { does_something } # it { does_something }
class EmptyLineAfterFinalLet < Cop class EmptyLineAfterFinalLet < Base
extend AutoCorrector extend AutoCorrector
include RuboCop::RSpec::BlankLineSeparation include RuboCop::RSpec::EmptyLineSeparation
MSG = 'Add an empty line after the last `let` block.' MSG = 'Add an empty line after the last `%<let>s`.'
def on_block(node) def on_block(node)
return unless example_group_with_body?(node) return unless example_group_with_body?(node)
latest_let = node.body.child_nodes.select { |child| let?(child) }.last final_let = node.body.child_nodes.reverse.find { |child| let?(child) }
return if latest_let.nil? return if final_let.nil?
return if last_child?(latest_let)
missing_separating_line(latest_let) do |location| missing_separating_line_offense(final_let) do |method|
add_offense(location) do |corrector| format(MSG, let: method)
corrector.insert_after(location.end, "\n")
end
end end
end end
end end

View File

@ -33,21 +33,17 @@ module RuboCop
# #
# it { does_something } # it { does_something }
# #
class EmptyLineAfterHook < Cop class EmptyLineAfterHook < Base
extend AutoCorrector extend AutoCorrector
include RuboCop::RSpec::BlankLineSeparation include RuboCop::RSpec::EmptyLineSeparation
MSG = 'Add an empty line after `%<hook>s`.' MSG = 'Add an empty line after `%<hook>s`.'
def on_block(node) def on_block(node)
return unless hook?(node) return unless hook?(node)
return if last_child?(node)
missing_separating_line(node) do |location| missing_separating_line_offense(node) do |method|
msg = format(MSG, hook: node.method_name) format(MSG, hook: method)
add_offense(location, message: msg) do |corrector|
corrector.insert_after(location.end, "\n")
end
end end
end end
end end

View File

@ -14,20 +14,17 @@ module RuboCop
# subject(:obj) { described_class } # subject(:obj) { described_class }
# #
# let(:foo) { bar } # let(:foo) { bar }
class EmptyLineAfterSubject < Cop class EmptyLineAfterSubject < Base
extend AutoCorrector extend AutoCorrector
include RuboCop::RSpec::BlankLineSeparation include RuboCop::RSpec::EmptyLineSeparation
MSG = 'Add empty line after `subject`.' MSG = 'Add an empty line after `%<subject>s`.'
def on_block(node) def on_block(node)
return unless subject?(node) && !in_spec_block?(node) return unless subject?(node) && !in_spec_block?(node)
return if last_child?(node)
missing_separating_line(node) do |location| missing_separating_line_offense(node) do |method|
add_offense(location) do |corrector| format(MSG, subject: method)
corrector.insert_after(location.end, "\n")
end
end end
end end

View File

@ -25,7 +25,7 @@ module RuboCop
# result = service.call # result = service.call
# expect(result).to be(true) # expect(result).to be(true)
# end # end
class ExampleLength < Cop class ExampleLength < Base
include CodeLength include CodeLength
MSG = 'Example has too many lines [%<total>d/%<max>d].' MSG = 'Example has too many lines [%<total>d/%<max>d].'

View File

@ -47,7 +47,7 @@ module RuboCop
# result = service.call # result = service.call
# expect(result).to be(true) # expect(result).to be(true)
# end # end
class ExampleWithoutDescription < Cop class ExampleWithoutDescription < Base
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle
MSG_DEFAULT_ARGUMENT = 'Omit the argument when you want to ' \ MSG_DEFAULT_ARGUMENT = 'Omit the argument when you want to ' \

View File

@ -29,7 +29,7 @@ module RuboCop
# # good # # good
# it 'does things' do # it 'does things' do
# end # end
class ExampleWording < Cop class ExampleWording < Base
extend AutoCorrector extend AutoCorrector
MSG_SHOULD = 'Do not use should when describing your tests.' MSG_SHOULD = 'Do not use should when describing your tests.'
@ -47,9 +47,9 @@ module RuboCop
def on_block(node) def on_block(node)
it_description(node) do |description_node, message| it_description(node) do |description_node, message|
if message =~ SHOULD_PREFIX if message.match?(SHOULD_PREFIX)
add_wording_offense(description_node, MSG_SHOULD) add_wording_offense(description_node, MSG_SHOULD)
elsif message =~ IT_PREFIX elsif message.match?(IT_PREFIX)
add_wording_offense(description_node, MSG_IT) add_wording_offense(description_node, MSG_IT)
end end
end end
@ -77,7 +77,7 @@ module RuboCop
def replacement_text(node) def replacement_text(node)
text = text(node) text = text(node)
if text =~ SHOULD_PREFIX if text.match?(SHOULD_PREFIX)
RuboCop::RSpec::Wording.new( RuboCop::RSpec::Wording.new(
text, text,
ignore: ignored_words, ignore: ignored_words,

View File

@ -16,7 +16,7 @@ module RuboCop
# expect(pattern).to eq(/foo/) # expect(pattern).to eq(/foo/)
# expect(name).to eq("John") # expect(name).to eq("John")
# #
class ExpectActual < Cop class ExpectActual < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Provide the actual you are testing to `expect(...)`.' MSG = 'Provide the actual you are testing to `expect(...)`.'

View File

@ -29,7 +29,7 @@ module RuboCop
# expect { run }.to change { Foo.bar(:count) } # expect { run }.to change { Foo.bar(:count) }
# expect { run }.to change { user.reload.name } # expect { run }.to change { user.reload.name }
# #
class ExpectChange < Cop class ExpectChange < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle

View File

@ -20,7 +20,7 @@ module RuboCop
# it do # it do
# expect(something).to eq 'foo' # expect(something).to eq 'foo'
# end # end
class ExpectInHook < Cop class ExpectInHook < Base
MSG = 'Do not use `%<expect>s` in `%<hook>s` hook' MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'
def_node_search :expectation, Expectations::ALL.send_pattern def_node_search :expectation, Expectations::ALL.send_pattern

View File

@ -14,7 +14,7 @@ module RuboCop
# #
# # good # # good
# expect { my_app.print_report }.to output('Hello World').to_stdout # expect { my_app.print_report }.to output('Hello World').to_stdout
class ExpectOutput < Cop class ExpectOutput < Base
MSG = 'Use `expect { ... }.to output(...).to_%<name>s` '\ MSG = 'Use `expect { ... }.to output(...).to_%<name>s` '\
'instead of mutating $%<name>s.' 'instead of mutating $%<name>s.'

View File

@ -24,7 +24,7 @@ module RuboCop
# #
# # good # # good
# count { 1 } # count { 1 }
class AttributeDefinedStatically < Cop class AttributeDefinedStatically < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Use a block to declare attribute values.' MSG = 'Use a block to declare attribute values.'
@ -39,7 +39,7 @@ module RuboCop
def on_block(node) def on_block(node)
attributes = factory_attributes(node) || [] attributes = factory_attributes(node) || []
attributes = [attributes] unless attributes.is_a?(Array) attributes = [attributes] unless attributes.is_a?(Array) # rubocop:disable Style/ArrayCoercion, Lint/RedundantCopDisableDirective
attributes.each do |attribute| attributes.each do |attribute|
next unless offensive_receiver?(attribute.receiver, node) next unless offensive_receiver?(attribute.receiver, node)
@ -84,7 +84,7 @@ module RuboCop
def autocorrect_replacing_parens(corrector, node) def autocorrect_replacing_parens(corrector, node)
left_braces, right_braces = braces(node) left_braces, right_braces = braces(node)
corrector.replace(node.location.begin, ' ' + left_braces) corrector.replace(node.location.begin, " #{left_braces}")
corrector.replace(node.location.end, right_braces) corrector.replace(node.location.end, right_braces)
end end

View File

@ -24,7 +24,7 @@ module RuboCop
# #
# # good # # good
# 3.times { create :user } # 3.times { create :user }
class CreateList < Cop class CreateList < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle
@ -44,7 +44,7 @@ module RuboCop
PATTERN PATTERN
def_node_matcher :factory_list_call, <<-PATTERN def_node_matcher :factory_list_call, <<-PATTERN
(send ${(const nil? {:FactoryGirl :FactoryBot}) nil?} :create_list (sym $_) (int $_) $...) (send {(const nil? {:FactoryGirl :FactoryBot}) nil?} :create_list (sym _) (int $_) ...)
PATTERN PATTERN
def on_block(node) def on_block(node)
@ -60,7 +60,7 @@ module RuboCop
def on_send(node) def on_send(node)
return unless style == :n_times return unless style == :n_times
factory_list_call(node) do |_receiver, _factory, count, _| factory_list_call(node) do |count|
message = format(MSG_N_TIMES, number: count) message = format(MSG_N_TIMES, number: count)
add_offense(node.loc.selector, message: message) do |corrector| add_offense(node.loc.selector, message: message) do |corrector|
TimesCorrector.new(node).call(corrector) TimesCorrector.new(node).call(corrector)
@ -79,7 +79,7 @@ module RuboCop
end end
# :nodoc # :nodoc
class Corrector module Corrector
private private
def build_options_string(options) def build_options_string(options)
@ -102,7 +102,9 @@ module RuboCop
end end
# :nodoc # :nodoc
class TimesCorrector < Corrector class TimesCorrector
include Corrector
def initialize(node) def initialize(node)
@node = node @node = node
end end
@ -130,7 +132,9 @@ module RuboCop
end end
# :nodoc: # :nodoc:
class CreateListCorrector < Corrector class CreateListCorrector
include Corrector
def initialize(node) def initialize(node)
@node = node.parent @node = node.parent
end end

View File

@ -19,7 +19,7 @@ module RuboCop
# # good # # good
# factory :foo, class: 'Foo' do # factory :foo, class: 'Foo' do
# end # end
class FactoryClassName < Cop class FactoryClassName < Base
extend AutoCorrector extend AutoCorrector
MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \ MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \

View File

@ -56,35 +56,43 @@ module RuboCop
# # good # # good
# my_class_spec.rb # describe MyClass, '#method' # my_class_spec.rb # describe MyClass, '#method'
# #
class FilePath < Cop class FilePath < Base
include RuboCop::RSpec::TopLevelDescribe include RuboCop::RSpec::TopLevelGroup
MSG = 'Spec path should end with `%<suffix>s`.' MSG = 'Spec path should end with `%<suffix>s`.'
def_node_search :const_described?, '(send _ :describe (const ...) ...)' def_node_matcher :const_described, <<~PATTERN
(block
$(send #rspec? _example_group $(const ...) $...) ...
)
PATTERN
def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))' def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))'
def on_top_level_describe(node, args) def on_top_level_group(node)
return unless const_described?(node) && single_top_level_describe? return unless top_level_groups.one?
return if routing_spec?(args)
glob = glob_for(args) const_described(node) do |send_node, described_class, arguments|
next if routing_spec?(arguments)
return if filename_ends_with?(glob) ensure_correct_file_path(send_node, described_class, arguments)
end
add_offense(
node,
message: format(MSG, suffix: glob)
)
end end
private private
def ensure_correct_file_path(send_node, described_class, arguments)
glob = glob_for(described_class, arguments.first)
return if filename_ends_with?(glob)
add_offense(send_node, message: format(MSG, suffix: glob))
end
def routing_spec?(args) def routing_spec?(args)
args.any?(&method(:routing_metadata?)) args.any?(&method(:routing_metadata?))
end end
def glob_for((described_class, method_name)) def glob_for(described_class, method_name)
return glob_for_spec_suffix_only? if spec_suffix_only? return glob_for_spec_suffix_only? if spec_suffix_only?
"#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb" "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
@ -94,10 +102,10 @@ module RuboCop
'*_spec.rb' '*_spec.rb'
end end
def name_glob(name) def name_glob(method_name)
return unless name&.str_type? return unless method_name&.str_type?
"*#{name.str_content.gsub(/\W/, '')}" unless ignore_methods? "*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
end end
def expected_path(constant) def expected_path(constant)

View File

@ -19,23 +19,19 @@ module RuboCop
# # good # # good
# describe MyClass do # describe MyClass do
# end # end
class Focus < Cop class Focus < Base
MSG = 'Focused spec found.' MSG = 'Focused spec found.'
focusable =
ExampleGroups::GROUPS +
ExampleGroups::SKIPPED +
Examples::EXAMPLES +
Examples::SKIPPED +
Examples::PENDING
focused = ExampleGroups::FOCUSED + Examples::FOCUSED focused = ExampleGroups::FOCUSED + Examples::FOCUSED
FOCUSABLE_SELECTORS = focusable.node_pattern_union def_node_matcher :focusable_selector?,
(ExampleGroups::GROUPS + ExampleGroups::SKIPPED +
Examples::EXAMPLES + Examples::SKIPPED +
Examples::PENDING).node_pattern_union
def_node_matcher :metadata, <<-PATTERN def_node_matcher :metadata, <<-PATTERN
{(send #{RSPEC} #{FOCUSABLE_SELECTORS} <$(sym :focus) ...>) {(send #rspec? #focusable_selector? <$(sym :focus) ...>)
(send #{RSPEC} #{FOCUSABLE_SELECTORS} ... (hash <$(pair (sym :focus) true) ...>))} (send #rspec? #focusable_selector? ... (hash <$(pair (sym :focus) true) ...>))}
PATTERN PATTERN
def_node_matcher :focused_block?, focused.send_pattern def_node_matcher :focused_block?, focused.send_pattern

View File

@ -57,21 +57,20 @@ module RuboCop
# before(:example) do # before(:example) do
# # ... # # ...
# end # end
class HookArgument < Cop class HookArgument < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle
IMPLICIT_MSG = 'Omit the default `%<scope>p` ' \ IMPLICIT_MSG = 'Omit the default `%<scope>p` argument for RSpec hooks.'
'argument for RSpec hooks.'
EXPLICIT_MSG = 'Use `%<scope>p` for RSpec hooks.' EXPLICIT_MSG = 'Use `%<scope>p` for RSpec hooks.'
HOOKS = Hooks::ALL.node_pattern_union.freeze def_node_matcher :hook?, Hooks::ALL.node_pattern_union
def_node_matcher :scoped_hook, <<-PATTERN def_node_matcher :scoped_hook, <<-PATTERN
(block $(send _ #{HOOKS} (sym ${:each :example})) ...) (block $(send _ #hook? (sym ${:each :example})) ...)
PATTERN PATTERN
def_node_matcher :unscoped_hook, "(block $(send _ #{HOOKS}) ...)" def_node_matcher :unscoped_hook, '(block $(send _ #hook?) ...)'
def on_block(node) def on_block(node)
hook(node) do |method_send, scope_name| hook(node) do |method_send, scope_name|

View File

@ -23,7 +23,7 @@ module RuboCop
# expect(foo).to be # expect(foo).to be
# end # end
# #
class HooksBeforeExamples < Cop class HooksBeforeExamples < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Move `%<hook>s` above the examples in the group.' MSG = 'Move `%<hook>s` above the examples in the group.'

View File

@ -16,7 +16,7 @@ module RuboCop
# it 'changes something to a new value' do # it 'changes something to a new value' do
# expect { do_something }.to change(something).to(new_value) # expect { do_something }.to change(something).to(new_value)
# end # end
class ImplicitBlockExpectation < Cop class ImplicitBlockExpectation < Base
MSG = 'Avoid implicit block expectations.' MSG = 'Avoid implicit block expectations.'
def_node_matcher :lambda?, <<-PATTERN def_node_matcher :lambda?, <<-PATTERN

View File

@ -24,7 +24,7 @@ module RuboCop
# # good # # good
# it { should be_truthy } # it { should be_truthy }
# #
class ImplicitExpect < Cop class ImplicitExpect < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle

View File

@ -26,7 +26,7 @@ module RuboCop
# # good # # good
# it { expect(subject).to be_truthy } # it { expect(subject).to be_truthy }
# #
class ImplicitSubject < Cop class ImplicitSubject < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle
@ -49,9 +49,10 @@ module RuboCop
def autocorrect(corrector, node) def autocorrect(corrector, node)
replacement = 'expect(subject)' replacement = 'expect(subject)'
if node.method_name == :should case node.method_name
when :should
replacement += '.to' replacement += '.to'
elsif node.method_name == :should_not when :should_not
replacement += '.not_to' replacement += '.not_to'
end end
@ -62,13 +63,14 @@ module RuboCop
example = node.ancestors.find { |parent| example?(parent) } example = node.ancestors.find { |parent| example?(parent) }
return false if example.nil? return false if example.nil?
example.method_name == :its || allowed_by_style?(example) example.method?(:its) || allowed_by_style?(example)
end end
def allowed_by_style?(example) def allowed_by_style?(example)
if style == :single_line_only case style
when :single_line_only
example.single_line? example.single_line?
elsif style == :single_statement_only when :single_statement_only
!example.body.begin_type? !example.body.begin_type?
else else
false false

View File

@ -18,7 +18,7 @@ module RuboCop
# expect(foo).to have_received(:bar) # expect(foo).to have_received(:bar)
# end # end
# #
class InstanceSpy < Cop class InstanceSpy < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Use `instance_spy` when you check your double '\ MSG = 'Use `instance_spy` when you check your double '\

View File

@ -46,7 +46,7 @@ module RuboCop
# it { expect(foo).to be_empty } # it { expect(foo).to be_empty }
# end # end
# #
class InstanceVariable < Cop class InstanceVariable < Base
include RuboCop::RSpec::TopLevelGroup include RuboCop::RSpec::TopLevelGroup
MSG = 'Avoid instance variables use let, ' \ MSG = 'Avoid instance variables use let, ' \

View File

@ -15,7 +15,7 @@ module RuboCop
# #
# # good # # good
# expect(foo).to be_something # expect(foo).to be_something
class InvalidPredicateMatcher < Cop class InvalidPredicateMatcher < Base
MSG = 'Omit `?` from `%<matcher>s`.' MSG = 'Omit `?` from `%<matcher>s`.'
def_node_matcher :invalid_predicate_matcher?, <<-PATTERN def_node_matcher :invalid_predicate_matcher?, <<-PATTERN

View File

@ -18,7 +18,7 @@ module RuboCop
# #
# # good # # good
# it_should_behave_like 'a foo' # it_should_behave_like 'a foo'
class ItBehavesLike < Cop class ItBehavesLike < Base
extend AutoCorrector extend AutoCorrector
include ConfigurableEnforcedStyle include ConfigurableEnforcedStyle

View File

@ -15,7 +15,7 @@ module RuboCop
# it 'validates users' do # it 'validates users' do
# expect([user1, user2, user3]).to all(be_valid) # expect([user1, user2, user3]).to all(be_valid)
# end # end
class IteratedExpectation < Cop class IteratedExpectation < Base
MSG = 'Prefer using the `all` matcher instead ' \ MSG = 'Prefer using the `all` matcher instead ' \
'of iterating over an array.' 'of iterating over an array.'

View File

@ -31,7 +31,7 @@ module RuboCop
# it { expect_something } # it { expect_something }
# it { expect_something_else } # it { expect_something_else }
# #
class LeadingSubject < Cop class LeadingSubject < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Declare `subject` above any other `%<offending>s` declarations.' MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
@ -43,33 +43,36 @@ module RuboCop
end end
def check_previous_nodes(node) def check_previous_nodes(node)
node.parent.each_child_node do |sibling| offending_node(node) do |offender|
if offending?(sibling) msg = format(MSG, offending: offender.method_name)
msg = format(MSG, offending: sibling.method_name)
add_offense(node, message: msg) do |corrector| add_offense(node, message: msg) do |corrector|
autocorrect(corrector, node) autocorrect(corrector, node, offender)
end end
end end
break if offending?(sibling) || sibling.equal?(node)
end
end end
private private
def autocorrect(corrector, node) def offending_node(node)
first_node = find_first_offending_node(node) node.parent.each_child_node.find do |sibling|
break if sibling.equal?(node)
yield sibling if offending?(sibling)
end
end
def autocorrect(corrector, node, sibling)
RuboCop::RSpec::Corrector::MoveNode.new( RuboCop::RSpec::Corrector::MoveNode.new(
node, corrector, processed_source node, corrector, processed_source
).move_before(first_node) ).move_before(sibling)
end end
def offending?(node) def offending?(node)
let?(node) || hook?(node) || example?(node) let?(node) ||
end hook?(node) ||
example?(node) ||
def find_first_offending_node(node) spec_group?(node) ||
node.parent.children.find { |sibling| offending?(sibling) } include?(node)
end end
def in_spec_block?(node) def in_spec_block?(node)

View File

@ -93,7 +93,7 @@ module RuboCop
# stub_const('SomeModule::SomeClass', foo_class) # stub_const('SomeModule::SomeClass', foo_class)
# end # end
# end # end
class LeakyConstantDeclaration < Cop class LeakyConstantDeclaration < Base
MSG_CONST = 'Stub constant instead of declaring explicitly.' MSG_CONST = 'Stub constant instead of declaring explicitly.'
MSG_CLASS = 'Stub class constant instead of declaring explicitly.' MSG_CLASS = 'Stub class constant instead of declaring explicitly.'
MSG_MODULE = 'Stub module constant instead of declaring explicitly.' MSG_MODULE = 'Stub module constant instead of declaring explicitly.'

View File

@ -30,7 +30,7 @@ module RuboCop
# it 'checks what some does' do # it 'checks what some does' do
# expect(some).to be # expect(some).to be
# end # end
class LetBeforeExamples < Cop class LetBeforeExamples < Base
extend AutoCorrector extend AutoCorrector
MSG = 'Move `let` before the examples in the group.' MSG = 'Move `let` before the examples in the group.'

Some files were not shown because too many files have changed in this diff Show More