Why doesn't Intellisense figure out dynamically generated methods / properties? Is there a fix for that?

Hi,

I found out this problem just by looking at createJS code and trying it out myself. Here is a simple example where WebStorm intellisense doesn't work, while this works in Visual Studio. In the code below the issue is that intellisense doesn't recognize what happened at this line:

app.SoundManagerEvent = createjs.promote(SoundManagerEvent, "Event");

and as a result these two lines are reported as problematic:

function SoundManagerEvent(type, bubbles, cancelable, data) {
this.Event_constructor(type, bubbles, cancelable);

saying that SoundManagerEvent doesn't have the Event methods being implemented and that there is no such function as this.Event_constructor();

Please let me know if there is a way to fix this. I just find it hard to believe that Visual Studio understands this code and WebStorm doesn't. Here is the full code of the issue:

this.createjs = this.createjs||{};
createjs.extend = function(subclass, superclass) {
"use strict";

function o() { this.constructor = subclass; }
o.prototype = superclass.prototype;
return (subclass.prototype = new o());
};
createjs.promote = function(subclass, prefix) {
"use strict";

var subP = subclass.prototype, supP = (Object.getPrototypeOf&&Object.getPrototypeOf(subP))||subP.__proto__;
if (supP) {
subP[(prefix+="_") + "constructor"] = supP.constructor; // constructor is not always innumerable
for (var n in supP) {
if (subP.hasOwnProperty(n) && (typeof supP[n] == "function")) { subP[prefix + n] = supP[n]; }
}
}
return subclass;
};
app = app || {};

(function () {
"use strict";

// constructor:
/**
* Contains the events that the SoundManager dispatches.
* @class SoundManagerEvent
* @extends Event
* @param {String} type The event type.
* @param {Boolean} bubbles Indicates whether the event will bubble through the display list.
* @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled.
* @param {Object} data Used to store custom data.
* @see SoundManager
* @constructor
**/
function SoundManagerEvent(type, bubbles, cancelable, data) {
this.Event_constructor(type, bubbles, cancelable);

this.data = data;
}

var p = createjs.extend(SoundManagerEvent, createjs.Event);
var s = SoundManagerEvent;

s.LOADER_FILELOAD = "loaderFileload";
s.LOADER_COMPLETE = "loaderComplete";
s.LOADER_ERROR = "loaderError";

p.clone = function() {
return new SoundManagerEvent(this.type, this.bubbles, this.cancelable, this.data);
};

app.SoundManagerEvent = createjs.promote(SoundManagerEvent, "Event");
}());
8 comments
Comment actions Permalink

2 problems here:

1. 'Event' is resolved to definition from plugins/JavaScriptLanguage/lib/JavaScriptLanguage.jar!/com/intellij/lang/javascript/index/predefined/DOMEvents.js, thus the warning about interface methods not being implemented. You can change

@extends Event

 

to

@extends createjs.Event

to solve the issue

 

2. Event_constructor is not directly defined anywhere in createJS code, it's generated dynamically, thus this property can't be resolved during static analysis.

This is not an issue in VS as, AFAIK, resolving there is based on JSLS service that works by executing the JavaScript code to dynamically provide syntactically correct completions. But, as you can imagine, need to execute code dynamically in background can cause a huge memory and CPU overdraft

1
Comment actions Permalink

Thanks! :) this was very helpful! I would guess there's no easy way of connecting the dots for WebStorm. One would have to define all properties for a class that would come from it's superclass, right ? Or is there a way to make dynamic analysis in WebStorm?

Oh and just two more question please:

1) right now after this line:

app.SoundManagerEvent = createjs.promote(SoundManagerEvent, "Event");

I don't have any documentation on app.SoundManagerEvent, but if I make it:

app.SoundManagerEvent = SoundManagerEvent;

Then there is no issue. I do understand that I have jsdoc on the promote function that says @param {Function} and @return {Function}, which obviously overrides SoundManagerEvent into a plain Function, but I wanted to ask is there a way to make it inherit the type of SoundManagerEvent. I mean I am passing it as a param into the function and then returning it. If I set anything else but Function I would understand why it would override but is there an option to preserve the parameter type and at the same time keep the function global with no jsdoc specific typecasting?

2) Right after I switched @extends to {createjs.Event} this.type, this.bubbles and this.cancelable in this code turned to unresolved variables and they are only found at this.prototype.type etc.:   

p.clone = function () {
        return new DataEvent(this.type, this.bubbles, this.cancelable, this.data);
    };

If I leave @extends to {Event} they all work as this.type etc but then WebStorm reports the DataEvent not implementing a bunch of methods.

So please tell me... how do I fix this?

Here is the full code (use the code from the original post as a reference for some of the mothods used):

