Skip to content

Commit

Permalink
Improve error messages when calling JavaScript object properties in m…
Browse files Browse the repository at this point in the history
…ethod style
  • Loading branch information
ledsun committed Sep 30, 2024
1 parent a0ca3af commit ad4ba8a
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 14 deletions.
29 changes: 22 additions & 7 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 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

0 comments on commit ad4ba8a

Please sign in to comment.