Merge pull request #9152 from reitermarkus/update-gems

Update Gems.
This commit is contained in:
Markus Reiter 2020-11-17 13:51:27 +01:00 committed by GitHub
commit b21c59f751
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
126 changed files with 1812 additions and 724 deletions

View File

@ -163,6 +163,14 @@ Performance/CaseWhenSplat:
Performance/Caller:
Enabled: false
# Enable when https://github.com/Homebrew/homebrew-core/pull/64983 is merged.
Performance/ConstantRegexp:
Enabled: false
# Makes code less readable for minor performance increases.
Performance/MethodObjectAsBlock:
Enabled: false
# Don't allow cops to be disabled in casks and formulae.
Style/DisableCopsWithinSourceCodeDirective:
Enabled: true

View File

@ -44,7 +44,7 @@ GEM
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512)
mime-types-data (3.2020.1104)
mini_portile2 (2.4.0)
minitest (5.14.2)
mustache (1.1.1)
@ -98,7 +98,7 @@ GEM
rspec-support (3.10.0)
rspec-wait (0.0.9)
rspec (>= 3, < 4)
rubocop (1.3.0)
rubocop (1.3.1)
parallel (~> 1.10)
parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0)
@ -109,8 +109,8 @@ GEM
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.1.1)
parser (>= 2.7.1.5)
rubocop-performance (1.8.1)
rubocop (>= 0.87.0)
rubocop-performance (1.9.0)
rubocop (>= 0.90.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rspec (2.0.0)
rubocop (~> 1.0)
@ -123,11 +123,11 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov-html (0.12.3)
sorbet (0.5.6060)
sorbet-static (= 0.5.6060)
sorbet-runtime (0.5.6060)
sorbet (0.5.6076)
sorbet-static (= 0.5.6076)
sorbet-runtime (0.5.6076)
sorbet-runtime-stub (0.2.0)
sorbet-static (0.5.6060-universal-darwin-14)
sorbet-static (0.5.6076-universal-darwin-14)
spoom (1.0.4)
colorize
sorbet (~> 0.5.5)
@ -142,14 +142,14 @@ GEM
thor (>= 0.19.2)
thor (1.0.1)
thread_safe (0.3.6)
tzinfo (1.2.7)
tzinfo (1.2.8)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
webrobots (0.1.2)
zeitwerk (2.4.0)
zeitwerk (2.4.1)
PLATFORMS
ruby

View File

@ -22,7 +22,7 @@ module Cask
attr_reader :token, :sourcefile_path, :config, :default_config
def self.each(&block)
return to_enum unless block_given?
return to_enum unless block
Tap.flat_map(&:cask_files).each do |f|
block.call CaskLoader::FromTapPathLoader.new(f).load(config: nil)

View File

@ -84,7 +84,7 @@ module Cask
def self.parser(&block)
Homebrew::CLI::Parser.new do
if block_given?
if block
instance_eval(&block)
else
usage_banner <<~EOS

View File

@ -68,7 +68,7 @@ module Cask
Cmd.parser do
usage_banner banner
instance_eval(&block) if block_given?
instance_eval(&block) if block
OPTIONS.each do |option|
send(*option)

View File

@ -34,7 +34,7 @@ module Cask
send(*option)
end
instance_eval(&block) if block_given?
instance_eval(&block) if block
end
end

View File

@ -125,7 +125,7 @@ module Cask
def language(*args, default: false, &block)
if args.empty?
language_eval
elsif block_given?
elsif block
@language_blocks ||= {}
@language_blocks[args] = block
@ -248,7 +248,7 @@ module Cask
def caveats(*strings, &block)
@caveats ||= DSL::Caveats.new(cask)
if block_given?
if block
@caveats.eval_caveats(&block)
elsif strings.any?
strings.each do |string|

View File

@ -136,7 +136,7 @@ module Homebrew
switch short, long, description: desc, env: option_to_name(long), method: :on_tail
end
instance_eval(&block) if block_given?
instance_eval(&block) if block
end
def switch(*names, description: nil, env: nil, required_for: nil, depends_on: nil, method: :on)

View File

@ -56,7 +56,7 @@ class CompilerFailure
def initialize(name, version, &block)
@name = name
@version = Version.parse(version.to_s)
instance_eval(&block) if block_given?
instance_eval(&block) if block
end
def fails_with?(compiler)

View File

@ -120,9 +120,9 @@ class Dependency
@expand_stack.pop
end
def action(dependent, dep, &_block)
def action(dependent, dep, &block)
catch(:action) do
if block_given?
if block
yield dependent, dep
elsif dep.optional? || dep.recommended?
prune unless dependent.build.with?(dep)

View File

@ -4,8 +4,8 @@
class Formula
undef on_linux
def on_linux(&_block)
raise "No block content defined for on_linux block" unless block_given?
def on_linux(&block)
raise "No block content defined for on_linux block" unless block
yield
end
@ -19,8 +19,8 @@ class Formula
class << self
undef on_linux
def on_linux(&_block)
raise "No block content defined for on_linux block" unless block_given?
def on_linux(&block)
raise "No block content defined for on_linux block" unless block
yield
end

View File

@ -4,8 +4,8 @@
class Formula
undef on_macos
def on_macos(&_block)
raise "No block content defined for on_macos block" unless block_given?
def on_macos(&block)
raise "No block content defined for on_macos block" unless block
yield
end
@ -13,8 +13,8 @@ class Formula
class << self
undef on_macos
def on_macos(&_block)
raise "No block content defined for on_macos block" unless block_given?
def on_macos(&block)
raise "No block content defined for on_macos block" unless block
yield
end

View File

@ -282,7 +282,7 @@ class Formula
# and is specified to this instance.
def installed_alias_path
path = build.source["path"] if build.is_a?(Tab)
return unless path&.match?(%r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases})
return unless path&.match?(%r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases}o)
return unless File.symlink?(path)
path
@ -1384,16 +1384,16 @@ class Formula
# <pre>on_macos do
# # Do something Mac-specific
# end</pre>
def on_macos(&_block)
raise "No block content defined for on_macos block" unless block_given?
def on_macos(&block)
raise "No block content defined for on_macos block" unless block
end
# Block only executed on Linux. No-op on macOS.
# <pre>on_linux do
# # Do something Linux-specific
# end</pre>
def on_linux(&_block)
raise "No block content defined for on_linux block" unless block_given?
def on_linux(&block)
raise "No block content defined for on_linux block" unless block
end
# Standard parameters for cargo builds.
@ -2118,7 +2118,7 @@ class Formula
# a block.
def mkdir(name, &block)
result = FileUtils.mkdir_p(name)
return result unless block_given?
return result unless block
FileUtils.chdir(name, &block)
end
@ -2458,7 +2458,7 @@ class Formula
# end</pre>
def stable(&block)
@stable ||= SoftwareSpec.new(flags: build_flags)
return @stable unless block_given?
return @stable unless block
@stable.instance_eval(&block)
end
@ -2482,7 +2482,7 @@ class Formula
# <pre>head "https://hg.is.awesome.but.git.has.won.example.com/", using: :hg</pre>
def head(val = nil, specs = {}, &block)
@head ||= HeadSoftwareSpec.new(flags: build_flags)
if block_given?
if block
@head.instance_eval(&block)
elsif val
@head.url(val, specs)
@ -2553,16 +2553,16 @@ class Formula
# <pre>on_macos do
# depends_on "mac_only_dep"
# end</pre>
def on_macos(&_block)
raise "No block content defined for on_macos block" unless block_given?
def on_macos(&block)
raise "No block content defined for on_macos block" unless block
end
# Block only executed on Linux. No-op on macOS.
# <pre>on_linux do
# depends_on "linux_only_dep"
# end</pre>
def on_linux(&_block)
raise "No block content defined for on_linux block" unless block_given?
def on_linux(&block)
raise "No block content defined for on_linux block" unless block
end
# @!attribute [w] option
@ -2757,7 +2757,7 @@ class Formula
# end</pre>
def livecheck(&block)
@livecheck ||= Livecheck.new(self)
return @livecheck unless block_given?
return @livecheck unless block
@livecheckable = true
@livecheck.instance_eval(&block)

View File

@ -270,7 +270,7 @@ module Language
next unless f.symlink?
next unless (rp = f.realpath.to_s).start_with? HOMEBREW_CELLAR
version = rp.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}
version = rp.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o
version = "@#{version.captures.first}" unless version.nil?
new_target = rp.sub %r{#{HOMEBREW_CELLAR}/python#{version}/[^/]+}, Formula["python#{version}"].opt_prefix
@ -281,7 +281,7 @@ module Language
Pathname.glob(@venv_root/"lib/python*/orig-prefix.txt").each do |prefix_file|
prefix_path = prefix_file.read
version = prefix_path.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}
version = prefix_path.match %r{^#{HOMEBREW_CELLAR}/python@(.*?)/}o
version = "@#{version.captures.first}" unless version.nil?
prefix_path.sub! %r{^#{HOMEBREW_CELLAR}/python#{version}/[^/]+}, Formula["python#{version}"].opt_prefix

View File

@ -129,7 +129,7 @@ class LinkageChecker
private
def dylib_to_dep(dylib)
dylib =~ %r{#{Regexp.escape(HOMEBREW_PREFIX)}/(opt|Cellar)/([\w+-.@]+)/}
dylib =~ %r{#{Regexp.escape(HOMEBREW_PREFIX)}/(opt|Cellar)/([\w+-.@]+)/}o
Regexp.last_match(2)
end

View File

@ -82,7 +82,7 @@ class Requirement
return unless @satisfied_result.is_a?(Pathname)
parent = @satisfied_result.resolved_path.parent
if parent.to_s =~ %r{^#{Regexp.escape(HOMEBREW_CELLAR)}/([\w+-.@]+)/[^/]+/(s?bin)/?$}
if parent.to_s =~ %r{^#{Regexp.escape(HOMEBREW_CELLAR)}/([\w+-.@]+)/[^/]+/(s?bin)/?$}o
parent = HOMEBREW_PREFIX/"opt/#{Regexp.last_match(1)}/#{Regexp.last_match(2)}"
end
parent
@ -168,14 +168,14 @@ class Requirement
attr_rw :fatal, :cask, :download
def satisfy(options = nil, &block)
return @satisfied if options.nil? && !block_given?
return @satisfied if options.nil? && !block
options = {} if options.nil?
@satisfied = Satisfier.new(options, &block)
end
def env(*settings, &block)
if block_given?
if block
@env_proc = block
else
super
@ -236,9 +236,9 @@ class Requirement
reqs
end
def prune?(dependent, req, &_block)
def prune?(dependent, req, &block)
catch(:prune) do
if block_given?
if block
yield dependent, req
elsif req.optional? || req.recommended?
prune unless dependent.build.with?(req)

View File

@ -32,7 +32,7 @@ class Resource
@checksum = nil
@using = nil
@patches = []
instance_eval(&block) if block_given?
instance_eval(&block) if block
end
def owner=(owner)

View File

@ -107,7 +107,7 @@ class SoftwareSpec
end
def resource(name, klass = Resource, &block)
if block_given?
if block
raise DuplicateResourceError, name if resource_defined?(name)
res = klass.new(name, &block)

View File

@ -586,7 +586,7 @@ class Tap
def self.each(&block)
return unless TAP_DIRECTORY.directory?
return to_enum unless block_given?
return to_enum unless block
TAP_DIRECTORY.subdirs.each do |user|
user.subdirs.each do |repo|

View File

@ -10,14 +10,14 @@ end
describe "brew --cache", :integration_test do
it "prints all cache files for a given Formula" do
expect { brew "--cache", testball }
.to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--testball-}).to_stdout
.to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--testball-}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end
it "prints the cache files for a given Cask" do
expect { brew "--cache", cask_path("local-caffeine") }
.to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--caffeine\.zip}).to_stdout
.to output(%r{#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--caffeine\.zip}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end
@ -28,7 +28,7 @@ describe "brew --cache", :integration_test do
%r{
#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--testball-.*\n
#{HOMEBREW_CACHE}/downloads/[\da-f]{64}--caffeine\.zip
}x,
}xo,
).to_stdout
.and not_to_output.to_stderr
.and be_a_success

View File

@ -10,7 +10,7 @@ end
describe "brew --cellar", :integration_test do
it "returns the Cellar subdirectory for a given Formula" do
expect { brew "--cellar", testball }
.to output(%r{#{HOMEBREW_CELLAR}/testball}).to_stdout
.to output(%r{#{HOMEBREW_CELLAR}/testball}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end

View File

@ -10,7 +10,7 @@ end
describe "brew --prefix", :integration_test do
it "prints a given Formula's prefix" do
expect { brew "--prefix", testball }
.to output(%r{#{HOMEBREW_CELLAR}/testball}).to_stdout
.to output(%r{#{HOMEBREW_CELLAR}/testball}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end

View File

@ -10,7 +10,7 @@ end
describe "brew --version", :integration_test do
it "prints the Homebrew version" do
expect { brew "--version" }
.to output(/^Homebrew #{Regexp.escape(HOMEBREW_VERSION)}\n/).to_stdout
.to output(/^Homebrew #{Regexp.escape(HOMEBREW_VERSION)}\n/o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end

View File

@ -22,7 +22,7 @@ describe "brew cleanup", :integration_test do
(HOMEBREW_CACHE/"test").write "test"
expect { brew "cleanup", "--prune=all" }
.to output(%r{#{Regexp.escape(HOMEBREW_CACHE)}/test}).to_stdout
.to output(%r{#{Regexp.escape(HOMEBREW_CACHE)}/test}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end

View File

@ -10,7 +10,7 @@ end
describe "brew config", :integration_test do
it "prints information about the current Homebrew configuration" do
expect { brew "config" }
.to output(/HOMEBREW_VERSION: #{Regexp.escape HOMEBREW_VERSION}/).to_stdout
.to output(/HOMEBREW_VERSION: #{Regexp.escape HOMEBREW_VERSION}/o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end

View File

@ -12,7 +12,7 @@ describe "brew install", :integration_test do
setup_test_formula "testball1"
expect { brew "install", "testball1" }
.to output(%r{#{HOMEBREW_CELLAR}/testball1/0\.1}).to_stdout
.to output(%r{#{HOMEBREW_CELLAR}/testball1/0\.1}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
expect(HOMEBREW_CELLAR/"testball1/0.1/foo/test").not_to be_a_file
@ -22,7 +22,7 @@ describe "brew install", :integration_test do
setup_test_formula "testball1"
expect { brew "install", "testball1", "--with-foo" }
.to output(%r{#{HOMEBREW_CELLAR}/testball1/0\.1}).to_stdout
.to output(%r{#{HOMEBREW_CELLAR}/testball1/0\.1}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
expect(HOMEBREW_CELLAR/"testball1/0.1/foo/test").to be_a_file
@ -36,7 +36,7 @@ describe "brew install", :integration_test do
RUBY
expect { brew "install", "testball1" }
.to output(%r{#{HOMEBREW_CELLAR}/testball1/1\.0}).to_stdout
.to output(%r{#{HOMEBREW_CELLAR}/testball1/1\.0}o).to_stdout
.and not_to_output.to_stderr
.and be_a_success
expect(HOMEBREW_CELLAR/"testball1/1.0/foo/test").not_to be_a_file
@ -69,7 +69,7 @@ describe "brew install", :integration_test do
# and there will be the git requirement, but we cannot instantiate git
# formula since we only have testball1 formula.
expect { brew "install", "testball1", "--HEAD", "--ignore-dependencies" }
.to output(%r{#{HOMEBREW_CELLAR}/testball1/HEAD-d5eb689}).to_stdout
.to output(%r{#{HOMEBREW_CELLAR}/testball1/HEAD-d5eb689}o).to_stdout
.and output(/Cloning into/).to_stderr
.and be_a_success
expect(HOMEBREW_CELLAR/"testball1/HEAD-d5eb689/foo/test").not_to be_a_file

View File

@ -10,7 +10,7 @@ end
describe "brew command", :integration_test do
it "returns the file for a given command" do
expect { brew "command", "info" }
.to output(%r{#{Regexp.escape(HOMEBREW_LIBRARY_PATH)}/cmd/info.rb}).to_stdout
.to output(%r{#{Regexp.escape(HOMEBREW_LIBRARY_PATH)}/cmd/info.rb}o).to_stdout
.and be_a_success
end
end

View File

@ -66,7 +66,7 @@ describe Homebrew::Diagnostic::Checks do
specify "#check_user_path_2" do
ENV["PATH"] = ENV["PATH"].gsub \
%r{(?:^|#{File::PATH_SEPARATOR})#{HOMEBREW_PREFIX}/bin}, ""
%r{(?:^|#{File::PATH_SEPARATOR})#{HOMEBREW_PREFIX}/bin}o, ""
expect(subject.check_user_path_1).to be nil
expect(subject.check_user_path_2)

View File

@ -7,15 +7,15 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/concurrent-ruby-1.1.7
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/i18n-1.8.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/minitest-5.14.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thread_safe-0.3.6/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-1.2.7/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/zeitwerk-2.4.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-1.2.8/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/zeitwerk-2.4.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-6.0.3.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ast-2.4.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/bindata-2.4.8/lib"
$:.unshift "#{path}/"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/byebug-11.1.3"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/byebug-11.1.3"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/byebug-11.1.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/json-2.3.1"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/json-2.3.1"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/json-2.3.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/docile-1.3.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.3/lib"
@ -27,20 +27,20 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/highline-2.0.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/commander-4.5.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/diff-lcs-1.4.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/unf_ext-0.0.7.7"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/unf_ext-0.0.7.7"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf_ext-0.0.7.7/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf-0.1.4/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/domain_name-0.5.20190701/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/elftools-1.1.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/hpricot-0.8.6"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/hpricot-0.8.6"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/hpricot-0.8.6/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/http-cookie-1.0.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mime-types-data-3.2020.0512/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mime-types-data-3.2020.1104/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mime-types-3.3.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/net-http-digest_auth-1.4.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/net-http-persistent-4.0.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mini_portile2-2.4.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/nokogiri-1.10.10"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/nokogiri-1.10.10"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/nokogiri-1.10.10/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ntlm-http-0.1.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/webrobots-0.1.2/lib"
@ -51,12 +51,12 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel-1.20.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parallel_tests-3.3.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parser-2.7.2.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.6040/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-0.5.6076/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parlour-4.0.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/patchelf-1.3.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.5.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/pry-0.13.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/rdiscount-2.2.0.2"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/rdiscount-2.2.0.2"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rdiscount-2.2.0.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/regexp_parser-1.8.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rexml-3.2.4/lib"
@ -72,13 +72,13 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-wait-0.0.9/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-ast-1.1.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.10.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.3.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.8.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.3.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.9.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.0.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.5.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.5.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.6042-universal-darwin-19/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.6042/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.6076-universal-darwin-20/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.6076/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-stub-0.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.0.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/spoom-1.0.4/lib"

View File

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

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
module RuboCop
module Performance
module Version
STRING = '1.8.1'
end
end
end

View File

@ -7,6 +7,11 @@ Performance/AncestorsInclude:
Safe: false
VersionAdded: '1.7'
Performance/ArraySemiInfiniteRangeSlice:
Description: 'Identifies places where slicing arrays with semi-infinite ranges can be replaced by `Array#take` and `Array#drop`.'
Enabled: pending
VersionAdded: '1.9'
Performance/BigDecimalWithNumericArgument:
Description: 'Convert numeric argument to string before passing to BigDecimal.'
Enabled: 'pending'
@ -17,11 +22,17 @@ Performance/BindCall:
Enabled: true
VersionAdded: '1.6'
Performance/BlockGivenWithExplicitBlock:
Description: 'Check block argument explicitly instead of using `block_given?`.'
Enabled: pending
VersionAdded: '1.9'
Performance/Caller:
Description: >-
Use `caller(n..n)` instead of `caller`.
Enabled: true
VersionAdded: '0.49'
VersionChanged: '1.9'
Performance/CaseWhenSplat:
Description: >-
@ -51,7 +62,7 @@ Performance/ChainArrayAllocation:
Performance/CollectionLiteralInLoop:
Description: 'Extract Array and Hash literals outside of loops into local variables or constants.'
Enabled: true
Enabled: 'pending'
VersionAdded: '1.8'
# Min number of elements to consider an offense
MinSize: 1
@ -61,6 +72,11 @@ Performance/CompareWithBlock:
Enabled: true
VersionAdded: '0.46'
Performance/ConstantRegexp:
Description: 'Finds regular expressions with dynamic components that are all constants.'
Enabled: pending
VersionAdded: '1.9'
Performance/Count:
Description: >-
Use `count` instead of `{select,find_all,filter,reject}...{size,count,length}`.
@ -154,6 +170,12 @@ Performance/IoReadlines:
Enabled: false
VersionAdded: '1.7'
Performance/MethodObjectAsBlock:
Description: 'Use block explicitly instead of block-passing a method object.'
Reference: 'https://github.com/JuanitoFatas/fast-ruby#normal-way-to-apply-method-vs-method-code'
Enabled: pending
VersionAdded: '1.9'
Performance/OpenStruct:
Description: 'Use `Struct` instead of `OpenStruct`.'
Enabled: false
@ -283,7 +305,9 @@ Performance/TimesMap:
Performance/UnfreezeString:
Description: 'Use unary plus to get an unfrozen string literal.'
Enabled: true
SafeAutoCorrect: false
VersionAdded: '0.50'
VersionChanged: '1.9'
Performance/UriDefaultParser:
Description: 'Use `URI::DEFAULT_PARSER` instead of `URI::Parser.new`.'

View File

@ -25,7 +25,7 @@ module RuboCop
# (tricky: \s, \d, and so on are metacharacters, but other characters
# escaped with a slash are just literals. LITERAL_REGEX takes all
# that into account.)
/\A\\A(?:#{Util::LITERAL_REGEX})+\z/.match?(regex_str)
/\A\\A(?:#{Util::LITERAL_REGEX})+\z/o.match?(regex_str)
end
def literal_at_start_with_caret?(regex_str)
@ -35,21 +35,21 @@ module RuboCop
# (tricky: \s, \d, and so on are metacharacters, but other characters
# escaped with a slash are just literals. LITERAL_REGEX takes all
# that into account.)
/\A\^(?:#{Util::LITERAL_REGEX})+\z/.match?(regex_str)
/\A\^(?:#{Util::LITERAL_REGEX})+\z/o.match?(regex_str)
end
def literal_at_end_with_backslash_z?(regex_str)
# is this regexp 'literal' in the sense of only matching literal
# chars, rather than using metachars like . and * and so on?
# also, is it anchored at the end of the string?
/\A(?:#{Util::LITERAL_REGEX})+\\z\z/.match?(regex_str)
/\A(?:#{Util::LITERAL_REGEX})+\\z\z/o.match?(regex_str)
end
def literal_at_end_with_dollar?(regex_str)
# is this regexp 'literal' in the sense of only matching literal
# chars, rather than using metachars like . and * and so on?
# also, is it anchored at the end of the string?
/\A(?:#{Util::LITERAL_REGEX})+\$\z/.match?(regex_str)
/\A(?:#{Util::LITERAL_REGEX})+\$\z/o.match?(regex_str)
end
def drop_start_metacharacter(regexp_string)

View File

@ -18,6 +18,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `<=` instead of `ancestors.include?`.'
RESTRICT_ON_SEND = %i[include?].freeze
def_node_matcher :ancestors_include_candidate?, <<~PATTERN
(send (send $_subclass :ancestors) :include? $_superclass)

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# This cop identifies places where slicing arrays with semi-infinite ranges
# can be replaced by `Array#take` and `Array#drop`.
#
# @example
# # bad
# # array[..2]
# # array[...2]
# # array[2..]
# # array[2...]
# # array.slice(..2)
#
# # good
# array.take(3)
# array.take(2)
# array.drop(2)
# array.drop(2)
# array.take(3)
#
class ArraySemiInfiniteRangeSlice < Base
include RangeHelp
extend AutoCorrector
extend TargetRubyVersion
minimum_target_ruby_version 2.7
MSG = 'Use `%<prefer>s` instead of `%<current>s` with semi-infinite range.'
SLICE_METHODS = Set[:[], :slice].freeze
RESTRICT_ON_SEND = SLICE_METHODS
def_node_matcher :endless_range_slice?, <<~PATTERN
(send $_ $%SLICE_METHODS $#endless_range?)
PATTERN
def_node_matcher :endless_range?, <<~PATTERN
{
({irange erange} nil? (int positive?))
({irange erange} (int positive?) nil?)
}
PATTERN
def on_send(node)
endless_range_slice?(node) do |receiver, method_name, range_node|
prefer = range_node.begin ? :drop : :take
message = format(MSG, prefer: prefer, current: method_name)
add_offense(node, message: message) do |corrector|
corrector.replace(node, correction(receiver, range_node))
end
end
end
private
def correction(receiver, range_node)
method_call = if range_node.begin
"drop(#{range_node.begin.value})"
elsif range_node.irange_type?
"take(#{range_node.end.value + 1})"
else
"take(#{range_node.end.value})"
end
"#{receiver.source}.#{method_call}"
end
end
end
end
end

View File

@ -20,6 +20,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Convert numeric argument to string before passing to `BigDecimal`.'
RESTRICT_ON_SEND = %i[BigDecimal].freeze
def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
(send nil? :BigDecimal $numeric_type? ...)

View File

@ -28,6 +28,7 @@ module RuboCop
MSG = 'Use `bind_call(%<bind_arg>s%<comma>s%<call_args>s)` ' \
'instead of `bind(%<bind_arg>s).call(%<call_args>s)`.'
RESTRICT_ON_SEND = %i[call].freeze
def_node_matcher :bind_with_call_method?, <<~PATTERN
(send

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# This cop identifies unnecessary use of a `block_given?` where explicit check
# of block argument would suffice.
#
# @example
# # bad
# def method(&block)
# do_something if block_given?
# end
#
# # good
# def method(&block)
# do_something if block
# end
#
# # good - block is reassigned
# def method(&block)
# block ||= -> { do_something }
# warn "Using default ..." unless block_given?
# # ...
# end
#
class BlockGivenWithExplicitBlock < Base
extend AutoCorrector
RESTRICT_ON_SEND = %i[block_given?].freeze
MSG = 'Check block argument explicitly instead of using `block_given?`.'
def_node_matcher :reassigns_block_arg?, '`(lvasgn %1 ...)'
def on_send(node)
def_node = node.each_ancestor(:def, :defs).first
return unless def_node
block_arg = def_node.arguments.find(&:blockarg_type?)
return unless block_arg
block_arg_name = block_arg.loc.name.source.to_sym
return if reassigns_block_arg?(def_node, block_arg_name)
add_offense(node) do |corrector|
corrector.replace(node, block_arg_name)
end
end
end
end
end
end

View File

@ -19,10 +19,10 @@ module RuboCop
# caller_locations(2..2).first
# caller_locations(1..1).first
class Caller < Base
MSG_BRACE = 'Use `%<method>s(%<n>d..%<n>d).first`' \
' instead of `%<method>s[%<m>d]`.'
MSG_FIRST = 'Use `%<method>s(%<n>d..%<n>d).first`' \
' instead of `%<method>s.first`.'
extend AutoCorrector
MSG = 'Use `%<preferred_method>s` instead of `%<current_method>s`.'
RESTRICT_ON_SEND = %i[first []].freeze
def_node_matcher :slow_caller?, <<~PATTERN
{
@ -41,26 +41,24 @@ module RuboCop
def on_send(node)
return unless caller_with_scope_method?(node)
message = message(node)
add_offense(node, message: message)
end
private
def message(node)
method_name = node.receiver.method_name
caller_arg = node.receiver.first_argument
n = caller_arg ? int_value(caller_arg) : 1
if node.method?(:[])
m = int_value(node.first_argument)
n += m
format(MSG_BRACE, n: n, m: m, method: method_name)
else
format(MSG_FIRST, n: n, method: method_name)
end
preferred_method = "#{method_name}(#{n}..#{n}).first"
message = format(MSG, preferred_method: preferred_method, current_method: node.source)
add_offense(node, message: message) do |corrector|
corrector.replace(node, preferred_method)
end
end
private
def int_value(node)
node.children[0]
end

View File

@ -23,6 +23,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
RESTRICT_ON_SEND = %i[== eql? !=].freeze
CASE_METHODS = %i[downcase upcase].freeze
def_node_matcher :downcase_eq, <<~PATTERN

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# This cop finds regular expressions with dynamic components that are all constants.
#
# Ruby allocates a new Regexp object every time it executes a code containing such
# a regular expression. It is more efficient to extract it into a constant
# or add an `/o` option to perform `#{}` interpolation only once and reuse that
# Regexp object.
#
# @example
#
# # bad
# def tokens(pattern)
# pattern.scan(TOKEN).reject { |token| token.match?(/\A#{SEPARATORS}\Z/) }
# end
#
# # good
# ALL_SEPARATORS = /\A#{SEPARATORS}\Z/
# def tokens(pattern)
# pattern.scan(TOKEN).reject { |token| token.match?(ALL_SEPARATORS) }
# end
#
# # good
# def tokens(pattern)
# pattern.scan(TOKEN).reject { |token| token.match?(/\A#{SEPARATORS}\Z/o) }
# end
#
class ConstantRegexp < Base
extend AutoCorrector
MSG = 'Extract this regexp into a constant or append an `/o` option to its options.'
def on_regexp(node)
return if within_const_assignment?(node) ||
!include_interpolated_const?(node) ||
node.single_interpolation?
add_offense(node) do |corrector|
corrector.insert_after(node, 'o')
end
end
private
def within_const_assignment?(node)
node.each_ancestor(:casgn).any?
end
def_node_matcher :regexp_escape?, <<~PATTERN
(send
(const nil? :Regexp) :escape const_type?)
PATTERN
def include_interpolated_const?(node)
return false unless node.interpolation?
node.each_child_node(:begin).all? do |begin_node|
inner_node = begin_node.children.first
inner_node && (inner_node.const_type? || regexp_escape?(inner_node))
end
end
end
end
end
end

View File

@ -42,6 +42,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
RESTRICT_ON_SEND = %i[count length size].freeze
def_node_matcher :count_candidate?, <<~PATTERN
{

View File

@ -51,6 +51,7 @@ module RuboCop
minimum_target_ruby_version 2.5
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
PREFERRED_METHODS = {
gsub: :delete_prefix,

View File

@ -51,6 +51,7 @@ module RuboCop
minimum_target_ruby_version 2.5
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
PREFERRED_METHODS = {
gsub: :delete_suffix,

View File

@ -4,8 +4,8 @@ module RuboCop
module Cop
module Performance
# This cop is used to identify usages of `first`, `last`, `[0]` or `[-1]`
# chained to `select`, `find_all`, or `find_all`
# and change them to use `detect` instead.
# chained to `select`, `find_all` or `filter` and change them to use
# `detect` instead.
#
# @example
# # bad
@ -39,6 +39,7 @@ module RuboCop
'`%<first_method>s[%<index>i]`.'
INDEX_REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
'`%<first_method>s[%<index>i]`.'
RESTRICT_ON_SEND = %i[first last []].freeze
def_node_matcher :detect_candidate?, <<~PATTERN
{

View File

@ -47,6 +47,7 @@ module RuboCop
MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
'the end of the string.'
RESTRICT_ON_SEND = %i[match =~ match?].freeze
def_node_matcher :redundant_regex?, <<~PATTERN
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))

View File

@ -47,6 +47,7 @@ module RuboCop
#
class FixedSize < Base
MSG = 'Do not compute the size of statically sized objects.'
RESTRICT_ON_SEND = %i[count length size].freeze
def_node_matcher :counter, <<~MATCHER
(send ${array hash str sym} {:count :length :size} $...)

View File

@ -19,6 +19,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `flat_map` instead of `%<method>s...%<flatten>s`.'
RESTRICT_ON_SEND = %i[flatten flatten!].freeze
FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
'and `flatten` can be used to flatten ' \
'multiple levels.'

View File

@ -39,6 +39,8 @@ module RuboCop
class InefficientHashSearch < Base
extend AutoCorrector
RESTRICT_ON_SEND = %i[include?].freeze
def_node_matcher :inefficient_include?, <<~PATTERN
(send (send $_ {:keys :values}) :include? _)
PATTERN

View File

@ -29,14 +29,14 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
RESTRICT_ON_SEND = (Enumerable.instance_methods + [:each]).freeze
def_node_matcher :readlines_on_class?, <<~PATTERN
$(send $(send (const nil? {:IO :File}) :readlines ...) #enumerable_method?)
$(send $(send (const nil? {:IO :File}) :readlines ...) _)
PATTERN
def_node_matcher :readlines_on_instance?, <<~PATTERN
$(send $(send ${nil? !const_type?} :readlines ...) #enumerable_method? ...)
$(send $(send ${nil? !const_type?} :readlines ...) _ ...)
PATTERN
def on_send(node)
@ -55,10 +55,6 @@ module RuboCop
private
def enumerable_method?(node)
ENUMERABLE_METHODS.include?(node.to_sym)
end
def autocorrect(corrector, enumerable_call, readlines_call, receiver)
# We cannot safely correct `.readlines` method called on IO/File classes
# due to its signature and we are not sure with implicit receiver

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# This cop identifies places where methods are converted to blocks, with the
# use of `&method`, and passed as arguments to method calls.
# It is faster to replace those with explicit blocks, calling those methods inside.
#
# @example
# # bad
# array.map(&method(:do_something))
# [1, 2, 3].each(&out.method(:puts))
#
# # good
# array.map { |x| do_something(x) }
# [1, 2, 3].each { |x| out.puts(x) }
#
class MethodObjectAsBlock < Base
MSG = 'Use block explicitly instead of block-passing a method object.'
def_node_matcher :method_object_as_argument?, <<~PATTERN
(^send (send _ :method sym))
PATTERN
def on_block_pass(node)
add_offense(node) if method_object_as_argument?(node)
end
end
end
end
end

View File

@ -30,6 +30,7 @@ module RuboCop
class OpenStruct < Base
MSG = 'Consider using `Struct` over `OpenStruct` ' \
'to optimize the performance.'
RESTRICT_ON_SEND = %i[new].freeze
def_node_matcher :open_struct, <<~PATTERN
(send (const {nil? cbase} :OpenStruct) :new ...)

View File

@ -28,6 +28,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
RESTRICT_ON_SEND = %i[include? member?].freeze
# TODO: If we traced out assignments of variables to their uses, we
# might pick up on a few more instances of this issue

View File

@ -80,11 +80,11 @@ module RuboCop
def calls_to_report(argname, body)
return [] if blockarg_assigned?(body, argname)
calls = to_enum(:blockarg_calls, body, argname)
blockarg_calls(body, argname).map do |call|
return [] if args_include_block_pass?(call)
return [] if calls.any? { |call| args_include_block_pass?(call) }
calls
call
end
end
def args_include_block_pass?(blockcall)

View File

@ -22,6 +22,7 @@ module RuboCop
MSG = 'Use `=~` in places where the `MatchData` returned by ' \
'`#match` will not be used.'
RESTRICT_ON_SEND = %i[match].freeze
# 'match' is a fairly generic name, so we don't flag it unless we see
# a string or regexp literal on one side or the other

View File

@ -29,6 +29,7 @@ module RuboCop
AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
RESTRICT_ON_SEND = %i[merge!].freeze
WITH_MODIFIER_CORRECTION = <<~RUBY
%<keyword>s %<condition>s

View File

@ -44,10 +44,10 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
RESTRICT_ON_SEND = %i[[] slice first last take drop length size empty?].freeze
def_node_matcher :redundant_chars_call?, <<~PATTERN
(send $(send _ :chars) $#replaceable_method? $...)
(send $(send _ :chars) $_ $...)
PATTERN
def on_send(node)
@ -66,10 +66,6 @@ module RuboCop
private
def replaceable_method?(method_name)
REPLACEABLE_METHODS.include?(method_name)
end
def offense_range(receiver, node)
range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
end

View File

@ -17,6 +17,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `reverse_each` instead of `reverse.each`.'
RESTRICT_ON_SEND = %i[each].freeze
UNDERSCORE = '_'
def_node_matcher :reverse_each?, <<~MATCHER

View File

@ -21,6 +21,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
RESTRICT_ON_SEND = %i[first].freeze
def_node_matcher :reverse_first_candidate?, <<~PATTERN
(send $(send _ :reverse) :first (int _)?)

View File

@ -39,6 +39,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `size` instead of `count`.'
RESTRICT_ON_SEND = %i[count].freeze
def_node_matcher :array?, <<~PATTERN
{

View File

@ -22,6 +22,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
RESTRICT_ON_SEND = %i[gsub gsub!].freeze
PREFERRED_METHODS = {
gsub: :squeeze,
@ -58,7 +59,7 @@ module RuboCop
private
def repeating_literal?(regex_str)
regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/o)
end
end
end

View File

@ -47,6 +47,7 @@ module RuboCop
MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
'the beginning of the string.'
RESTRICT_ON_SEND = %i[match =~ match?].freeze
def_node_matcher :redundant_regex?, <<~PATTERN
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))

View File

@ -23,6 +23,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
RESTRICT_ON_SEND = %i[match =~ match?].freeze
def_node_matcher :redundant_regex?, <<~PATTERN
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
@ -47,7 +48,7 @@ module RuboCop
private
def literal?(regex_str)
regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/o)
end
end
end

View File

@ -23,6 +23,7 @@ module RuboCop
extend AutoCorrector
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
RESTRICT_ON_SEND = %i[gsub gsub!].freeze
DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
DELETE = 'delete'
TR = 'tr'

View File

@ -0,0 +1,236 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Performance
# This cop identifies places where custom code finding the sum of elements
# in some Enumerable object can be replaced by `Enumerable#sum` method.
#
# This cop can change auto-correction scope depending on the value of
# `SafeAutoCorrect`.
# Its auto-correction is marked as safe by default (`SafeAutoCorrect: true`)
# to prevent `TypeError` in auto-correced code when initial value is not
# specified as shown below:
#
# [source,ruby]
# ----
# ['a', 'b'].sum # => (String can't be coerced into Integer)
# ----
#
# Therefore if initial value is not specified, unsafe auto-corrected will not occur.
#
# If you always want to enable auto-correction, you can set `SafeAutoCorrect: false`.
#
# [source,yaml]
# ----
# Performance/Sum:
# SafeAutoCorrect: false
# ----
#
# Please note that the auto-correction command line option will be changed from
# `rubocop -a` to `rubocop -A`, which includes unsafe auto-correction.
#
# @example
# # bad
# [1, 2, 3].inject(:+) # These bad cases with no initial value are unsafe and
# [1, 2, 3].inject(&:+) # will not be auto-correced by default. If you want to
# [1, 2, 3].reduce { |acc, elem| acc + elem } # auto-corrected, you can set `SafeAutoCorrect: false`.
# [1, 2, 3].reduce(10, :+)
# [1, 2, 3].map { |elem| elem ** 2 }.sum
# [1, 2, 3].collect(&:count).sum(10)
#
# # good
# [1, 2, 3].sum
# [1, 2, 3].sum(10)
# [1, 2, 3].sum { |elem| elem ** 2 }
# [1, 2, 3].sum(10, &:count)
#
class Sum < Base
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
MSG_IF_NO_INIT_VALUE =
'Use `%<good_method>s` instead of `%<bad_method>s`, unless calling `%<bad_method>s` on an empty array.'
RESTRICT_ON_SEND = %i[inject reduce sum].freeze
def_node_matcher :sum_candidate?, <<~PATTERN
(send _ ${:inject :reduce} $_init ? ${(sym :+) (block_pass (sym :+))})
PATTERN
def_node_matcher :sum_map_candidate?, <<~PATTERN
(send
{
(block $(send _ {:map :collect}) ...)
$(send _ {:map :collect} (block_pass _))
}
:sum $_init ?)
PATTERN
def_node_matcher :sum_with_block_candidate?, <<~PATTERN
(block
$(send _ {:inject :reduce} $_init ?)
(args (arg $_acc) (arg $_elem))
$send)
PATTERN
def_node_matcher :acc_plus_elem?, <<~PATTERN
(send (lvar %1) :+ (lvar %2))
PATTERN
alias elem_plus_acc? acc_plus_elem?
def on_send(node)
return if empty_array_literal?(node)
handle_sum_candidate(node)
handle_sum_map_candidate(node)
end
def on_block(node)
sum_with_block_candidate?(node) do |send, init, var_acc, var_elem, body|
if acc_plus_elem?(body, var_acc, var_elem) || elem_plus_acc?(body, var_elem, var_acc)
range = sum_block_range(send, node)
message = build_block_message(send, init, var_acc, var_elem, body)
add_offense(range, message: message) do |corrector|
autocorrect(corrector, init, range)
end
end
end
end
private
def handle_sum_candidate(node)
sum_candidate?(node) do |method, init, operation|
range = sum_method_range(node)
message = build_method_message(node, method, init, operation)
add_offense(range, message: message) do |corrector|
autocorrect(corrector, init, range)
end
end
end
def handle_sum_map_candidate(node)
sum_map_candidate?(node) do |map, init|
next if node.block_literal? || node.block_argument?
message = build_sum_map_message(map.method_name, init)
add_offense(sum_map_range(map, node), message: message) do |corrector|
autocorrect_sum_map(corrector, node, map, init)
end
end
end
def empty_array_literal?(node)
receiver = node.children.first
array_literal?(node) && receiver && receiver.children.empty?
end
def array_literal?(node)
receiver = node.children.first
receiver&.literal? && receiver&.array_type?
end
def autocorrect(corrector, init, range)
return if init.empty? && safe_autocorrect?
replacement = build_good_method(init)
corrector.replace(range, replacement)
end
def autocorrect_sum_map(corrector, sum, map, init)
sum_range = method_call_with_args_range(sum)
map_range = method_call_with_args_range(map)
block_pass = map.last_argument if map.last_argument&.block_pass_type?
replacement = build_good_method(init, block_pass)
corrector.remove(sum_range)
corrector.replace(map_range, ".#{replacement}")
end
def sum_method_range(node)
range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
end
def sum_map_range(map, sum)
range_between(map.loc.selector.begin_pos, sum.source_range.end.end_pos)
end
def sum_block_range(send, node)
range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
end
def build_method_message(node, method, init, operation)
good_method = build_good_method(init)
bad_method = build_method_bad_method(init, method, operation)
msg = if init.empty? && !array_literal?(node)
MSG_IF_NO_INIT_VALUE
else
MSG
end
format(msg, good_method: good_method, bad_method: bad_method)
end
def build_sum_map_message(method, init)
sum_method = build_good_method(init)
good_method = "#{sum_method} { ... }"
bad_method = "#{method} { ... }.#{sum_method}"
format(MSG, good_method: good_method, bad_method: bad_method)
end
def build_block_message(send, init, var_acc, var_elem, body)
good_method = build_good_method(init)
bad_method = build_block_bad_method(send.method_name, init, var_acc, var_elem, body)
format(MSG, good_method: good_method, bad_method: bad_method)
end
def build_good_method(init, block_pass = nil)
good_method = 'sum'
args = []
unless init.empty?
init = init.first
args << init.source unless init.int_type? && init.value.zero?
end
args << block_pass.source if block_pass
good_method += "(#{args.join(', ')})" unless args.empty?
good_method
end
def build_method_bad_method(init, method, operation)
bad_method = "#{method}("
unless init.empty?
init = init.first
bad_method += "#{init.source}, "
end
bad_method += if operation.block_pass_type?
'&:+)'
else
':+)'
end
bad_method
end
def build_block_bad_method(method, init, var_acc, var_elem, body)
bad_method = method.to_s
unless init.empty?
init = init.first
bad_method += "(#{init.source})"
end
bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
bad_method
end
def method_call_with_args_range(node)
node.receiver.source_range.end.join(node.source_range.end)
end
end
end
end
end

View File

@ -23,6 +23,7 @@ module RuboCop
MESSAGE = 'Use `Array.new(%<count>s)` with a block ' \
'instead of `.times.%<map_or_collect>s`'
MESSAGE_ONLY_IF = 'only if `%<count>s` is always 0 or more'
RESTRICT_ON_SEND = %i[map collect].freeze
def on_send(node)
check(node)

View File

@ -10,6 +10,7 @@ module RuboCop
# NOTE: `String.new` (without operator) is not exactly the same as `+''`.
# These differ in encoding. `String.new.encoding` is always `ASCII-8BIT`.
# However, `(+'').encoding` is the same as script encoding(e.g. `UTF-8`).
# Therefore, auto-correction is unsafe.
# So, if you expect `ASCII-8BIT` encoding, disable this cop.
#
# @example
@ -24,7 +25,10 @@ module RuboCop
# +'something'
# +''
class UnfreezeString < Base
extend AutoCorrector
MSG = 'Use unary plus to get an unfrozen string literal.'
RESTRICT_ON_SEND = %i[dup new].freeze
def_node_matcher :dup_string?, <<~PATTERN
(send {str dstr} :dup)
@ -38,7 +42,21 @@ module RuboCop
PATTERN
def on_send(node)
add_offense(node) if dup_string?(node) || string_new?(node)
return unless dup_string?(node) || string_new?(node)
add_offense(node) do |corrector|
corrector.replace(node, "+#{string_value(node)}")
end
end
private
def string_value(node)
if node.receiver.source == 'String' && node.method?(:new)
node.arguments.empty? ? "''" : node.first_argument.source
else
node.receiver.source
end
end
end
end

View File

@ -18,6 +18,7 @@ module RuboCop
MSG = 'Use `%<double_colon>sURI::DEFAULT_PARSER` instead of ' \
'`%<double_colon>sURI::Parser.new`.'
RESTRICT_ON_SEND = %i[new].freeze
def_node_matcher :uri_parser_new?, <<~PATTERN
(send

View File

@ -4,13 +4,16 @@ require_relative 'mixin/regexp_metacharacter'
require_relative 'mixin/sort_block'
require_relative 'performance/ancestors_include'
require_relative 'performance/array_semi_infinite_range_slice'
require_relative 'performance/big_decimal_with_numeric_argument'
require_relative 'performance/bind_call'
require_relative 'performance/block_given_with_explicit_block'
require_relative 'performance/caller'
require_relative 'performance/case_when_splat'
require_relative 'performance/casecmp'
require_relative 'performance/collection_literal_in_loop'
require_relative 'performance/compare_with_block'
require_relative 'performance/constant_regexp'
require_relative 'performance/count'
require_relative 'performance/delete_prefix'
require_relative 'performance/delete_suffix'
@ -20,6 +23,7 @@ require_relative 'performance/end_with'
require_relative 'performance/fixed_size'
require_relative 'performance/flat_map'
require_relative 'performance/inefficient_hash_search'
require_relative 'performance/method_object_as_block'
require_relative 'performance/open_struct'
require_relative 'performance/range_include'
require_relative 'performance/io_readlines'

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module RuboCop
module Performance
# This module holds the RuboCop Performance version information.
module Version
STRING = '1.9.0'
def self.document_version
STRING.match('\d+\.\d+').to_s
end
end
end
end

View File

@ -1,300 +0,0 @@
module TZInfo
# Use send as a workaround for erroneous 'wrong number of arguments' errors
# with JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
send(:using, RubyCoreSupport::UntaintExt) if RubyCoreSupport.const_defined?(:UntaintExt)
# An InvalidZoneinfoFile exception is raised if an attempt is made to load an
# invalid zoneinfo file.
class InvalidZoneinfoFile < StandardError
end
# Represents a timezone defined by a compiled zoneinfo TZif (\0, 2 or 3) file.
#
# @private
class ZoneinfoTimezoneInfo < TransitionDataTimezoneInfo #:nodoc:
# Minimum supported timestamp (inclusive).
#
# Time.utc(1700, 1, 1).to_i
MIN_TIMESTAMP = -8520336000
# Maximum supported timestamp (exclusive).
#
# Time.utc(2500, 1, 1).to_i
MAX_TIMESTAMP = 16725225600
# Constructs the new ZoneinfoTimezoneInfo with an identifier and path
# to the file.
def initialize(identifier, file_path)
super(identifier)
File.open(file_path, 'rb') do |file|
parse(file)
end
end
private
# Unpack will return unsigned 32-bit integers. Translate to
# signed 32-bit.
def make_signed_int32(long)
long >= 0x80000000 ? long - 0x100000000 : long
end
# Unpack will return a 64-bit integer as two unsigned 32-bit integers
# (most significant first). Translate to signed 64-bit
def make_signed_int64(high, low)
unsigned = (high << 32) | low
unsigned >= 0x8000000000000000 ? unsigned - 0x10000000000000000 : unsigned
end
# Read bytes from file and check that the correct number of bytes could
# be read. Raises InvalidZoneinfoFile if the number of bytes didn't match
# the number requested.
def check_read(file, bytes)
result = file.read(bytes)
unless result && result.length == bytes
raise InvalidZoneinfoFile, "Expected #{bytes} bytes reading '#{file.path}', but got #{result ? result.length : 0} bytes"
end
result
end
# Zoneinfo files don't include the offset from standard time (std_offset)
# for DST periods. Derive the base offset (utc_offset) where DST is
# observed from either the previous or next non-DST period.
#
# Returns the index of the offset to be used prior to the first
# transition.
def derive_offsets(transitions, offsets)
# The first non-DST offset (if there is one) is the offset observed
# before the first transition. Fallback to the first DST offset if there
# are no non-DST offsets.
first_non_dst_offset_index = offsets.index {|o| !o[:is_dst] }
first_offset_index = first_non_dst_offset_index || 0
return first_offset_index if transitions.empty?
# Determine the utc_offset of the next non-dst offset at each transition.
utc_offset_from_next = nil
transitions.reverse_each do |transition|
offset = offsets[transition[:offset]]
if offset[:is_dst]
transition[:utc_offset_from_next] = utc_offset_from_next if utc_offset_from_next
else
utc_offset_from_next = offset[:utc_total_offset]
end
end
utc_offset_from_previous = first_non_dst_offset_index ? offsets[first_non_dst_offset_index][:utc_total_offset] : nil
defined_offsets = {}
transitions.each do |transition|
offset_index = transition[:offset]
offset = offsets[offset_index]
utc_total_offset = offset[:utc_total_offset]
if offset[:is_dst]
utc_offset_from_next = transition[:utc_offset_from_next]
difference_to_previous = (utc_total_offset - (utc_offset_from_previous || utc_total_offset)).abs
difference_to_next = (utc_total_offset - (utc_offset_from_next || utc_total_offset)).abs
utc_offset = if difference_to_previous == 3600
utc_offset_from_previous
elsif difference_to_next == 3600
utc_offset_from_next
elsif difference_to_previous > 0 && difference_to_next > 0
difference_to_previous < difference_to_next ? utc_offset_from_previous : utc_offset_from_next
elsif difference_to_previous > 0
utc_offset_from_previous
elsif difference_to_next > 0
utc_offset_from_next
else
# No difference, assume a 1 hour offset from standard time.
utc_total_offset - 3600
end
if !offset[:utc_offset]
offset[:utc_offset] = utc_offset
defined_offsets[offset] = offset_index
elsif offset[:utc_offset] != utc_offset
# An earlier transition has already derived a different
# utc_offset. Define a new offset or reuse an existing identically
# defined offset.
new_offset = offset.dup
new_offset[:utc_offset] = utc_offset
offset_index = defined_offsets[new_offset]
unless offset_index
offsets << new_offset
offset_index = offsets.length - 1
defined_offsets[new_offset] = offset_index
end
transition[:offset] = offset_index
end
else
utc_offset_from_previous = utc_total_offset
end
end
first_offset_index
end
# Defines an offset for the timezone based on the given index and offset
# Hash.
def define_offset(index, offset)
utc_total_offset = offset[:utc_total_offset]
utc_offset = offset[:utc_offset]
if utc_offset
# DST offset with base utc_offset derived by derive_offsets.
std_offset = utc_total_offset - utc_offset
elsif offset[:is_dst]
# DST offset unreferenced by a transition (offset in use before the
# first transition). No derived base UTC offset, so assume 1 hour
# DST.
utc_offset = utc_total_offset - 3600
std_offset = 3600
else
# Non-DST offset.
utc_offset = utc_total_offset
std_offset = 0
end
offset index, utc_offset, std_offset, offset[:abbr].untaint.to_sym
end
# Parses a zoneinfo file and intializes the DataTimezoneInfo structures.
def parse(file)
magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
check_read(file, 44).unpack('a4 a x15 NNNNNN')
if magic != 'TZif'
raise InvalidZoneinfoFile, "The file '#{file.path}' does not start with the expected header."
end
if (version == '2' || version == '3') && RubyCoreSupport.time_supports_64bit
# Skip the first 32-bit section and read the header of the second 64-bit section
file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisgmtcnt + ttisstdcnt, IO::SEEK_CUR)
prev_version = version
magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
check_read(file, 44).unpack('a4 a x15 NNNNNN')
unless magic == 'TZif' && (version == prev_version)
raise InvalidZoneinfoFile, "The file '#{file.path}' contains an invalid 64-bit section header."
end
using_64bit = true
elsif version != '3' && version != '2' && version != "\0"
raise InvalidZoneinfoFile, "The file '#{file.path}' contains a version of the zoneinfo format that is not currently supported."
else
using_64bit = false
end
unless leapcnt == 0
raise InvalidZoneinfoFile, "The zoneinfo file '#{file.path}' contains leap second data. TZInfo requires zoneinfo files that omit leap seconds."
end
transitions = []
if using_64bit
timecnt.times do |i|
high, low = check_read(file, 8).unpack('NN'.freeze)
transition_time = make_signed_int64(high, low)
transitions << {:at => transition_time}
end
else
timecnt.times do |i|
transition_time = make_signed_int32(check_read(file, 4).unpack('N'.freeze)[0])
transitions << {:at => transition_time}
end
end
timecnt.times do |i|
localtime_type = check_read(file, 1).unpack('C'.freeze)[0]
transitions[i][:offset] = localtime_type
end
offsets = []
typecnt.times do |i|
gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC'.freeze)
gmtoff = make_signed_int32(gmtoff)
isdst = isdst == 1
offset = {:utc_total_offset => gmtoff, :is_dst => isdst, :abbr_index => abbrind}
unless isdst
offset[:utc_offset] = gmtoff
offset[:std_offset] = 0
end
offsets << offset
end
abbrev = check_read(file, charcnt)
offsets.each do |o|
abbrev_start = o[:abbr_index]
raise InvalidZoneinfoFile, "Abbreviation index is out of range in file '#{file.path}'" unless abbrev_start < abbrev.length
abbrev_end = abbrev.index("\0", abbrev_start)
raise InvalidZoneinfoFile, "Missing abbreviation null terminator in file '#{file.path}'" unless abbrev_end
o[:abbr] = RubyCoreSupport.force_encoding(abbrev[abbrev_start...abbrev_end], 'UTF-8')
end
transitions.each do |t|
if t[:offset] < 0 || t[:offset] >= offsets.length
raise InvalidZoneinfoFile, "Invalid offset referenced by transition in file '#{file.path}'."
end
end
# Derive the offsets from standard time (std_offset).
first_offset_index = derive_offsets(transitions, offsets)
define_offset(first_offset_index, offsets[first_offset_index])
offsets.each_with_index do |o, i|
define_offset(i, o) unless i == first_offset_index
end
if !using_64bit && !RubyCoreSupport.time_supports_negative
# Filter out transitions that are not supported by Time on this
# platform.
# Move the last transition before the epoch up to the epoch. This
# allows for accurate conversions for all supported timestamps on the
# platform.
before_epoch, after_epoch = transitions.partition {|t| t[:at] < 0}
if before_epoch.length > 0 && after_epoch.length > 0 && after_epoch.first[:at] != 0
last_before = before_epoch.last
last_before[:at] = 0
transitions = [last_before] + after_epoch
else
transitions = after_epoch
end
end
# Ignore transitions that occur outside of a defined window. The
# transition index cannot handle a large range of transition times.
#
# This is primarily intended to ignore the far in the past transition
# added in zic 2014c (at timestamp -2**63 in zic 2014c and at the
# approximate time of the big bang from zic 2014d).
transitions.each do |t|
at = t[:at]
if at >= MIN_TIMESTAMP && at < MAX_TIMESTAMP
time = Time.at(at).utc
transition time.year, time.mon, t[:offset], at
end
end
end
end
end

View File

@ -10,6 +10,8 @@ require 'tzinfo/timezone_definition'
require 'tzinfo/timezone_offset'
require 'tzinfo/timezone_transition'
require 'tzinfo/transition_rule'
require 'tzinfo/annual_rules'
require 'tzinfo/timezone_transition_definition'
require 'tzinfo/timezone_index_definition'
@ -22,6 +24,7 @@ require 'tzinfo/zoneinfo_timezone_info'
require 'tzinfo/data_source'
require 'tzinfo/ruby_data_source'
require 'tzinfo/posix_time_zone_parser'
require 'tzinfo/zoneinfo_data_source'
require 'tzinfo/timezone_period'

View File

@ -0,0 +1,51 @@
module TZInfo
# A set of rules that define when transitions occur in time zones with
# annually occurring daylight savings time.
#
# @private
class AnnualRules #:nodoc:
# Returned by #transitions. #offset is the TimezoneOffset that applies
# from the UTC TimeOrDateTime #at. #previous_offset is the prior
# TimezoneOffset.
Transition = Struct.new(:offset, :previous_offset, :at)
# The standard offset that applies when daylight savings time is not in
# force.
attr_reader :std_offset
# The offset that applies when daylight savings time is in force.
attr_reader :dst_offset
# The rule that determines when daylight savings time starts.
attr_reader :dst_start_rule
# The rule that determines when daylight savings time ends.
attr_reader :dst_end_rule
# Initializes a new {AnnualRules} instance.
def initialize(std_offset, dst_offset, dst_start_rule, dst_end_rule)
@std_offset = std_offset
@dst_offset = dst_offset
@dst_start_rule = dst_start_rule
@dst_end_rule = dst_end_rule
end
# Returns the transitions between standard and daylight savings time for a
# given year. The results are ordered by time of occurrence (earliest to
# latest).
def transitions(year)
start_dst = apply_rule(@dst_start_rule, @std_offset, @dst_offset, year)
end_dst = apply_rule(@dst_end_rule, @dst_offset, @std_offset, year)
end_dst.at < start_dst.at ? [end_dst, start_dst] : [start_dst, end_dst]
end
private
# Applies a given rule between offsets on a year.
def apply_rule(rule, from_offset, to_offset, year)
at = rule.at(from_offset, year)
Transition.new(to_offset, from_offset, at)
end
end
end

View File

@ -0,0 +1,136 @@
# encoding: UTF-8
# frozen_string_literal: true
require 'strscan'
module TZInfo
# An {InvalidPosixTimeZone} exception is raised if an invalid POSIX-style
# time zone string is encountered.
#
# @private
class InvalidPosixTimeZone < StandardError #:nodoc:
end
# A parser for POSIX-style TZ strings used in zoneinfo files and specified
# by tzfile.5 and tzset.3.
#
# @private
class PosixTimeZoneParser #:nodoc:
# Parses a POSIX-style TZ string, returning either a TimezoneOffset or
# an AnnualRules instance.
def parse(tz_string)
raise InvalidPosixTimeZone unless tz_string.kind_of?(String)
return nil if tz_string.empty?
s = StringScanner.new(tz_string)
check_scan(s, /([^-+,\d<][^-+,\d]*) | <([^>]+)>/x)
std_abbrev = s[1] || s[2]
check_scan(s, /([-+]?\d+)(?::(\d+)(?::(\d+))?)?/)
std_offset = get_offset_from_hms(s[1], s[2], s[3])
if s.scan(/([^-+,\d<][^-+,\d]*) | <([^>]+)>/x)
dst_abbrev = s[1] || s[2]
if s.scan(/([-+]?\d+)(?::(\d+)(?::(\d+))?)?/)
dst_offset = get_offset_from_hms(s[1], s[2], s[3])
else
# POSIX is negative for ahead of UTC.
dst_offset = std_offset - 3600
end
dst_difference = std_offset - dst_offset
start_rule = parse_rule(s, 'start')
end_rule = parse_rule(s, 'end')
raise InvalidPosixTimeZone, "Expected the end of a POSIX-style time zone string but found '#{s.rest}'." if s.rest?
if start_rule.is_always_first_day_of_year? && start_rule.transition_at == 0 &&
end_rule.is_always_last_day_of_year? && end_rule.transition_at == 86400 + dst_difference
# Constant daylight savings time.
# POSIX is negative for ahead of UTC.
TimezoneOffset.new(-std_offset, dst_difference, dst_abbrev.to_sym)
else
AnnualRules.new(
TimezoneOffset.new(-std_offset, 0, std_abbrev.to_sym),
TimezoneOffset.new(-std_offset, dst_difference, dst_abbrev.to_sym),
start_rule,
end_rule)
end
elsif !s.rest?
# Constant standard time.
# POSIX is negative for ahead of UTC.
TimezoneOffset.new(-std_offset, 0, std_abbrev.to_sym)
else
raise InvalidPosixTimeZone, "Expected the end of a POSIX-style time zone string but found '#{s.rest}'."
end
end
private
# Parses the rule from the TZ string, returning a TransitionRule.
def parse_rule(s, type)
check_scan(s, /,(?: (?: J(\d+) ) | (\d+) | (?: M(\d+)\.(\d)\.(\d) ) )/x)
julian_day_of_year = s[1]
absolute_day_of_year = s[2]
month = s[3]
week = s[4]
day_of_week = s[5]
if s.scan(/\//)
check_scan(s, /([-+]?\d+)(?::(\d+)(?::(\d+))?)?/)
transition_at = get_seconds_after_midnight_from_hms(s[1], s[2], s[3])
else
transition_at = 7200
end
begin
if julian_day_of_year
JulianDayOfYearTransitionRule.new(julian_day_of_year.to_i, transition_at)
elsif absolute_day_of_year
AbsoluteDayOfYearTransitionRule.new(absolute_day_of_year.to_i, transition_at)
elsif week == '5'
LastDayOfMonthTransitionRule.new(month.to_i, day_of_week.to_i, transition_at)
else
DayOfMonthTransitionRule.new(month.to_i, week.to_i, day_of_week.to_i, transition_at)
end
rescue ArgumentError => e
raise InvalidPosixTimeZone, "Invalid #{type} rule in POSIX-style time zone string: #{e}"
end
end
# Returns an offset in seconds from hh:mm:ss values. The value can be
# negative. -02:33:12 would represent 2 hours, 33 minutes and 12 seconds
# ahead of UTC.
def get_offset_from_hms(h, m, s)
h = h.to_i
m = m.to_i
s = s.to_i
raise InvalidPosixTimeZone, "Invalid minute #{m} in offset for POSIX-style time zone string." if m > 59
raise InvalidPosixTimeZone, "Invalid second #{s} in offset for POSIX-style time zone string." if s > 59
magnitude = (h.abs * 60 + m) * 60 + s
h < 0 ? -magnitude : magnitude
end
# Returns the seconds from midnight from hh:mm:ss values. Hours can exceed
# 24 for a time on the following day. Hours can be negative to subtract
# hours from midnight on the given day. -02:33:12 represents 22:33:12 on
# the prior day.
def get_seconds_after_midnight_from_hms(h, m, s)
h = h.to_i
m = m.to_i
s = s.to_i
raise InvalidPosixTimeZone, "Invalid minute #{m} in time for POSIX-style time zone string." if m > 59
raise InvalidPosixTimeZone, "Invalid second #{s} in time for POSIX-style time zone string." if s > 59
(h * 3600) + m * 60 + s
end
# Scans for a pattern and raises an exception if the pattern does not
# match the input.
def check_scan(s, pattern)
result = s.scan(pattern)
raise InvalidPosixTimeZone, "Expected '#{s.rest}' to match #{pattern} in POSIX-style time zone string." unless result
result
end
end
end

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