this.app.events = this.app.events || {};

(function () {
"use strict";

// constructor:
/**
* An Event with a data propery to use for custom data.
* @class {DataEvent}
* @extends {createjs.Event}
* @param {String} type The event type.
* @param {Boolean} bubbles Indicates whether the event will bubble through the display list.
* @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled.
* @param {Object} data Use to store custom data.
* @see createjs.Event
* @constructor
**/
function DataEvent(type, bubbles, cancelable, data) {
this.Event_constructor(type, bubbles, cancelable);

this.data = data;
}

var p = createjs.extend(DataEvent, createjs.Event);
var s = DataEvent;


p.clone = function () {
return new DataEvent(this.type, this.bubbles, this.cancelable, this.data);
};

/**
*
* @type {DataEvent}
*/
app.events.DataEvent = createjs.promote(DataEvent, "Event");
}());

 

 

0
Comment actions Permalink

Anyone? Please see my questions in the comment above.

0
Comment actions Permalink

1. you can try changing JSDoc for promote() as follows:
/** @method promote
* @template T
* @param {T} subclass The class to promote super class methods on.
* @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass.
* @return {T} Returns the subclass.
*/
createjs.promote = function(subclass, prefix) {...}

Documentation will be shown correctly then... Or, just add @type {SoundManagerEvent} annotation to app.SoundManagerEvent

1
Comment actions Permalink

2. looks weird, but adding type annotation to var p = createjs.extend(DataEvent, createjs.Event); solves the problem to me. It can be @type {Function} oir even @type {*}...

1
Comment actions Permalink

Ok so I did some more testing and here are the results:

1) First I define the base class
this.app = this.app || {};

(function () {
"use strict";

/**
* @constructor
*/
function Animal() {
/** Is this animal alive? */
this.alive = true;
}

app.Animal = Animal;
}());

2 ) Then I try to figure out the proper way to extend it with proper jsdocs. I will separate and describe each case by modifying the first case that works best:
1.1. Having "@extends Animal" without explicitly setting the namepath to "@extends app.Animal" seems to give the best result - we have no errors and when starting to type "this.al" the first thing code hinting brings up is alive (Animal), which is perfect.
(function () {
"use strict";

/**
* @constructor
* @extends Animal
*/
function Duck() {}
Duck.prototype = new app.Animal();

/** What do ducks say? */
Duck.prototype.speak = function() {
if (this.alive) {
alert('Quack!');
}
};
}());
1.2. This works absolutely the same way - and no need to document the d variable, no errors, perfect code hinting
(function () {
"use strict";

/**
* @constructor
* @extends Animal
*/
function Duck() {}
Duck.prototype = new app.Animal();

var d = Duck.prototype;

/** What do ducks say? */
d.speak = function() {
if (this.alive) {
alert('Quack!');
}
};
}());
2.1. I switch to "@extend app.Animal". The result is that we now have unresolved variable alive unless I remove the d variable and just type Duck.prototype.speak = function () { ... }
(function () {
"use strict";

/**
* @constructor
* @extends app.Animal
*/
function Duck() {}
Duck.prototype = new app.Animal();

var d = Duck.prototype;

/** What do ducks say? */
d.speak = function() {
if (this.alive) {
alert('Quack!');
}
};
}());
2.2. In all these cases the d variable was reported by the docs as Inferred type: Object|Duck without me writing any docs. So I tried adding the docs for it in order to fix the unresolved variable alive. What happened is that this fixed this issue and now alive is found but I have another issue - Property speak is not defined in type Object|Duck. The only way I could fix this issue is by switching the correct @type {Object|Duck} only to the incorrect @type {Object}. @type {Function} did not work cause it says that the object|Duck is not assignable to Function.

(function () {
"use strict";

/**
* @constructor
* @extends app.Animal
*/
function Duck() {}
Duck.prototype = new app.Animal();

/**
*
* @type {Object|Duck}
*/
var d = Duck.prototype;

/** What do ducks say? */
d.speak = function() {
if (this.alive) {
alert('Quack!');
}
};
}());

My observation is that the jsdocs are quite messy inaccurate and buggy to work with in WebStorm and I am just getting strarted. Seems to me that Visual Studio no matter it dynamically analyses the code at least doesn't give me the headache of dealing with these issues - cause right now even if I wanted to document properly my code - it doesn't seem like I actually can. I am kind of disappointed.

If you could give me any fix on the issues or advise on this I would be happy to follow it :) Or maybe there is a bug to report here ? Anyways, thank you! :)

0
Comment actions Permalink

Logged as https://youtrack.jetbrains.com/issue/WEB-21998, please vote for it to be notified on any progress

0
Comment actions Permalink

Awesome! :) Thank you very much!

0

Please sign in to leave a comment.