Get Tern type using its name - javascript

Tl;dr version:
Using TernJS for autocompletion purposes, I want to use a rule on a function, that taking a string as parameter, in order to return a type with the name same with the param given, for example:
foo("TypeA") returns +TypeA
foo("TypeB") returns +TypeB
In simple words, I am searching whether or not there is a notation in Tern that can give you a type using its name.
Long version:
Using CodeMirror in combination with TernJS set up in NodeJS, I am trying to simulate a custom factory behavior and provide similar capabilities in JavaScript in ES6:
//Module: Test.js
module.exports = (function() {
var test = {};
//....
test.classA = class ClassA {
constructor() {
//....
},
foo() {}
//....
}
test.factoryClass = class Factory {
constructor() {
//....
}
register(name, classData) {
//....
}
createInstance(name) {
//....
}
}
return test;
})();
Now, what I want to do is this:
If I have registered ClassA by doing:
var test = require("Test");
var factory = new test.factoryClass();
factory.register("ClassA", classA);
And I create a class instance of it:
var classAInst = factory.createInstance("ClassA");
I want in the editor to be able to get the methods of the ClassA instance.
classAInst.<AutoCompletion-KeyStroke> //This should give the completion list containing `foo`.
Now, I know that in order to get this, I need to have generated the TernJS Inference Engine rules in the .json definitions, so for ClassA and Factory I have something like:
{
"Test" : {
"ClassA": {
"!type": "fn()"
"prototype": {
"foo": {
"!type": "fn() -> string"
}
}
}
//....
"Factory": {
"prototype": {
"register": {
"!type": "fn(className: string, classDefinition: object)"
},
"createInstance": {
"!type": "<This is what I need to specify properly>"
}
}
}
}
}
The problem is that I figured out how to form the rules by inspecting the ecma6.json file in the TernJS repo, as I was unable to find any documentation of the inference engine.
I tried doing something like this, attempting to tell the engine to index the type in a way, but it does not work (I suppose that [] is used for array definition only):
"createInstance": {
"!type": "fn(name: string) -> Test[!0]"
}
So, any help on this would be appreciated. Any link or reference to the inference engine manual would be very helpful too, as I did not manage to find such information.

Related

Cannot call subclass function in Gnome Shell Extension ES6

I am trying to write a GNOME shell extension. I have a class which extends a GNOME shell type. Whenever I call class method on this subclass, it throws an error. If I do not extend the class, then the method is invoked without a problem.
Example:
This works
var Dummy = class Dummy {
constructor() {}
foo() {
log("foo!")
}
};
let d = new Dummy();
d.foo();
--> RESULT: log shows "foo!"
However, this does not work
const St = imports.gi.St;
var Dummy = class Dummy extends St.Bin {
constructor() {
super();
}
foo() {
log("foo!")
}
};
let d = new Dummy();
d.foo();
--> RESULT: TypeError: d.foo is not a function
I'm not very proficient in Javascript, and I'm having trouble Googling my way out of this situation.
Any help is appreciated. Thanks!
Unfortunately there is a bit of mix of Class styles, for couple reasons, but namely because GJS existed before ES6 classes. You should avoid usage of the Lang module if at all possible.
Sometimes it won't be clear if the object you're subclassing is a native JS class or a GObject. Any object coming from imports.gi (GObject Introspection) will be a GObject, while objects coming from say imports.ui.popupMenu (an import in GNOME Shell) could be either and you may have to check the source.
If you are subclassing a GObject, this is the proper way to subclass:
var Bin = GObject.registerClass({
// This is optional, but useful to avoid namespace collisions. By
// convention you prefix your GType's like StBin or GtkBin.
GTypeName: 'PohoBin',
// Custom GProperties
Properties: {
'example-prop': GObject.ParamSpec.string(
'example-prop', // property name
'ExampleProperty', // nickname
'An example read write property', // description
(GObject.ParamFlags.READWRITE | // READABLE/READWRITE/CONSTRUCT/etc
GObject.ParamFlags.CONSTRUCT),
null // implement defaults manually
)
}
}, class Bin extends St.Bin {
// Currently GObjects use `constructor()` for bootstrapping; you
// should not call this function in GObject subclasses.
//
// Instead, you should chain-up to the parent class's _init() function,
// which takes a dictionary of properties
_init(params = {}) {
// Chaining up. If you need to, you can use this opportunity to
// pull properties from the #params dictionary
super._init(params);
}
get example_prop() {
if (this._example_prop === undefined)
this._example_prop = null;
return this._example_prop;
}
set example_prop(value) {
if (this.example_prop !== value) {
this._example_prop = value;
this.notify('example-prop');
}
}
foo() {
log(this.example_prop);
}
});
let obj = new Bin({
visible: true, // Clutter.Actor property
x_align: St.Align.MIDDLE, // St.Bin property
example_prop: 'example' // custom property
});
obj.foo();
// expected output: 'example'
There is more information about mapping GObject to JavaScript here:
https://gitlab.gnome.org/GNOME/gjs/wikis/Mapping
There are also a number of more complete examples here:
https://gitlab.gnome.org/GNOME/gjs/tree/master/examples

