1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #39140 from Shopify/missing-name-corner-case

Make NameError#missing_name work even for real Ruby NameError
This commit is contained in:
Xavier Noria 2020-05-08 18:26:26 +02:00 committed by GitHub
commit 5ffccfad8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 5 deletions

View file

@ -88,7 +88,11 @@ class HelpersTypoControllerTest < ActiveSupport::TestCase
def test_helper_typo_error_message
e = assert_raise(NameError) { HelpersTypoController.helper "admin/users" }
# This message is better if autoloading.
assert_equal "uninitialized constant Admin::UsersHelper", e.message
if RUBY_VERSION >= "2.6"
assert_equal "uninitialized constant Admin::UsersHelper\nDid you mean? Admin::UsersHelpeR", e.message
else
assert_equal "uninitialized constant Admin::UsersHelper", e.message
end
end
end

View file

@ -14,9 +14,22 @@ class NameError
# It extends NameError#message with spell corrections which are SLOW.
# We should use original_message message instead.
message = respond_to?(:original_message) ? original_message : self.message
return unless message.start_with?("uninitialized constant ")
unless /undefined local variable or method/.match?(message)
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
receiver = begin
self.receiver
rescue ArgumentError
nil
end
if receiver == Object
name.to_s
elsif receiver
"#{real_mod_name(receiver)}::#{self.name}"
else
if match = message.match(/((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/)
match[1]
end
end
end
@ -35,4 +48,18 @@ class NameError
missing_name == name.to_s
end
end
private
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
private_constant :UNBOUND_METHOD_MODULE_NAME
if UnboundMethod.method_defined?(:bind_call)
def real_mod_name(mod)
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
end
else
def real_mod_name(mod)
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
end
end
end

View file

@ -592,8 +592,8 @@ module ActiveSupport #:nodoc:
end
end
name_error = NameError.new("uninitialized constant #{qualified_name}", const_name)
name_error.set_backtrace(caller.reject { |l| l.start_with?(__FILE__) })
name_error = uninitialized_constant(qualified_name, const_name, receiver: from_mod)
name_error.set_backtrace(caller.reject { |l| l.start_with? __FILE__ })
raise name_error
end
@ -801,6 +801,16 @@ module ActiveSupport #:nodoc:
end
private
if RUBY_VERSION < "2.6"
def uninitialized_constant(qualified_name, const_name, receiver:)
NameError.new("uninitialized constant #{qualified_name}", const_name)
end
else
def uninitialized_constant(qualified_name, const_name, receiver:)
NameError.new("uninitialized constant #{qualified_name}", const_name, receiver: receiver)
end
end
# Returns the original name of a class or module even if `name` has been
# overridden.
def real_mod_name(mod)

View file

@ -11,6 +11,9 @@ class NameErrorTest < ActiveSupport::TestCase
assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name
assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really)
assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really")
if RUBY_VERSION >= "2.6"
assert_equal NameErrorTest, exc.receiver
end
end
def test_missing_method_should_ignore_missing_name
@ -19,5 +22,6 @@ class NameErrorTest < ActiveSupport::TestCase
end
assert_not exc.missing_name?(:Foo)
assert_nil exc.missing_name
assert_equal self, exc.receiver
end
end