Get name of class method in TypeScript
Unfortunately, the name of Typescript class methods is lost when compiling to JS (as you correctly inferred). Typescript methods are compiled to Javascript by adding the method to the prototype of a Javascript class. (Check the compiled Javascript for more info).
In Javascript, this works:
Foo.prototype["bar"] // returns foo.bar <<the function>>
So, the thing you could think of is reversing the prototype of the class, so the class itself becomes the key of the object:
Foo.prototype[foo.bar] // return "bar"
Of course, this is a very hacky solution, since
- Complexity is
O(N)
(loop through array) - I'm not sure this works in every case.
(Working) Example for your problem:
class Foo{
bar(){}
}
class ClassHelper{
static reversePrototype(cls:any){
let r = {};
for (var key in cls.prototype){
r[cls.prototype[key]] = key;
}
return r;
}
static getMethodNameOf(cls: any, method:any):string{
let reverseObject = ClassHelper.reversePrototype(cls);
return reverseObject[method];
}
}
var foo = new Foo();
console.log(ClassHelper.getMethodNameOf(Foo, foo.bar)) // "bar"
The better solution would be a typescript compiler option that changes the way typescript transpiles classes to javascript. However, I'm currently not aware of any option that does this sort of thing.
You could pass in both the object and the method, get the array of property keys on the object, then using property keys see if each given property is the same object reference as the supplied method -- and if so, there is a match.
class ClassHelper {
getMethodName(obj: any, method: any) {
var methodName: string;
if (method) {
Object.keys(obj).forEach(key => {
if (obj[key] === method) {
methodName = key;
}
});
}
return methodName;
}
}
This has the downside that the host object must also be known. If the method cannot be found, undefined
will be returned.
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));
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; });