Get method name from within a typescript method

I want to get the name of the current method from within an instance method of a class in Typescript.
(Pseudocode, doesn't work):
class Foo {
bar() {
console.log(something); //what should something be?
}
}
new Foo().bar();
I expect 'something' to return 'bar'. I realize that this can give me the class, and I could somehow get the class and its attributes from it, but I do not know how to get 'this function' (i.e, the method bar, not the class Foo).
I have seen several other questions related to finding the class name, etc. but not one that addresses getting the current method name.
Besides the arguments.callee.name there is no straightforward way of getting this.
I propose 2 other methods:
Use decorators to inject the method name:
function annotateName(target, name, desc) {
var method = desc.value;
desc.value = function () {
var prevMethod = this.currentMethod;
this.currentMethod = name;
method.apply(this, arguments);
this.currentMethod = prevMethod;
}
}
class Foo {
currentMethod: string;
#annotateName
bar() {
alert(this.currentMethod);
this.tux();
alert(this.currentMethod);
}
#annotateName
tux() {
alert(this.currentMethod);
}
}
new Foo().bar();
The downside is that you have to annotate all the functions you want to get the name from. You could instead just annotate the class and in the decorator you would iterate over all prototype functions and apply the same idea.
My second option is not standardised and need more care to get consistent results across browsers. It relies on creating an Error object and getting it's stack trace.
class Foo {
bar() {
console.log(getMethodName());
}
}
function getMethodName() {
var err = new Error();
return /at \w+\.(\w+)/.exec(err.stack.split('\n')[2])[1] // we want the 2nd method in the call stack
}
new Foo().bar();
Not sure if this would help, but:
class Foo {
bar() {
console.log(Object.getOwnPropertyNames(Foo.prototype)); // ["constructor", "bar"]
}
}
new Foo().bar();
Here is my solution to get the method name.
/**
* #description Get log invoker name
* #return {string} The invoker name
*/
private static callerName(): string {
try {
throw new Error();
} catch (e) {
try {
return e.stack.split('at ')[3].split(' ')[0];
} catch (e) {
return '';
}
}
}
I was looking for a solution as well, try this:
class Foo {
bar() {
console.log(this.bar.name); // <-- Print out the function name.
}
}
new Foo().bar();
What is nice is that you'll get an error if you change the function name, but forget to update the console statement.
Keep in mind that during compilation and minification you might lose the actual name of what you're trying to use. You might consider looking into the ts-nameof babel macro that reads the name of virtually anything during compilation and returns it's actual string representation.
for class name - Foo.name
for method name - this.bar.name
Just to answer the question with another interesting take, you could do (but shouldn't do) something like this:
class Foo {
constructor(private http: HttpClient) {
const apiUrl = 'http://myapi.com/api/';
{
const functionName = 'getBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
{
const functionName = 'postBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
{
const functionName= 'putBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
{
const functionName= 'deleteBar';
this[functionName] = function () {
return http.get(apiUrl + functionName);
}
}
}
}
It certainly is not an elegant solution, and I can't really imagine a good use case for doing something like this, as I'm pretty sure the compiler doesn't recognize new Foo(http).deleteBar(). Maybe someone can come up with an elegant solution with this idea, I'll leave that as an experiment for someone.
But with this pattern (if you employ some kind of devops scaffolding or "strong copy-paste skills") you can always access your method's name via functionName
function getFunctionName() {
return getFunctionName.caller.name
}
function foobar() {
console.log(getFunctionName())
}
foobar() // logs 'foobar' as the currently running function
You can use the .caller property:
A Function object's caller property accessor property represents the function that invoked the specified function. For strict, async function, and generator function callers, accessing the caller property throws an exception.
Although non standard, in my experience the caller property is supported everywhere I have used it (mostly node.js). Check for compatibility before using it. I have only every used it for debugging purposes. For more information, please see
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller

Accessing static methods from instance in Typescript

Why can't I do this? Is it due to technical limitations of Javascript/Typescript, or is this a design decision by the developers of Typescript? This same code would work fine in Java or C#.
class Test {
static str: string = "test";
public static getTest(): string {
return this.str;
}
}
//works as expected
console.log(Test.getTest());
//won't compile
var test: Test = new Test();
console.log(test.getTest());
but I'd still like to know why.
Less magic. Static properties exist on the Class not instances. Its clear exactly what is getting called from the code instead of magic binding to class or member function at runtime.
Is it due to technical limitations of Javascript/Typescript, or is this a design decision by the developers of Typescript
Not a technical limitation. A design decision. More to align with ES6 than anything else : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static But over there its a decision made for less magic
As #basarat says, it's just a design decision, not a technical limitation.
Actually, even though it's not possible to access test.getTest() just like you would in Java or C#, there are ways to access it:
Both Object.getPrototypeOf() (replacement to the now deprecated Object.prototype.__proto__) or Object.prototype.constructor should work:
Object.getPrototypeOf(test).constructor.getTest();
test.constructor.getTest();
Actually:
Object.getPrototypeOf(test).constructor === test.constructor; // true
Here you can see the compiled source in action:
class Test {
static getTest() {
return this.str;
}
}
Test.str = 'test';
const test = new Test();
console.log(Object.getPrototypeOf(test).constructor.getTest());
console.log(test.constructor.getTest());
console.log(Object.getPrototypeOf(test).constructor === test.constructor);
console.log(Object.getPrototypeOf(test) === Test.prototype);
Note static properties exist in classes but not in instances.
Therefore, if you want to go from test to its prototype, you should call Object.getPrototypeOf(test), not test.prototype, which is a completely different thing.
The .prototype property only exists in functions and, when instantiating a new object using new and a call to that constructor function (new Test()), will become the newly created object's prototype (the deprecated .__proto__).
In your example:
test.prototype; // undefined
Test; // class Test { static getTest() { ... } }
Test.protoype; // { constructor: class Test { ... } }
Test.protoype.constructor; // class Test { static getTest() { ... } }
Test.protoype.constructor === Test; // true
test.constructor; // class Test { static getTest() { ... } }
test.constructor === Test; // true
Object.getPrototypeOf(test) === Test.prototype; // true

Is it possible to have multiple dynamic method names in a class?

I'm reading through the ES6 class information on Babel.js's documentation and noticed that it says that objects can now have dynamic property names:
var obj = {
...
// Computed (dynamic) property names
[ "prop_" + (() => 42)() ]: 42
};
This seems like it would be useful in classes as well. Is it possible to do something similar in an ES6 class without doing it in a constructor, i.e.:
class Foo {
[ "read" + (...)(['format1', 'format2']) ] {
// my format reading function
}
}
rather than doing something like this in the constructor:
class Foo {
constructor(opts) {
let formats = ['format1', 'format2'];
let self = this;
formats.forEach(function(format) {
self["read" + format] = function() {
// my format reading function
}
})
}
}
In other words, I want to be able to take some array, such as ['format1', 'format2'] and create two methods, readformat1 and readformat2, in the class dynamically, without using the constructor. Is this possible?
Yes, it's possible, you only missed the required () for the method signature:
class Foo {
[ "read" + ((format) => format)(myFormat) ]() {
// my format reading function // ^--- this what missed
}
}
Babel repl: long and ugly url here
As of your updated question: it's not possible (at least I'm not aware of it). So you can create methods with names resolved in runtime, but you cannot create N methods from the array using that syntax.
I found this question in a first Google link, so should give another helpful answer :)
ES6 classes is mostly just a syntactic sugar, so you still can use prototypes and do something like
class Foo { ... }
let formats = [ 'format1', 'format2' ];
formats.forEach(function(format) {
Foo.prototype['read' + format] = function () { ... }
});
I've not found a better way.

JavaScript : Create new object of a custom class based on an existing one (adding methods to it)

I use the iOS UI Automation framework to make sure my iPhone app rocks.
Everybody who uses this framework would tell you that it's great, but that it's lacking a lot of structure.
So I have to deal with instances of UIAWindow, which represent different screens of my app. To be more object-oriented, I'd like to have a specific class for each screen, so I could add specific methods, like
myScreen1.tapDoneButton();
var total = myScreen2.getNumberOfElements();
For the moment, I'm able to achieve this by passing the instances of UIAWindow to functions that will add the appropriate methods, like this :
function makeMainScreen(actualScreen)
{
actualScreen.constructor.prototype.getAddButton = function() {
return this.buttons()["add button"];
};
actualScreen.constructor.prototype.tapAddButton = function() {
this.getAddButton().tap();
};
// Add any desired method...
return actualScreen;
}
It works fine, I use it like this :
var mainScreen = makeMainScreen(app.mainWindow());
mainScreen.tapAddButton();
But that doesn't seem object-oriented enough, I would like to create real objects, using the new and this keywords, so I'd have a declaration like this :
function MainScreen(actualScreen){
// This line doesn't work : because 'this' is immutable
this = actualScreen;
this.tapAddButton = function(){
this.getAddButton().tap();
}
//...
}
And I'd use it like this :
var mainScreen = new MainScreen(app.mainWindow());
mainScreen.tapAddButton();
I thought I could save the actualScreen as a property of the object (Like in Grace Shao's answer below), and call all the methods on it, but I'd like keep the original UIAWindow methods.
Does anybody know how to do this?
Or perhaps what I'm trying to achieve doesn't make sense, in which case I'd be happy to know.
If I understand correctly, you could try the following:
function MainScreen(actualScreen){
this.screen = actualScreen;
}
MainScreen.prototype.tapAddButton = function () {
this.screen.getAddButton().tap();
};
MainScreen.prototype.getScreen = function () {
return this.screen;
};
//...
var mainScreen = new MainScreen(app.mainWindow());
mainScreen.tapAddButton();
You are correct that you cannot assign anything to this. You could also define the methods inside the constructor MainScreen, but they would be considered privileged members.
function MainScreen(actualScreen){
this.screen = actualScreen;
this.tapAddButton = function () {
this.screen.getAddButton().tap();
};
}
If you dont want them to be privileged members, it is better to define them outside the constructor. Otherwise, the members will be initialized over and over again everytime when you instantiate a new object.
Updated:
You could also wrappers for the methods of screen inside the constructor as below.
var prop;
for (prop in actualScreen) {
if (typeof actualScreen[prop] !== 'Function') {
continue;
}
this[prop] = function () {
return actualScreen[prop].apply(actualScreen, arguments);
};
}

Categories

Resources