twobob / jsdoc-toolkit

Automatically exported from code.google.com/p/jsdoc-toolkit
0 stars 0 forks source link

"this" not recognized within classes in an anonymous function #252

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
I'm trying to document code that's wrapped in an anonymous function. This 
seems to be increasingly popular for Javascript libraries, as it allows 
internal names to be compressed (and then assigned to the window object to 
give them global scope). My basic code structure looks like this (sorry for 
the lengthy example):

// wrap in anonymous function
(function() {

/**
 * @name MyClass
 * @class
 * @param {Object} options  Some options.
 */
function MyClass(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
}

/**
 * Set a property.
 * @param {String} val  Value for the property.
 */
MyClass.prototype.setProp = function(val) {
    /** 
     * Some property
     * @type String
     */
    this.prop = val;
};

/**
 * @name MyOtherClass
 * @class
 * @param {Object} options  Some options.
 */
function MyOtherClass(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
}

// add classes to global scope
window.MyClass = MyClass;
window.MyOtherClass = MyOtherClass;

})();

Two issues here, one small, one big:

1. The small issue is that I have to manually set the @name tag for my 
classes, or they won't get recognized at all, and I will get a warning 
message for the method, like this:

>> WARNING: Trying to document setProp as a member of undocumented symbol 
MyClass.

2. The bigger issue is that "this" won't be recognized in either 
constructor (though it will be recognized in the method). The above code 
will give the warnings:

>> WARNING: The symbol 'this.options' is documented more than once.
>> WARNING: Overwriting symbol documentation for: this.options.
>> WARNING: Trying to document options as a member of undocumented symbol 
this.

It's not recognizing the scope of "this", and isn't differentiating between 
"this" in different constructors. The workaround here is to document each 
property with tags like "@name MyClass#options". But this gets lengthy and 
tedious, and worse, carries an ongoing requirement to keep the @name up to 
date with the variable name, increasing the chance that documentation will 
get out of sync with the code :(.

Version: 2.3.0

Original issue reported on code.google.com by nick.rab...@gmail.com on 10 Sep 2009 at 11:52

GoogleCodeExporter commented 8 years ago
The behavior you are describing is "as expected." When you are using the @name 
tag the doc comment is no longer 
related to the code that surrounds it. If you later document something called 
"this.options" the "this" bit is no longer 
related to the class you documented previously. To solve this you would have to 
provide a @name for the members as 
well, for example @name MyClass#options. The reason prop, on the other hand, is 
correctly associated with the 
method setProp is because you didn't use @name to document setProp. This is 
explained somewhat more here:

    http://code.google.com/p/jsdoc-toolkit/wiki/TagName

Of course this solution of using all @name tags everywhere is only taking you 
further from where you really want to 
be, which is where you don't have to use any @name tags anywhere, so read on...

Firstly, I want to address your statement that using @name tags is problematic. 
I understand but let me tell you that I 
have personal experience with A Very Large JavaScript Library (AVLJSL) and it 
is 100% documented with @name tags. 
This has not proven to be the problem you suggest, in fact we find it gives us 
very welcome control over exactly what 
will appear in our documentation.

But OK, you wish to take advantage of JsDoc's imperfect code parser, forcing it 
to try to guess the names of your 
symbols for you. That's allowed, but if you want to make JsDoc Toolkit do the 
work, you must write your code in a 
way that it understands. It's not that it's stupid, it just doesn't get the 
advantage of being able to run the code you're 
documenting so it must try to guess what WOULD be defined IF your code WERE 
run, in other words "static analysis," 
which is a much harder job. But that's OK too.

Here's how you could rewrite that same code so that JsDoc Toolkit can see more 
clearly what the names are:

{{{

(function() {

/**
 * @constructor
 * @param {Object} options  Some options.
 */
MyClass = function(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
}

/**
 * Set a property.
 * @param {String} val  Value for the property.
 */
MyClass.prototype.setProp = function(val) {
    /** 
     * Some property
     * @type String
     */
    this.prop = val;
};

/**
 * @constructor
 * @param {Object} options  Some options.
 */
MyOtherClass = function(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
}

})();

}}}

So unfortunately it boils down to either you provide the @name or you make it 
obvious to JsDoc Toolkit what that 
name will be. Hope that helps, I can't fix it any better than that I'm afraid.

Original comment by micmath on 12 Sep 2009 at 5:34

GoogleCodeExporter commented 8 years ago
Thanks so much for your clear explanation (and your great library). You make 
very 
good points about the @name tag. I think the main issue is that I have 
constitutional 
difficulties doing anything manually that I think a computer should do for me, 
even 
when the latter takes me more time :).

I wanted to follow up, just to make sure that there isn't a hidden bug here 
that you 
would want to address. The reason I was using the @name tag to begin with is 
that the 
way I was initially setting up the code did not allow jsdoc-toolkit to 
recognize the 
symbols at all, as shown in the following example:

(function() {

// this class is identified properly.
/**
 * @constructor
 * @param {Object} options  Some options.
 */
MyClass1 = function(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
};

// this class is ignored.
/**
 * @constructor
 * @param {Object} options  Some options.
 */
function MyClass2(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
};

// this class is ignored.
/**
 * @constructor
 * @param {Object} options  Some options.
 */
var MyClass3 = function(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
};

})();

While I understand that due to the variety of ways prototypes can be defined in 
Javascript, jsdoc-toolkit can't be expected to recognize all of these symbols, 
I was 
surprised that MyClass2 and MyClass3 were totally ignored inside an anonymous 
function, though they work perfectly well outside, even though I explicitly 
used the 
@constructor tag.

If this is also expected behavior, please ignore. Thanks for your help with 
this - 
the syntax you suggested works great.

Original comment by nick.rab...@gmail.com on 12 Sep 2009 at 8:29

GoogleCodeExporter commented 8 years ago
I don't mind discussing this topic, it is one I'm fairly interested in after 
all, but bear with me as it's slightly hard to 
explain. I'll *try* to be brief...

Firstly, keep in mind that writing this:

    function MyClass2(options) {/* ... */}

is, for the purposes of this discussion, practically equivalent to writing this:

    var MyClass2 = function(options) {/* ... */}

and if you write it that way it should be clear what the difference between the 
following two lines of code is:

        MyClass1 = function(options) {/* ... */}
    var MyClass2 = function(options) {/* ... */}

The answer of course is that while MyClass1 is visible globally, MyClass2 is 
only visible in it's current scope (that's 
what the var keyword does after all). As you know, in JavaScript symbol 
visibility is limited by the enclosing function, 
so if you declare a symbol with the var keyword inside an enclosing function, 
that name visibility is limited to that 
function, which is effectively what you've done with MyClass2 and MyClass3. 
Once the Big Anonymous Wrapper 
Function (BAWF) ends, so too do those names, which is why JsDoc Toolkit doesn't 
automatically include those symbols 
in your documentation: as near as it can tell those symbols have no name that 
can be accessed from outside the BAWF 
(technically they are inner functions of the BAWF, so their names are like 
"anonymous"-MyClass2, if anonymous had a 
name, which of course it doesn't). You can help explain things to JsDoc Toolkit 
by providing an explicit global @name 
for those symbols, but that gets us back to where we started again, doesn't it?

I understand the source of the confusion is that in your real code, somewhere 
later on, you give those inner functions 
more outerish names like so:

    /*global name*/ window.MyClass2 = /*inner name*/ MyClass2;

and that makes it all work for you when you run the code, but poor JsDoc 
Toolkit doesn't (can't) know what that line 
will accomplish because it never tries to run your code and simply from looking 
it can't tell if that line will ever be 
reached, or if it is reached if the value of MyClass2 has changed recently or 
not... etc, etc. In contrast, JsDoc Toolkit 
can safely tell that this will result in a global MyClass symbol:

    MyClass1 = function(options) {/* ... */}

And that is why it can and does document the name of MyClass1, while MyClass2 
and MyClass3 appear to be missed. 
Actually it isn't missing them at all, it just doesn't know what to call them.

BTW, I perfectly understand your wish to let JsDoc Toolkit find the names for 
you. I'd estimate that probably 70% of the 
code in the JsDoc Toolkit application is devoted to that one single task, so I 
hope you can get it to work for you.

Original comment by micmath on 13 Sep 2009 at 7:45

GoogleCodeExporter commented 8 years ago
Thanks again, this is a great explanation. I withdraw all bug reports, and 
depart a 
more informed programmer (and documenter). I should note, though I consider 
this a 
good thing, that including the var statement earlier seems to get my desired 
effect 
(local symbol that can later be made global) and my desired documentation 
(symbol 
recognized as global) - so my current solution, which seems to be working 
great, is:

(function() {
// declare local vars
var MyClass1;

// recognized by jsdoc-toolkit as desired
/**
 * @constructor
 * @param {Object} options  Some options.
 */
MyClass1 = function(options) {
    /** 
     * This instance's options
     * @type Object
     */
    this.options = options;
};
// push local var global
window.MyClass1 = MyClass1;

})();

I can't quite decide whether the little dance required to get the right 
documentation 
improves my code, by pushing me to explicitly define my scopes, or results in 
the 
tail wagging the dog a little, in which case I should rely more on @name tags - 
for 
example, the above code would be more compact as 

var MyClass1 = window.MyClass1 = function() { ...etc... };

but that would need the @name work discussed above. I'll think about it - 
either way, 
thank you for a very informative discussion.

Original comment by nick.rab...@gmail.com on 13 Sep 2009 at 6:09

GoogleCodeExporter commented 8 years ago
Ha! You've found yet another way that JsDoc Toolkit is easily confused by names 
and used it to your advantage this time. I must 
say: that's quite a nifty solution! I hope you don't mind if I add it to the 
FAQ (with credit to you of course).

function() {
    var MyClass1;

 /**
  * Plainly a function-scoped variable but poor JsDoc Toolkit can't see that.
  * @constructor
  * @param {Object} options  Some options.
  */
    MyClass1 = function(options) {
    }

    // make the world right again
    window.MyClass1 = MyClass1
}

Well played, sir!

Original comment by micmath on 13 Sep 2009 at 9:09

GoogleCodeExporter commented 8 years ago
Glad you like it, and glad you got something out of this exchange as well. :)

Original comment by nick.rab...@gmail.com on 13 Sep 2009 at 9:27