Issues / questions with documenting AMD style subclasses / interfaces

141 views
Skip to first unread message

frontend_dev

unread,
Jun 4, 2013, 1:30:30 PM6/4/13
to jsdoc...@googlegroups.com
I have 2 AMD modules which both act as a class. Module "B" inherits from "A" and also uses some common methods from "A". In Module "A" (the superclass) I want do define and document an (empty) method which acts like a kind of interface and has to be subclassed in Module "B". For extending, I use a dedicated Class Library (in this case: "my-class")

Right now, my (simplified) pattern looks like this:

define(["myClass"], function (myClass) {

    /**
      * My Superclass module
      * @module modules/A
      * @exports modules/A
      */

   var A = myClass(/** @lends modules/A.prototype */ {

      /**
        * @constructs
        */

      constructor: function () { // constructor code here },

      /**
       * This is a common method also shared by the Subclass
       */

      commonMethod: function () { // some shared method },

     /**
       * This method represents an interface to be implemented by the subclass
       * @abstract
       */

      overrideMethod: function () { throw new Error("Must be subclassed"); }

   }

}


And Module "B" looks like this:

define(["myClass", "modules/A"], function (myClass, moduleA) {

    /**
      * My Superclass module
      * @module modules/B
      * @extends module:modules/A
      * @exports modules/B
      */

   var B = myClass(moduleA, /** @lends modules/B.prototype */ {

      /**
        * @constructs
        * @extends module:modules/A
        */

      constructor: function () { // constructor code here },

      // no separate doc comments here! But seems I need them .... or this is not visible, also docs from "modules/A" do not show.
      // shouldn't jsdoc use the Superclass description here?

     
overrideMethod: function () {  // concrete implementation here }

   }

}


While this works for the most part, I am not sure if everything is OK here, and there might be still some issues.

So now for my questions:
  • Is that the correct pattern to do this (in general)?
  • It seems that I need to specify @extends twice in "modules/B". If I omit it in the module definition block, the "Extends" Link is missing in the resulting doc. If I omit it in the "@constructs" block, it will not pull members and methods out of the Superclass. Is this intended behavior?
  • In "modules/A", the overrideMethod is shown as "virtual". Also shown in the Subclass docs if the overrideMethod is not overridden (definition is just taken from the Superclass). However, if I DO implement a concrete implementation of overrideMethod, this is not documented, and I have to add extra comments (basically by duplicating the Superclass docs) for the method to show up. Is this also intended behavior? I was rather expecting that in this case, the doc of the virtual class is also taken over, but without the "@abstract" of course, since this is a concrete implementation. So what I want to achieve is to document the interface methods on the Superclass _once_, and not having to duplicate them in every concrete subclass implementation.
  • If that does not work, what would be an appropriate workaround to prevent duplicating these methods? Using @typedef does feel a bit awkward in this case.
Hope I described clear enough what I mean - any help is greatly appreciated!

frontend_dev

unread,
Jun 6, 2013, 10:47:50 AM6/6/13
to jsdoc...@googlegroups.com
OK, so I slightly rewrote my code for module "B":

define(["myClass", "modules/A"], function (myClass, moduleA) {

    /**
      * My Superclass module
      * @module modules/B
      * @extends module:modules/A
      * @exports modules/B
      */

   var B = myClass(moduleA, /** @lends modules/B */ {


      /**
        * @constructs
        * @extends module:modules/A
        */

      constructor: function () { // constructor code here },

      // no separate doc comments here! But seems I need them .... or this is not visible, also docs from "modules/A" do not show.
      // shouldn't jsdoc use the Superclass description here?

     
overrideMethod: function () {  // concrete implementation here }

   }

}


So by removing the ".prototype" in "@lends" I am _almost_ there. Now, I do not have to document a overriden Subclass method, it just takes the docs from the superclass definition.

However, if I _do_ want to "overide" my docs by adding some tags to the overrided subclass, I now have _two_ methods with this name in my docs: the doc from the superclass method plus the docs from the subclass method.

Also, by this way the methods are not described as static - which they should. Also it seems a bit weird that in the Superclass I have to write: @lends modules/A.prototype while in the subclass it states: @lends modules/B (without the "prototype"). And if I omit the "prototype" also in the Superclass, its method are correctly described as "static", but then the inhertited methods do NOT show up in the Submodule anymore (but members actually still do ....)

No matter what combination I try, it is only almost right, but never works completely as I would expect.

So I am not sure if I have found a bug in jsDocs, or if I am doing something wrong here. Any enlightment would be helpful!

Daniel Ellis

unread,
Jun 6, 2013, 5:48:30 PM6/6/13
to frontend_dev, JSDoc Users
We do all kinds of inheritance and it documents pretty well for us. Our child modules look something like this:

define(function(require) {
    'use strict';

    var ModuleA = require('ModuleA');

    /**
     * ModuleB, a subclass of ModuleA
     *
     * @exports ModuleB
     * @extends module:ModuleA
     */
    var ModuleB = Class.create(
        ModuleA,
        /** @lends module:ModuleB.prototype */
        {
            /**
             * Constructor for ModuleA
             *
             * @constructs
             */
            initialize : function() { ... },

            /**
             * Method in ModuleB that overrides
             * a method defined as abstract in ModuleA
             */
            someMethod : function(id, k) { ... }
        }
    );

    return ModuleB;
});

Some differences I see that may or may not make a difference
  • Module reference syntax (modules/ModuleA vs. module:ModuleA)
  • No @module tag in your parent module doc
  • No @extends tag in your child constructor doc
  • @lends references the prototype
I will say that we spent a lot of hours trying different combinations until we found the right one. Please do update us when you've found a solution for your code style :)



--
You received this message because you are subscribed to the Google Groups "JSDoc Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jsdoc-users...@googlegroups.com.
To post to this group, send email to jsdoc...@googlegroups.com.
Visit this group at http://groups.google.com/group/jsdoc-users?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Michael Mathews

unread,
Jun 7, 2013, 5:19:57 AM6/7/13
to frontend_dev, jsdoc...@googlegroups.com
This do what you want at all?

````javascript
/**
 * This is a utility class.
 * @class MyClass
 */
var myClass = function() {}

define("modules/A", ["myClass"], function (myClass) {

    /**
     * My Superclass module
     * @module modules/A
     */
    var A = myClass(/** @lends module:modules/A.prototype */ {

        /**
         * Use this to make a module:modules/A
         * @constructs
         */
        constructor: function () {
        },

        /**
         * This is a common method also shared by the Subclass
         */
        commonMethod: function () {
        },

        /**
         * This method represents an interface to be implemented by the subclass
         * @abstract
         */
        overrideMethod: function () {
            throw new Error("Must be subclassed");
        }

    });
});


define("modules/B", ["myClass", "modules/A"], function (myClass, moduleA) {

    /**
     * My Subclass module
     * @module modules/B
     * @requires module:modules/A
     */
   var B = myClass(moduleA, /** @lends module:modules/B */ {

      /**
       * Use this to make a module:modules/B
       * @constructs
       * @extends module:modules/A
       */
      constructor: function () { // constructor code here
      },

      // no separate doc comments here
      overrideMethod: function () {  // concrete implementation here
      }
   });

});
````

Michael Mathews
mic...@gmail.com



frontend_dev

unread,
Jun 7, 2013, 9:50:02 AM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
Some differences I see that may or may not make a difference
  • Module reference syntax (modules/ModuleA vs. module:ModuleA)
I think that's only the path, does not seem to make any difference
 
  • No @module tag in your parent module doc

Also, does not seem to make any difference.
 
  • No @extends tag in your child constructor doc

If you omit this, there will be no "Extends:  {link}" entry in your docs.
 
  • @lends references the prototype
Yeah, and here things are getting interesting. First of all is lending the prototype really the right thing? Because then, the static methods do not show up as "<static>" anymore.

 
I will say that we spent a lot of hours trying different combinations until we found the right one. Please do update us when you've found a solution for your code style :)

Me too .. and I guess we still do not have the final solution ...

OK, so I rewrote my code to better match it  with yours. So ModuleA looks like this:

define(function () {

  'use strict';


    /**
     * My Superclass module
     * @exports ModuleA
     */

   var ModuleA = Class.create(/** @lends module:moduleA.prototype */ {


      /**
       * Constructor for ModuleA
       * @constructs
       */

      constructor: function () {  },


      /**
       * This is a common method also shared by the Subclass
       */

      commonMethod: function () {  },


     /**
       * This method represents an interface to be implemented by the subclass
       * @abstract
       */

      someMethod: function () { throw new Error("Must be subclassed"); }

   });

   return ModuleA;

})
;

And ModuleB like this:

define(function(require) {

    'use strict';

    var ModuleA = require('ModuleA');

    /**
     * ModuleB, a subclass of ModuleA
     * @extends  ModuleA
     * @exports ModuleB
     */

    var ModuleB = Class.create(ModuleA, /** @lends module:ModuleB.prototype */ {

            /**
             * Constructor for ModuleB
             * @constructs
             */
            initialize : function() {   },


            /**
             * Method in ModuleB that overrides
             * a method defined as abstract in ModuleA
             */
            someMethod : function(id, k) {   },

            /**
             * method exclusive to the Subclass
             */

            someOtherMethod: function () {}
        }
    );

    return ModuleB;
});


What happens then is that the docs for "ModuleA" are empty, but instead a new Class module:ModuleA is created - which is absolutely not desired! Furthermore, what happened to the method "commonMethod"? It does not show up in ModuleB, but it should, since it is inherited. Plus, methods are not marked as "static". And while I can "override" my docs for an overrided function, if I omit this additional description, the overrided method also does not show up in MethodB.

What does work here though, is that the "virtual" function in the Superclass does become a non virtual one, but that is about the only thing that works as expected here.

So, if I omit the ".prototype" in @lends at least the <static> description is back. But all other problems remain.

OK, next I replaced "@constructs" with "@constructor @alias module:ModuleA" (like in the official example). Now, the Class vanished and the module is treated like a class.

But alas, now the methods of the Superclass just vanish! Also they are not reflected in the Subclass ....

No matter what I do, I simply cannot get to the desired result. I think I am slowly going mad ;)

And the longer I try I think there might be something going on with jsdoc - a bug, maybe?

frontend_dev

unread,
Jun 7, 2013, 10:05:31 AM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
This do what you want at all?

Your example is similar to what I achieved, but problems remain:
  • First of all, what I still do not understand, is that in the Superclass I have to write @lends module:modules/A.prototype while in the Subclass I have to write: /** @lends module:modules/B */. It just seems weird.
  • Also, no "<static>" methods in the Superclass.
  • But, (exclusive) Subclass methods ARE <static>.
  • The "virtual" methods also shows as "virtual" in the Subclass
  • If I omit the description for "commonMethod" and just define it in the Subclass, the description of the Superclass is inserted, which is fine. However, if I DO add a custom description for my overrided method, there are actually TWO descriptions of this method appear in my docs. Interestingly, this seems NOT to happen if I try this with members instead of methods.

This is how the test code look like with your proposed solution:

ModuleA:

/**
 * This is a utility class.
 * @class MyClass
 */
var myClass = function() {}

define("modules/A", ["myClass"], function (myClass) {

    /**
     * My Superclass module
     * @module modules/A
     */
    var A = myClass(/** @lends module:modules/A.prototype */ {

        /**
         * Use this to make a module:modules/A
         * @constructs
         */
        constructor: function () {

          /**
           * Member of the Superclass
           * @type {String}
           */
          this.memberA = "test";



        },

        /**
         * This is a common method also shared by the Subclass
         */
        commonMethod: function () {
        },

        /**
         * This method represents an interface to be implemented by the subclass
         * @abstract
         */
        overrideMethod: function () {
            throw new Error("Must be subclassed");
        }

    });
});


ModuleB:

define("modules/B", ["myClass", "modules/A"], function (myClass, moduleA) {

    /**
     * My Subclass module
     * @module modules/B
     * @requires module:modules/A
     */
   var B = myClass(moduleA, /** @lends module:modules/B */ {

      /**
       * Use this to make a module:modules/B
       * @constructs
       * @extends module:modules/A
       */
      constructor: function () { // constructor code here

          /**
           * Overide Member Docs
           * @type {String}
           */
          this.memberA = "test";

          /**
           * Member of the Subclass
           * @type {String}
           */
          this.memberB = "test";

      },

      /**
       * Override docs here

       */

      commonMethod: function () { },

      // no separate doc comments here
      overrideMethod: function () {  // concrete implementation here
      },

      /**
       * Exclusive to the subclass
       */

      anotherMethod: function () {}

   });

});

So it still seems to me that there is a kind of bug or missing feature somewhere.

Michael Mathews

unread,
Jun 7, 2013, 10:46:23 AM6/7/13
to frontend_dev, jsdoc...@googlegroups.com
On 7 Jun 2013, at 15:05, frontend_dev <kude...@alphanull.de> wrote:

This do what you want at all?

Your example is similar to what I achieved, but problems remain:
  • First of all, what I still do not understand, is that in the Superclass I have to write @lends module:modules/A.prototype while in the Subclass I have to write: /** @lends module:modules/B */. It just seems weird.

Yeh that seems like a bug to me too. Have you raised a ticket for this on the issue tracker yet?

  • Also, no "<static>" methods in the Superclass.
On Superclass? I see no static methods in your example. There is no way for JSDoc to know what myClass() will do with your methods, whether it will make them static or instance. You told it you were sticking them on the prototype with the @lends tag and so that's how they are documented. If you want them to be documented as static you need to say so (try @memberof or @static).

(or maybe you meant "Subclass"? in which case, read on...)
  • But, (exclusive) Subclass methods ARE <static>.
True and that seems correct to me, because in that case you are telling @lends you are sticking those methods directly onto the module object (not it's prototype) so they will be considered "static". Of course the reason you are doing that is the likely bug in your first point.

  • The "virtual" methods also shows as "virtual" in the Subclass
Yep, two feature requests here: one, the word "virtual" should arguably be expressed as "abstract" in the output. It's called "virtual" for two reasons, one is that "abstract" is a reserved word in JS, two is that JS doesn't have abstract methods implemented in any available engine (so we may as well make up a word for a made up concept -- but I prefer "abstract" in the output). The second feature is that an abstract doc should lose it's abstract tag when it is in an @extended context. This might be complex to implement, so maybe a new explicit tag, like @implemented or @concrete when in the implementing doc would strip the abstract status.

  • If I omit the description for "commonMethod" and just define it in the Subclass, the description of the Superclass is inserted, which is fine. However, if I DO add a custom description for my overrided method, there are actually TWO descriptions of this method appear in my docs. Interestingly, this seems NOT to happen if I try this with members instead of methods.
The reason you are seeing two is, again, due to the @lends (prototype) in Superclass combined with @lends (no prototype) in the subclass. It is perfectly possible to have two methods with the same name, one on the object, another on that object's prototype, and JSDoc supports this. You don't see the second (static) docs when you don't doc-comment the method because, well, there's no doc comment, and JSDoc won't document uncommented things. The reason you see two when you do comment it is because it appears that you are commenting two different methods with the same name, one on the inherited prototype, and another directly on the Subclass itself (static).

frontend_dev

unread,
Jun 7, 2013, 11:11:43 AM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
  • First of all, what I still do not understand, is that in the Superclass I have to write @lends module:modules/A.prototype while in the Subclass I have to write: /** @lends module:modules/B */. It just seems weird.

Yeh that seems like a bug to me too. Have you raised a ticket for this on the issue tracker yet?

Not yet, wanted to clarify first if this might be an actual bug.
 
  • Also, no "<static>" methods in the Superclass.
On Superclass? I see no static methods in your example. There is no way for JSDoc to know what myClass() will do with your methods, whether it will make them static or instance. You told it you were sticking them on the prototype with the @lends tag and so that's how they are documented. If you want them to be documented as static you need to say so (try @memberof or @static).

Maybe I am getting something wrong, but since the methods of ModuleA are also on the prototype, they are static, right?

Also using @static does just nothing.

The only thing to get it right is to write: @lends module:modules/A (_without_ the ".prototype")

But then, the "overrideMethod" is simply not showing up in my Subclass, but it should!
 
(or maybe you meant "Subclass"? in which case, read on...)
  • But, (exclusive) Subclass methods ARE <static>.
True and that seems correct to me, because in that case you are telling @lends you are sticking those methods directly onto the module object (not it's prototype) so they will be considered "static". Of course the reason you are doing that is the likely bug in your first point.

Yes, I think it is correct to mark them as static. But still, the _inherited_ Superclass methods remain also static then, right?
 
  • The "virtual" methods also shows as "virtual" in the Subclass
Yep, two feature requests here: one, the word "virtual" should arguably be expressed as "abstract" in the output. It's called "virtual" for two reasons, one is that "abstract" is a reserved word in JS, two is that JS doesn't have abstract methods implemented in any available engine (so we may as well make up a word for a made up concept -- but I prefer "abstract" in the output).

Well, I actually used @abstract - it's just the output that reads "virtual" - maybe change this also to "<abstract>", to avoid confusion?
 
The second feature is that an abstract doc should lose it's abstract tag when it is in an @extended context. This might be complex to implement, so maybe a new explicit tag, like @implemented or @concrete when in the implementing doc would strip the abstract status.

And it seems that there is already code that handles this - if I take Daniels example, the "<virtual>" designation does  _not_ show up in the concrete subclass implementation which I would expect. It just seems not to work when using the module notation (instead of a @class like structure)
 
  • If I omit the description for "commonMethod" and just define it in the Subclass, the description of the Superclass is inserted, which is fine. However, if I DO add a custom description for my overrided method, there are actually TWO descriptions of this method appear in my docs. Interestingly, this seems NOT to happen if I try this with members instead of methods.
The reason you are seeing two is, again, due to the @lends (prototype) in Superclass combined with @lends (no prototype) in the subclass. It is perfectly possible to have two methods with the same name, one on the object, another on that object's prototype, and JSDoc supports this. You don't see the second (static) docs when you don't doc-comment the method because, well, there's no doc comment, and JSDoc won't document uncommented things.

Well, if the undocumented method is documented on the Superclass, this doc is already used - which is fine. But: if I actually want to override the doc in the subclassed function, _two_ entries appear - which shouldn't be the case here imho. I also don't think you can have two methods with the same name on the same object, so this still seems wrong to me. Just try my latest example for seeing this.
 
The reason you see two when you do comment it is because it appears that you are commenting two different methods with the same name, one on the inherited prototype, and another directly on the Subclass itself (static).

Yes, and therefore I think @lends _without_ adding .prototype should be the right thing. But still, the problems I mentioned remain.

So the question is: what is the actual bug that causes this? Maybe we  should narrow it down further before opening a ticket?

Michael Mathews

unread,
Jun 7, 2013, 11:44:15 AM6/7/13
to frontend_dev, jsdoc...@googlegroups.com
On 7 Jun 2013, at 16:11, frontend_dev <kude...@alphanull.de> wrote:

  • First of all, what I still do not understand, is that in the Superclass I have to write @lends module:modules/A.prototype while in the Subclass I have to write: /** @lends module:modules/B */. It just seems weird.

Yeh that seems like a bug to me too. Have you raised a ticket for this on the issue tracker yet?

Not yet, wanted to clarify first if this might be an actual bug.
 
  • Also, no "<static>" methods in the Superclass.
On Superclass? I see no static methods in your example. There is no way for JSDoc to know what myClass() will do with your methods, whether it will make them static or instance. You told it you were sticking them on the prototype with the @lends tag and so that's how they are documented. If you want them to be documented as static you need to say so (try @memberof or @static).

Maybe I am getting something wrong, but since the methods of ModuleA are also on the prototype, they are static, right?

Yes. But opposite.

function Foo() {
}
Foo.prototype.bar = 1; // instance, via prototype
Foo.bar = 2; // static

Two completely different properties.


Also using @static does just nothing.

I guess it depend how you use it. It appears to do something here:


The only thing to get it right is to write: @lends module:modules/A (_without_ the ".prototype")



But then, the "overrideMethod" is simply not showing up in my Subclass, but it should!
 
(or maybe you meant "Subclass"? in which case, read on...)
  • But, (exclusive) Subclass methods ARE <static>.
True and that seems correct to me, because in that case you are telling @lends you are sticking those methods directly onto the module object (not it's prototype) so they will be considered "static". Of course the reason you are doing that is the likely bug in your first point.

Yes, I think it is correct to mark them as static. But still, the _inherited_ Superclass methods remain also static then, right?


I think if your subclass has a static method, and it inherits an instance/prototypal method with the same name from a superclass, then congratulations, you now have two methods. But I can't keep track of which methods you think are static anymore, sorry.

 
  • The "virtual" methods also shows as "virtual" in the Subclass
Yep, two feature requests here: one, the word "virtual" should arguably be expressed as "abstract" in the output. It's called "virtual" for two reasons, one is that "abstract" is a reserved word in JS, two is that JS doesn't have abstract methods implemented in any available engine (so we may as well make up a word for a made up concept -- but I prefer "abstract" in the output).

Well, I actually used @abstract - it's just the output that reads "virtual" - maybe change this also to "<abstract>", to avoid confusion?

Yes, that's exactly what I said (or meant to say at least).

 
The second feature is that an abstract doc should lose it's abstract tag when it is in an @extended context. This might be complex to implement, so maybe a new explicit tag, like @implemented or @concrete when in the implementing doc would strip the abstract status.

And it seems that there is already code that handles this - if I take Daniels example, the "<virtual>" designation does  _not_ show up in the concrete subclass implementation which I would expect. It just seems not to work when using the module notation (instead of a @class like structure)

That's because Daniel is using @lends module:ModuleB.prototype and then documenting the concrete method with a new comment. The bug this issue identified already means the (virtual) comment from the Superclass is not inherited, thus you do not see the "<virtual>" designation.

 
  • If I omit the description for "commonMethod" and just define it in the Subclass, the description of the Superclass is inserted, which is fine. However, if I DO add a custom description for my overrided method, there are actually TWO descriptions of this method appear in my docs. Interestingly, this seems NOT to happen if I try this with members instead of methods.
The reason you are seeing two is, again, due to the @lends (prototype) in Superclass combined with @lends (no prototype) in the subclass. It is perfectly possible to have two methods with the same name, one on the object, another on that object's prototype, and JSDoc supports this. You don't see the second (static) docs when you don't doc-comment the method because, well, there's no doc comment, and JSDoc won't document uncommented things.

Well, if the undocumented method is documented on the Superclass, this doc is already used - which is fine. But: if I actually want to override the doc in the subclassed function, _two_ entries appear - which shouldn't be the case here imho. I also don't think you can have two methods with the same name on the same object, so this still seems wrong to me. Just try my latest example for seeing this.

You are not putting two methods with the same name on the same object: one is on the object the other is on the object's prototype. See my example above.

 
The reason you see two when you do comment it is because it appears that you are commenting two different methods with the same name, one on the inherited prototype, and another directly on the Subclass itself (static).

Yes, and therefore I think @lends _without_ adding .prototype should be the right thing. But still, the problems I mentioned remain.

Well, yes, but @lends should not have any opinion about your inheritance patterns. If you want to @lends onto a prototype, then you should be able to do that or not. This should have no effect of how @extends works. I think your question may actually be about how @extends should work when methods of the Superclass are attached to the prototype, and same-named methods on the Subclass are attached to the object directly (or vice versa).

Of course this question would be moot if you could fix the @lends problem of requiring one to be on prototype and the other to be on the object directly.


So the question is: what is the actual bug that causes this? Maybe we  should narrow it down further before opening a ticket?

Jeff Williams

unread,
Jun 7, 2013, 12:07:34 PM6/7/13
to frontend_dev, jsdoc...@googlegroups.com
I played around with this for a bit. Here's what I've discovered.

I started writing this before micmath's follow-up emails, which I haven't read, so apologies if this duplicates or contradicts those emails.

tl;dr
  • I don't think the current behavior is the result of a JSDoc bug (but I filed #432 to change "virtual" to "abstract" in the default template).
  • I've provided a code sample with JSDoc comments that should do what you want.
  • In JSDoc (and in plain-vanilla JavaScript), a subclass does not automatically inherit static members from its parent class. This is by design. However, it's possible to work around this.
  • It would be nice if you could tell JSDoc to automatically inherit static members. I filed #433 for this enhancement.
  • Your life will become easier once JSDoc supports @inheritDoc and @implements tags (coming in version 3.3).
Working sample
The following JSDoc comments should give you almost exactly the output you want, but they require a lot of duplication (keep reading for an explanation of this). They'll require less duplication in JSDoc 3.3.

Note that I'm using @lends module:modules/X.prototype on the loaned object in each module, to indicate that it contains instance members. Also, no need for an @extends tag in the module definition.

The output will say "virtual" instead of "abstract" until we fix #432.

A.js:
define(["myClass"], function(myClass) {

    /**
     * My Superclass module
     * @module modules/A
     * @exports modules/A
     */
    var A = myClass(/** @lends module:modules/A.prototype */{

        /**
         * @constructs
         */
        constructor: function() {/* constructor code here */},

        /**
         * This is a common method also shared by the Subclass
         */
        commonMethod: function() {/* some shared method */},

        /**
         * Describe static version of commonMethod
         * (hopefully JSDoc 3.3 will let you use @inheritdoc instead)
         * (or a plugin; see https://github.com/jsdoc3/jsdoc/issues/433)
         * @name module:modules/A.commonMethod
         * @function
         */

        /**
         * This method represents an interface to be implemented by the subclass
         * @abstract
         */
        overrideMethod: function() { throw new Error("Must be subclassed"); }

        /**
         * Describe static version of overrideMethod
         * (hopefully JSDoc 3.3 will let you use @inheritdoc instead)
         * (or a plugin; see https://github.com/jsdoc3/jsdoc/issues/433)
         * @name module:modules/A.overrideMethod
         * @function
         */
    });
});


B.js:
define(["myClass", "modules/A"], function (myClass, moduleA) {

    /**
     * My Superclass module
     * @module modules/B
     * @exports modules/B
     */
    var B = myClass(moduleA, /** @lends module:modules/B.prototype */{

        /**
         * @constructs
         * @extends module:modules/A
         */
        constructor: function() { /* constructor code here */ },

        /**
         * Describe commonMethod again
         * (hopefully JSDoc 3.3 will let you use @inheritdoc instead)
         * (or a plugin; see https://github.com/jsdoc3/jsdoc/issues/433)
         * @name module:modules/B.commonMethod
         * @function
         */

        /**
         * Describe overrideMethod again
         * (hopefully JSDoc 3.3 will let you use @implements instead)
         */
        overrideMethod: function() {  /* concrete implementation here */ }

        /**
         * Describe static version of overrideMethod
         * (hopefully JSDoc 3.3 will let you use @inheritdoc instead)
         * (or a plugin; see https://github.com/jsdoc3/jsdoc/issues/433)
         * @name module:modules/B.overrideMethod
         * @function
         */
   });
});


Inheritance of static members
If Class B is a subclass of Class A, and Class A has static members, JSDoc assumes that Class B does not inherit Class A's static members.

This makes sense when you think about how things work in plain-vanilla JavaScript. For example, suppose the class MyClass has a static method called sayName. You will not be able to call that method from an instance of the class (let alone a subclass), because the method is not attached to the class' prototype:

var MyClass = function() {};
MyClass.sayName = function(name) { console.log('hello %s', name); };

var c = new MyClass();
c.sayName('Jeff'); // TypeError: Object [object Object] has no method 'sayName'

Of course, you can add the static method to the prototype, so that it's also available on instances:

MyClass.prototype.sayName = MyClass.sayName;
c.sayName('Jeff'); // prints 'hello Jeff'

And that's exactly what happens in many JavaScript class frameworks.

Right now, if you want your docs to reflect this pattern, you have to document each method twice: Once for the instance version, plus a virtual comment for the static version. It would be nice if you had the option of telling JSDoc to do this automatically. I filed #433 for that enhancement.

Why all the repetition?
Two reasons:
  1. In JSDoc, a member can be identified as a static member or an instance member, but not both. Changing this would be extremely difficult.
  2. We don't currently have a tag that allows you to grab the docs from some other symbol, or to say "just use the superclass' docs for this symbol." That will change in JSDoc 3.3, which will add @inheritDoc, @override, and @implements tags. I've said this so many times in the past few days that I think it's become a promise. (Not a Promises/A+ promise, mind you. Sorry, couldn't help myself.)

I hope at least some of that helps. Let me know if I've just confused things even further.

- Jeff


--

Michael Mathews

unread,
Jun 7, 2013, 12:09:00 PM6/7/13
to frontend_dev, jsdoc...@googlegroups.com
I've created a reduced example case of what I think is the issue here: https://gist.github.com/micmath/5730364

Turns out it is completely the behaviour or @extends. Note that @abstarct is not required to see the effect but is there to illustrate the use case.

It appears that the subclass's non-commented method is expressed (by showing no docs for that method), over the superclass's commented method. I'd propose that if a subclass has defined the inherited method, but not provided any docs, then the docs from the superclass are shown instead (minus any @abstract state, as discussed).

Michael Mathews
mic...@gmail.com


Jeff Williams

unread,
Jun 7, 2013, 12:12:26 PM6/7/13
to Michael Mathews, frontend_dev, jsdoc...@googlegroups.com
Yeah, that probably makes sense. In essence, we'd be adding the not-yet-implemented @inheritDoc tag on the user's behalf. I updated issue #53 accordingly.

- Jeff

frontend_dev

unread,
Jun 7, 2013, 12:12:53 PM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
Maybe I am getting something wrong, but since the methods of ModuleA are also on the prototype, they are static, right?

Yes. But opposite.

Yes, you are right. I think I mixed something up here.

So therefore, the @lends should be written as: /** @lends module:modules/B.prototype */ in _both_ modules, right?
    
But still, I have the problem that the "overrideMethod" docs _ only_ show up in ModuleB if:

- It is not defined in ModuleB at all (takes the doc from the Superclass)
- It is defined in ModuleB, _and_ if I add a separate doc entry for this method in ModuleB

But in most cases this is not what I want, since the overriden methods share the same params etc as defined in the superclass. I would expect that the Superclass docs _also_ show up when you define a concrete subclass, but do _not_ document it separately.

So is this a bug?
 
 
That's because Daniel is using @lends module:ModuleB.prototype and then documenting the concrete method with a new comment. The bug this issue identified already means the (virtual) comment from the Superclass is not inherited, thus you do not see the "<virtual>" designation.

OK, so I know it is a bug.
 
You are not putting two methods with the same name on the same object: one is on the object the other is on the object's prototype. See my example above.

Of course not! This was only my confusion with the correct @lends tag. But actually, if you subclass via whatever mechanism, the prototype of the Superclass is copied over to the subclass. if the subclass has a method with the same name, it is just overriden - depending on the implementation, you still might have access to the original method via some "Super...." construct
 

frontend_dev

unread,
Jun 7, 2013, 12:18:59 PM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
Yeah, I think you nailed it down with your example. So it seems that these are two separate issues:

1) take over any superclass method docs if a subclass overrides this method method, but does not add any additional docs
2) If a method was defined as @abstract in the Superclass, remove this in any concrete  subclass method.

Michael Mathews

unread,
Jun 7, 2013, 12:20:12 PM6/7/13
to frontend_dev, jsdoc...@googlegroups.com
I think Jeff is already on top of this:

A general statement, if I may: at least some of this issue is to do with the fact that JSDoc is a general-purpose tool, designed to handle VanillaJS, which doesn't (yet, commonly) have classes or abstracts or classical inheritance. So users' that are relying on a library to simulate those things are making some assumptions that JSDoc can't make. The only way forward, in my opinion, is to provide @tags to allow the user to express those assumptions. But again, I think Jeff is squarely on top of this with the @inheritdocs work, etc.

Michael Mathews
mic...@gmail.com



frontend_dev

unread,
Jun 7, 2013, 12:24:50 PM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
I think Jeff is already on top of this:

A general statement, if I may: at least some of this issue is to do with the fact that JSDoc is a general-purpose tool, designed to handle VanillaJS, which doesn't (yet, commonly) have classes or abstracts or classical inheritance. So users' that are relying on a library to simulate those things are making some assumptions that JSDoc can't make. The only way forward, in my opinion, is to provide @tags to allow the user to express those assumptions. But again, I think Jeff is squarely on top of this with the @inheritdocs work, etc.

Well, actually I do not think any additional tags are necessary here, if the two remaining issues I described above are resolved. Jeffs example is also quite verbose, I'd really prefer to use the way I described (also without the need to throw in @inheritdocs everywhere - it just seems superfluous here, as long as the comments in my Superclass are carried over correctly, which seems to be the real issue, also with @virtual)

To me, these still sound like actual bugs?

frontend_dev

unread,
Jun 7, 2013, 12:31:08 PM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
Also, Michaels example shows that this happens when you use @class, too. And since the Subclass B "knows" that it inherits from "A", the method "b" _should_ appear in the docs imho, wether it has additional docs (take them) or not (take them from the class it inherited from). Same thing goes for @virtual, since it also should be clear that the method defined in B cannot be virtual anymore.

Michael

unread,
Jun 7, 2013, 12:41:59 PM6/7/13
to frontend_dev, jsdoc...@googlegroups.com, frontend_dev
Not sure I agree with that last statement. Should it be possible to inherit a method that is abstract and have it (optionally) remain abstract -- thinking of a longer chain of inheritance here where the concrete implementation might not appear until we get to C or D.

--
Michael

On 7 Jun 2013, at 17:31, frontend_dev <kude...@alphanull.de> wrote:

Also, Michaels example shows that this happens when you use @class, too. And since the Subclass B "knows" that it inherits from "A", the method "b" _should_ appear in the docs imho, wether it has additional docs (take them) or not (take them from the class it inherited from). Same thing goes for @virtual, since it also should be clear that the method defined in B cannot be virtual anymore.

frontend_dev

unread,
Jun 7, 2013, 12:59:40 PM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
Well, actually the more serious issue for me is that the docs of a subclassed, non described (in the subclass) method are not carried over from the superclass. That would be really nice to fix, since it is giving me all the headache and there is no good workaround (without breaking other things or having to duplicate a lot of docs).

As for @abstract, this is how I see it:

- you define an abstract method on the superclass (essentially acting as a kind of interface) -> show up as <virtual> - fine!
- in a subclass, you do not implement this method -> still shows up as <virtual> which is also OK for me. So this remains still <virtual> in the docs also right now - just as you said.
- But! if you do implement this method in a subclass (and add no additinal doc tags) -> result should not appear as <virtual> anymore (imho!)

So, as soon as you define your abstract method in a superclass, isn't it safe to say that then it is not abstract anymore?

Daniel Ellis

unread,
Jun 7, 2013, 1:35:36 PM6/7/13
to frontend_dev, JSDoc Users
Another idea would be to throw a parse error if you're trying to document a function that overrides a virtual function in a superclass without the @inheritDoc or @override tags, a la the required override keyword in C# (and others).


frontend_dev

unread,
Jun 7, 2013, 2:05:39 PM6/7/13
to jsdoc...@googlegroups.com, frontend_dev
Well, of course you could use some additional tags to enforce a certain style of documentation, but in this case I still see no need, because everything is already (implicitly) defined. Otherwise I would still have to add lots of @override tags.

I think when the two issues mentioned are fixed, we are good to go without needing to introduce new functionality, at least for this usecase which I think would be the most common. (ie define your virtual method in your interface class, then override it later - but no need to duplicate docs or adding additional tags)
Reply all
Reply to author
Forward
0 new messages