Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get name of class method in TypeScript

For anyone viewing this, this question is similar to the following:

How do I get the name of an object's type in JavaScript?
Get an object's class name at runtime in TypeScript

However it is different in a few regards.

I'm looking to get the name of method that belongs to a class and store it in a variable in TypeScript / JavaScript.
Take a look at the following setup:

class Foo {

    bar(){
        // logic
    }

}

The above is valid TypeScript and I would like to create a method in a different class that will return me the name of the bar() method, i.e "bar"
eg:

class ClassHelper {

    getMethodName(method: any){
        return method.name; // per say
    }

}

I would then like to be able to use the ClassHelper in the following way:

var foo = new Foo();
var barName = ClassHelper.getMethodName(foo.bar); // "bar"

I've looked at a lot of posts, some suggest using the following:

var funcNameRegex = /function (.{1,})\(/;   
var results = (funcNameRegex).exec(obj.toString());
var result = results && results.length > 1 && results[1];

but this fails as my methods do not begin with function
another suggestion was:

public getClassName() {
    var funcNameRegex = /function (.{1,})\(/;
    var results  = (funcNameRegex).exec(this["constructor"].toString());
    return (results && results.length > 1) ? results[1] : "";
}

This only returns the class name however and from reading posts, it seems using constructor can be unreliable.

Also, when I've debugged the code using some of these methods, passing in the method like so: ClassHelper.getMethodName(foo.bar); will result in the parameter being passed if the method takes one, eg:

class Foo {

    bar(param: any){
        // logic
    }

}

var foo = new Foo();
var barName = ClassHelper.getMethodName(foo.bar); // results in param getting passed through

I've been struggling with this for a while, if anyone has any information on how I can solve this it would be greatly appreciated.

My .toString() on the method passed in returns this:

.toString() = "function (param) { // code }"

rather than:

.toString() = "function bar(param) { // code }"

and according to MDN it isn't supposed to either:

That is, toString decompiles the function, and the string returned includes the function keyword, the argument list, curly braces, and the source of the function body.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString#Description

like image 708
Colum Avatar asked Jun 30 '16 08:06

Colum


Video Answer


2 Answers

I found a solution. I'm not sure how efficient and reusable it is, but it worked in multiple test cases, included nested methods, eg Class -> Class -> Method

My solution:

class ClassHelpers {
    getName(obj: any): string {    
        if (obj.name) {
            return obj.name;
        }

        var funcNameRegex = /function (.{1,})\(/;   
        var results = (funcNameRegex).exec(obj.toString());
        var result = results && results.length > 1 && results[1];

        if(!result){
            funcNameRegex = /return .([^;]+)/;
            results = (funcNameRegex).exec(obj.toString());
            result = results && results.length > 1 && results[1].split(".").pop();
        }

        return result || "";
    }
}


class Foo {

    bar(param: any){
        // logic
    }

}

var foo = new Foo();
var barName = ClassHelper.getMethodName(() => foo.bar);

The lambda notation ClassHelper.getMethodName(() => foo.bar); was key to getting this to work as it allowed the .toString() to contain return foo.bar;

The next thing I had to do was to extract the method call from the .toString() then I used array and string functions to return the last substring which inevitably is the method name.

Like I said, it's probably not the most elegant solution but it has worked and even worked for nested methods

NOTE: You can replace the lambda function with a regular anonymous function

var foo = new Foo();
var barName = ClassHelper.getMethodName(function() { return foo.bar; });
like image 154
Colum Avatar answered Oct 12 '22 23:10

Colum


I have taken John White's idea and improved it so it works for every case I could think of. This method has the advantage of not needing to parse js code at runtime. There is an edge case though, where it simply can't deduce the right property name because there are multiple right property names.

class Foo {
  bar() {}
  foo() {}
}

class ClassHelper {
  static getMethodName(obj, method) {
    var methodName = null;
    Object.getOwnPropertyNames(obj).forEach(prop => {
      if (obj[prop] === method) {
        methodName = prop;
      }
    });

    if (methodName !== null) {
      return methodName;
    }
    
    var proto = Object.getPrototypeOf(obj);
    if (proto) {
      return ClassHelper.getMethodName(proto, method);
    }
    return null;
  }
}

var foo = new Foo();
console.log(ClassHelper.getMethodName(foo, foo.bar));
console.log(ClassHelper.getMethodName(Foo.prototype, foo.bar));
console.log(ClassHelper.getMethodName(Foo.prototype, Foo.prototype.bar));

var edgeCase = { bar(){}, foo(){} };
edgeCase.foo = edgeCase.bar;
console.log(ClassHelper.getMethodName(edgeCase, edgeCase.bar));
like image 23
Tamas Hegedus Avatar answered Oct 12 '22 22:10

Tamas Hegedus