Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance in Ruby 2.4 and later #2

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
5 changes: 1 addition & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
language: ruby
rvm:
- "2.0.0"
- "2.1"
- "2.2"
- "2.3"
- "2.4"
- "2.5"
- "2.6"
- ruby-head
matrix:
allow_failures:
Expand Down
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ With the help of this gem you can filter out all those useless messages and only
Add to your Gemfile:

```ruby
gem "ruby_warning_filter", "~> 1.0.0"
gem "ruby_warning_filter", "~> 1.1.0"
```

Put the following code somewhere before your project is loaded. In a Rails application, a good place would be at the end of "config/boot.rb".
Expand All @@ -28,4 +28,4 @@ The filter works by proxying all writes to stderr. It has been running for a whi

[![Build Status](https://travis-ci.org/semaperepelitsa/ruby_warning_filter.svg?branch=master)](https://travis-ci.org/semaperepelitsa/ruby_warning_filter)

The gem is tested against Ruby versions 2.5 down to 2.0.
The gem is tested against Ruby 2.6, 2.5, 2.4.
57 changes: 21 additions & 36 deletions lib/ruby_warning_filter.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
require "delegate"
require "set"

Expand All @@ -20,67 +21,51 @@
#
class RubyWarningFilter < DelegateClass(IO)
attr_reader :ruby_warnings
BACKTRACE = "\tfrom"
RUBY_WARNING = %r{(^<main>|\.bundle|:\d+|:in `\S+'): warning:}
EVAL_REDEFINED = /\(eval\):\d+: warning: previous definition of .+ was here/

# Variables used in tag attributes (Slim) always cause a warning.
# TODO: Report this.
IGNORED_TEMPLATE_WARNING = %r{\.slim:\d+: warning: possibly useless use of a variable in void context$}

# The source of this warning cannot be located so we have to ignore it completely
# See example: https://github.com/deivid-rodriguez/pry-byebug/issues/221
IGNORED_EVAL_WARNING = %r{^<main>(:\d+)?: warning: .+ in eval may not return location in binding; use Binding#source_location instead$}

def initialize(io, ignore_path: Gem.path)
super(io)

@ruby_warnings = 0
@ignored = false
@ignore_path = ignore_path.to_set

# Gem path can contain symlinks.
# Some warnings use real path instead of symlinks so we need to ignore both.
ignore_path.each do |a|
@ignore_path << File.realpath(a) if File.exist?(a)
end
ignore_full_path = ignore_path + ignore_path.select{ |a| File.exist?(a) }.map{ |a| File.realpath(a) }
@ignore_regexp = Regexp.new("^(#{Regexp.union(ignore_full_path).source})")
end

def write(line)
if @ignored && (backtrace?(line) || line == "\n")
if @ignored && (line == "\n" || line.start_with?(BACKTRACE))
# Ignore the whole backtrace after ignored warning.
# Some warnings write newline separately for some reason.
@ignored = true
nil
elsif @ignored && eval_redefined?(line)
elsif @ignored && EVAL_REDEFINED.match?(line)
# Some gems use eval to redefine methods and the second warning with the source does not have file path, so we need to ignore that explicitly.
@ignored = false
nil
elsif ruby_warning?(line)
@ignored = ignored_warning?(line)
elsif RUBY_WARNING.match?(line)
@ignored = IGNORED_TEMPLATE_WARNING.match?(line) ||
IGNORED_EVAL_WARNING.match?(line) ||
@ignore_regexp.match?(line)
unless @ignored
@ruby_warnings += 1
super
end
else
@ignored = false
super
end
end

private

def ruby_warning?(line)
line =~ %r{:(\d+|in `\S+'): warning:}
end

def ignored_warning?(line)
external_warning?(line) || ignored_template_warning?(line)
end

def external_warning?(line)
@ignore_path.any?{ |path| line.start_with?(path) }
end

# Variables used in tag attributes (Slim) always cause a warning.
# TODO: Report this.
def ignored_template_warning?(line)
line =~ %r{\.slim:\d+: warning: possibly useless use of a variable in void context$}
end

def backtrace?(line)
line.start_with?("\tfrom")
end

def eval_redefined?(line)
line =~ /\(eval\):\d+: warning: previous definition of .+ was here/
end
end
5 changes: 3 additions & 2 deletions ruby_warning_filter.gemspec
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Gem::Specification.new do |gem|
gem.name = "ruby_warning_filter"
gem.version = "1.0.1"
gem.version = "1.1.0"
gem.summary = "Hassle-free Ruby warnings"
gem.license = "MIT"
gem.author = "Semyon Perepelitsa"
gem.author = "Simon Perepelitsa"
gem.email = "sema@sema.in"
gem.required_ruby_version = '>= 2.4.0'

gem.homepage = "https://github.com/semaperepelitsa/ruby_warning_filter"

Expand Down
12 changes: 6 additions & 6 deletions test/bench.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ def write_errors(io)
io.write("/path/to/gems/file.rb:297: warning: instance variable @object not initialized\n")
end

10.times do |i|
7.times do |i|
io.write("\tfrom /something/foo/bar:#{i}:in `<main>'")
io.write("\n")
end

10.times do
7.times do
io.write("(eval):1: warning: previous definition of foo was here")
io.write("\n")
end
end


# Sample results in Ruby 2.3.1:
#
# plain File 80.673k (± 2.5%) i/s - 410.454k in 5.091134s
# RubyWarningFilter 11.934k4.7%) i/s - 59.721k in 5.017126s
# plain File 81.435k (± 2.5%) i/s - 411.632k in 5.057905s
# RubyWarningFilter 41.323k2.5%) i/s - 210.236k in 5.090913s
Benchmark.ips do |x|
x.report "plain File" do
write_errors(file)
Expand All @@ -33,5 +35,3 @@ def write_errors(io)
write_errors(filter)
end
end


17 changes: 15 additions & 2 deletions test/ruby_warnings_filter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def setup
def test_no_effect
assert_equal 0, @err.ruby_warnings
@err.puts "hello"
assert_equal "hello\n", @err.string
@err.print "world", "\n"
assert_equal "hello\nworld\n", @err.string
assert_equal 0, @err.ruby_warnings
end

Expand All @@ -27,12 +28,17 @@ def test_ruby_warning

@err.write "#{@gems_dir}/unicode_utils-1.4.0/lib/unicode_utils/sid.rb:11: warning: shadowing outer local variable - al\n"
@err.write "/path/to/ruby/2.2.0/gems/unicode_utils-1.4.0/lib/unicode_utils/sid.rb:11: warning: shadowing outer local variable - al\n"
@err.write "/path/to/ruby/2.2.0/gems/fast_blank-1.0.0/lib/fast_blank.bundle: warning: method redefined; discarding old blank?"

# This warning actually writes newline separately.
@err.write "/path/to/ruby/2.2.0/gems/dragonfly-1.0.6/lib/dragonfly/utils.rb:41:in `uri_unescape': warning: URI.unescape is obsolete"
@err.write "\n"

assert_equal "/path/to/script/middleware_test.rb:58: warning: assigned but unused variable - status\n",
# warn "custom warning"
@err.write "custom warning"
@err.write "\n"

assert_equal "/path/to/script/middleware_test.rb:58: warning: assigned but unused variable - status\ncustom warning\n",
@err.string
assert_equal 1, @err.ruby_warnings
end
Expand Down Expand Up @@ -87,4 +93,11 @@ def test_template_warning
@err.string
assert_equal 1, @err.ruby_warnings
end

def test_eval_warning
@err.write "<main>:1: warning: __FILE__ in eval may not return location in binding; use Binding#source_location instead\n"
@err.write "<main>: warning: __LINE__ in eval may not return location in binding; use Binding#source_location instead\n"
assert_equal "", @err.string
assert_equal 0, @err.ruby_warnings
end
end