diff --git a/ext/js/bindgen/rb-js-abi-host.wit b/ext/js/bindgen/rb-js-abi-host.wit index 481d19e6e2..f23eb17f65 100644 --- a/ext/js/bindgen/rb-js-abi-host.wit +++ b/ext/js/bindgen/rb-js-abi-host.wit @@ -14,6 +14,8 @@ js-value-to-string: function(value: js-abi-value) -> string export-js-value-to-host: function(value: js-abi-value) import-js-value-from-host: function() -> js-abi-value +js-value-typeof: function(value: js-abi-value) -> string + reflect-apply: function(target: js-abi-value, this-argument: js-abi-value, arguments: list) -> js-abi-value reflect-construct: function(target: js-abi-value, arguments: list) -> js-abi-value reflect-delete-property: function(target: js-abi-value, property-key: string) -> bool diff --git a/ext/js/js-core.c b/ext/js/js-core.c index 040179e9dd..84d800831e 100644 --- a/ext/js/js-core.c +++ b/ext/js/js-core.c @@ -218,6 +218,25 @@ static VALUE _rb_js_obj_call(int argc, VALUE *argv, VALUE obj) { rb_js_abi_host_reflect_apply(abi_method->abi, p->abi, &abi_args)); } +/* + * call-seq: + * js_value.typeof -> String + * + * Returns the result string of JavaScript 'typeof' operator. + * See also JS.is_a? for 'instanceof' operator. + * p JS.global.typeof # => "object" + * p JS.eval("return 1").typeof # => "number" + * p JS.eval("return 'str'").typeof # => "string" + * p JS.eval("return undefined").typeof # => "undefined" + * p JS.eval("return null").typeof # => "object" + */ +static VALUE _rb_js_obj_typeof(VALUE obj) { + struct jsvalue *p = check_jsvalue(obj); + rb_js_abi_host_string_t ret0; + rb_js_abi_host_js_value_typeof(p->abi, &ret0); + return rb_str_new(ret0.ptr, ret0.len); +} + /* * call-seq: * inspect -> string @@ -319,6 +338,7 @@ void Init_js() { rb_define_method(rb_cJS_Object, "[]", _rb_js_obj_aref, 1); rb_define_method(rb_cJS_Object, "[]=", _rb_js_obj_aset, 2); rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1); + rb_define_method(rb_cJS_Object, "typeof", _rb_js_obj_typeof, 0); rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0); rb_define_singleton_method(rb_cJS_Object, "__import_from_js", _rb_js_import_from_js, 0); rb_define_method(rb_cJS_Object, "inspect", _rb_js_obj_inspect, 0); diff --git a/packages/npm-packages/ruby-wasm-wasi/src/index.ts b/packages/npm-packages/ruby-wasm-wasi/src/index.ts index 8df4bd5f87..cd31fa93b9 100644 --- a/packages/npm-packages/ruby-wasm-wasi/src/index.ts +++ b/packages/npm-packages/ruby-wasm-wasi/src/index.ts @@ -120,6 +120,9 @@ export class RubyVM { return false; } }, + jsValueTypeof(value) { + return typeof value; + }, reflectApply: function (target, thisArgument, args) { return Reflect.apply(target as any, thisArgument, args); }, diff --git a/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts b/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts index 921efc6f00..df41ed2f5b 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts @@ -24,6 +24,18 @@ describe("Manipulation of JS from Ruby", () => { expect(vm.eval(code).toString()).toBe(String(props.result)); }); + test.each([ + { object: `JS.eval('return 1')`, result: "number" }, + { object: `JS.eval('return "x"')`, result: "string" }, + { object: `JS.eval('return null')`, result: "object" }, + { object: `JS.eval('return undefined')`, result: "undefined" }, + { object: `JS.global`, result: "object" }, + ])("JS::Object#typeof (%s)", async (props) => { + const vm = await initRubyVM(); + const code = `require "js"; (${props.object}).typeof`; + expect(vm.eval(code).toString()).toBe(String(props.result)); + }); + test.each([ { expr: "JS.global[:Object]", result: Object }, { expr: "JS.global[:Object][:keys]", result: Object.keys },