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

Listing class properties #584

Closed
danthegoodman1 opened this issue Jun 25, 2024 · 5 comments
Closed

Listing class properties #584

danthegoodman1 opened this issue Jun 25, 2024 · 5 comments

Comments

@danthegoodman1
Copy link

danthegoodman1 commented Jun 25, 2024

It does not seem there is a goja equivalent of get_own_property_names in v8, which allows you to do things like inspect what methods exist on a class. The only thing I've found that can be pulled is what variables exist on an instance:

vm := goja.New()
	_, err := vm.RunString(`
		class MyClass {
			constructor() {
				this.i = 0;
			}
			DoThing() {
				this.i++;
				return this.i;
			}
		}
	`)
	if err != nil {
		panic(err)
	}

	// Retrieve the MyClass constructor from the VM
	myClassConstructor := vm.Get("MyClass").ToObject(vm)

	// Create an instance of MyClass
	myClassInstance, err := vm.New(myClassConstructor)
	if err != nil {
		panic(err)
	}

	fmt.Println(myClassInstance.Keys())                // prints "[i]"
	fmt.Println(myClassConstructor.Keys())             // prints "[]"
	fmt.Println(myClassInstance.Prototype().Keys())    // prints "[]"
	fmt.Println(myClassConstructor.Prototype().Keys()) // prints "[]"

But I am failing to find how I would, from a constructor, lis tthe DoThing method without prior knowledge of it. For reference the respective (rust) v8 looks like:

 // Get the MyClass constructor from the global object.
        let global = context.global(&mut scope);
        let key = v8::String::new(&mut scope, "MyClass").unwrap();
        let class_value = global.get(&mut scope, key.into()).unwrap();

        // Ensure it's a function (constructor).
        if !class_value.is_function() {
            panic!("MyClass is not a function");
        }

        let class_constructor = v8::Local::<v8::Function>::try_from(class_value).unwrap();

        // Get the prototype of the class
        let proto_key = v8::String::new(&mut scope, "prototype").unwrap().into();
        let prototype = class_constructor
            .get(&mut scope, proto_key)
            .unwrap();
        let prototype_object = v8::Local::<v8::Object>::try_from(prototype).unwrap();

        // Get the property names of the prototype object
        let property_names = prototype_object.get_own_property_names(&mut scope, v8::GetPropertyNamesArgs { mode: v8::KeyCollectionMode::IncludePrototypes, property_filter: PropertyFilter::ALL_PROPERTIES, index_filter: v8::IndexFilter::IncludeIndices, key_conversion: v8::KeyConversionMode::ConvertToString }).unwrap();

        println!("Instance methods:");
        for i in 0..property_names.length() {
            let key = property_names.get_index(&mut scope, i).unwrap();
            let key_str = key.to_string(&mut scope).unwrap();
            println!(" - {}", key_str.to_rust_string_lossy(&mut scope));
        }

Prints:

Instance methods:
 - constructor
 - multiply
 - testQuery
@dop251
Copy link
Owner

dop251 commented Jun 27, 2024

In general, you can just use built-in functions (Object.getOwnPropertyNames in your case), either by using the reflect magic:

	var getOwnPropertyNames func(o *Object) []string

	err := vm.ExportTo(vm.Get("Object").ToObject(vm).Get("getOwnPropertyNames"), &getOwnPropertyNames)
	if err != nil {
		t.Fatal(err)
	}
        // ...
        t.Log(getOwnPropertyNames(myClassInstance.Prototype())) // prints [constructor DoThing]

or, to make it perform better, by using raw calls:

	getOwnPropertyNames, ok := AssertFunction(vm.Get("Object").ToObject(vm).Get("getOwnPropertyNames"))
	if !ok {
		t.Fatal("Object.getOwnPropertyNames is not a function")
	}
        // ...
        array, err := getOwnPropertyNames(nil, myClassInstance.Prototype())
	if err != nil {
		t.Fatal(err)
	}

	t.Log(array.ToObject(vm).Get("1")) // prints DoThing

However, in this particular case I think it makes sense to add it, I'll do it shortly.

@danthegoodman1
Copy link
Author

Great, it would be ideal to be able to inspect class methods without having to create an instance of the object.

@dop251
Copy link
Owner

dop251 commented Jun 27, 2024

Class methods are non-enumerable properties of the class prototype, so even if you have an instance, you need to use Object.getOwnPropertyNames(instance.__proto__), otherwise it's Object.getOwnPropertyNames(Class.prototype)

@danthegoodman1
Copy link
Author

Ah ok, I had to do vm.Get("MyClass").ToObject(vm).Get("prototype"). I tried vm.Get("MyClass").ToObject(vm).Prototype() and that gave [constructor length name apply bind call toString caller arguments]

@dop251
Copy link
Owner

dop251 commented Jun 27, 2024

Yes. Class.prototype is where all the methods are defined as properties and this is what becomes instance.__proto__. Class.__proto__, on the other hand, is Function.prototype, because Class is an instance of Function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants