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 errer mesage for property #531

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions packages/gems/js/lib/js.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,13 @@ def method_missing(sym, *args, &block)
if sym_str.end_with?("?")
# When a JS method is called with a ? suffix, it is treated as a predicate method,
# and the return value is converted to a Ruby boolean value automatically.
result = self.call(sym_str[0..-2].to_sym, *args, &block)

result = invoke_js_method(sym_str[0..-2].to_sym, *args, &block)
# Type coerce the result to boolean type
# to match the true/false determination in JavaScript's if statement.
JS.global.Boolean(result) == JS::True
elsif self[sym].typeof == "function"
self.call(sym, *args, &block)
else
super
return JS.global.Boolean(result) == JS::True
end

invoke_js_method(sym, *args, &block)
end

# Check if a JavaScript method exists
Expand All @@ -195,10 +192,10 @@ def respond_to_missing?(sym, include_private)
self[sym].typeof == "function"
end

# Call the receiver (a JavaScript function) with `undefined` as its receiver context.
# Call the receiver (a JavaScript function) with `undefined` as its receiver context.
# This method is similar to JS::Object#call, but it is used to call a function that is not
# a method of an object.
#
#
# floor = JS.global[:Math][:floor]
# floor.apply(3.14) # => 3
# JS.global[:Promise].new do |resolve, reject|
Expand Down Expand Up @@ -239,6 +236,24 @@ def await
promise = JS.global[:Promise].resolve(self)
JS.promise_scheduler.await(promise)
end

private

# Invoke a JavaScript method
# If the property of JavaScritp object does not exist, raise a `NoMethodError`.
# If the property exists but is not a function, raise a `TypeError`.
def invoke_js_method(sym, *args, &block)
return self.call(sym, *args, &block) if self[sym].typeof == "function"

# Check to see if a non-functional property exists.
if JS.global[:Reflect].call(:has, self, sym.to_s) == JS::True
raise TypeError,
"`#{sym}` is not a function. To reference a property, use `[:#{sym}]` syntax instead."
end

raise NoMethodError,
"undefined method `#{sym}' for an instance of JS::Object"
end
end

# A wrapper class for JavaScript Error to allow the Error to be thrown in Ruby.
Expand Down
34 changes: 27 additions & 7 deletions packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,6 @@ def test_method_missing_with_block
assert_true block_called
end

def test_method_missing_with_undefined_method
object = JS.eval(<<~JS)
return { foo() { return true; } };
JS
assert_raise(NoMethodError) { object.bar }
end

def test_method_missing_with_?
object = JS.eval(<<~JS)
return {
Expand Down Expand Up @@ -344,6 +337,33 @@ def test_method_missing_with_?
assert_false object.return_empty_string?
end

def test_method_missing_with_property
object = JS.eval(<<~JS)
return { property: 42 };
JS

e = assert_raise(TypeError) { object.property }
assert_equal "`property` is not a function. To reference a property, use `[:property]` syntax instead.",
e.message

e = assert_raise(TypeError) { object.property? }
assert_equal "`property` is not a function. To reference a property, use `[:property]` syntax instead.",
e.message
end

def test_method_missing_with_undefined_method
object = JS.eval(<<~JS)
return { foo() { return true; } };
JS
e = assert_raise(NoMethodError) { object.bar }
assert_equal "undefined method `bar' for an instance of JS::Object",
e.message

e = assert_raise(NoMethodError) { object.bar? }
assert_equal "undefined method `bar' for an instance of JS::Object",
e.message
end

def test_respond_to_missing?
object = JS.eval(<<~JS)
return { foo() { return true; } };
Expand Down
Loading