From a0ca3af3c86f58b5b024d5c1692b1503fd69b26e Mon Sep 17 00:00:00 2001 From: ledsun Date: Sat, 7 Sep 2024 20:22:57 +0900 Subject: [PATCH 1/2] Remove unwanted whitespace --- packages/gems/js/lib/js.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gems/js/lib/js.rb b/packages/gems/js/lib/js.rb index 04ef16761e..494e827441 100644 --- a/packages/gems/js/lib/js.rb +++ b/packages/gems/js/lib/js.rb @@ -195,10 +195,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| From ad4ba8ac05db44a0ae97e91bfb8782e22d89249a Mon Sep 17 00:00:00 2001 From: ledsun Date: Sat, 7 Sep 2024 20:25:46 +0900 Subject: [PATCH 2/2] Improve error messages when calling JavaScript object properties in method style --- packages/gems/js/lib/js.rb | 29 ++++++++++++---- .../ruby-wasm-wasi/test/unit/test_object.rb | 34 +++++++++++++++---- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/packages/gems/js/lib/js.rb b/packages/gems/js/lib/js.rb index 494e827441..cd73649bb4 100644 --- a/packages/gems/js/lib/js.rb +++ b/packages/gems/js/lib/js.rb @@ -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 @@ -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. diff --git a/packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb b/packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb index 84b2885552..a0af8241d7 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb +++ b/packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb @@ -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 { @@ -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; } };