Skip to content

Commit 84561c3

Browse files
committed
Improve error messages when calling JavaScript object properties in method style
1 parent 1d77b26 commit 84561c3

File tree

2 files changed

+38
-16
lines changed

2 files changed

+38
-16
lines changed

packages/gems/js/lib/js.rb

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,13 @@ def method_missing(sym, *args, &block)
173173
if sym_str.end_with?("?")
174174
# When a JS method is called with a ? suffix, it is treated as a predicate method,
175175
# and the return value is converted to a Ruby boolean value automatically.
176-
sym = sym_str[0..-2].to_sym
177-
if self[sym].typeof == "function"
178-
result = self.call(sym, *args, &block)
179-
# Type coerce the result to boolean type
180-
# to match the true/false determination in JavaScript's if statement.
181-
return JS.global.Boolean(result) == JS::True
182-
end
183-
end
184-
185-
if self[sym].typeof == "function"
186-
return self.call(sym, *args, &block)
176+
result = invoke_js_method(sym_str[0..-2].to_sym, *args, &block)
177+
# Type coerce the result to boolean type
178+
# to match the true/false determination in JavaScript's if statement.
179+
return JS.global.Boolean(result) == JS::True
187180
end
188181

189-
super
182+
invoke_js_method(sym, *args, &block)
190183
end
191184

192185
# Check if a JavaScript method exists
@@ -243,6 +236,24 @@ def await
243236
promise = JS.global[:Promise].resolve(self)
244237
JS.promise_scheduler.await(promise)
245238
end
239+
240+
private
241+
242+
# Invoke a JavaScript method
243+
# If the property of JavaScritp object does not exist, raise a `NoMethodError`.
244+
# If the property exists but is not a function, raise a `TypeError`.
245+
def invoke_js_method(sym, *args, &block)
246+
return self.call(sym, *args, &block) if self[sym].typeof == "function"
247+
248+
# Check to see if a non-functional property exists.
249+
if JS.global[:Reflect].call(:has, self, sym.to_s) == JS::True
250+
raise TypeError,
251+
"`#{sym}` is not a function. To reference a property, use `[:#{sym}]` syntax instead."
252+
end
253+
254+
raise NoMethodError,
255+
"undefined method `#{sym}' for an instance of JS::Object"
256+
end
246257
end
247258

248259
# A wrapper class for JavaScript Error to allow the Error to be thrown in Ruby.

packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,16 +341,27 @@ def test_method_missing_with_property
341341
object = JS.eval(<<~JS)
342342
return { property: 42 };
343343
JS
344-
assert_raise(NoMethodError) { object.property }
345-
assert_raise(NoMethodError) { object.property? }
344+
345+
e = assert_raise(TypeError) { object.property }
346+
assert_equal "`property` is not a function. To reference a property, use `[:property]` syntax instead.",
347+
e.message
348+
349+
e = assert_raise(TypeError) { object.property? }
350+
assert_equal "`property` is not a function. To reference a property, use `[:property]` syntax instead.",
351+
e.message
346352
end
347353

348354
def test_method_missing_with_undefined_method
349355
object = JS.eval(<<~JS)
350356
return { foo() { return true; } };
351357
JS
352-
assert_raise(NoMethodError) { object.bar }
353-
assert_raise(NoMethodError) { object.bar? }
358+
e = assert_raise(NoMethodError) { object.bar }
359+
assert_equal "undefined method `bar' for an instance of JS::Object",
360+
e.message
361+
362+
e = assert_raise(NoMethodError) { object.bar? }
363+
assert_equal "undefined method `bar' for an instance of JS::Object",
364+
e.message
354365
end
355366

356367
def test_respond_to_missing?

0 commit comments

Comments
 (0)