How do I JSDoc A Nested Object's Methods?
Asked Answered
A

4

48

I've been trying to use JSDoc3 to generate documentation on a file, but I'm having some difficulty. The file (which is a Require.js module) basically looks like this:

define([], function() {

    /*
     * @exports mystuff/foo
     */
    var foo = {
        /**
         * @member
         */
        bar: {
            /**
             * @method
             */
            baz: function() { /*...*/ }
        }
    };

    return foo;
}

The problem is, I can't get baz to show up in the generated documentation. Instead I just get a documentation file for a foo/foo module, which lists a bar member, but bar has no baz (just a link to foo's source code).

I've tried changing bar's directive to @property instead, and I've tried changing baz's directive to @member or @property, but none of that helps. No matter what I do, baz just doesn't seem to want to show up.

Does anyone know what directive structure I could use to get baz to appear in the generated documentation?

P.S. I've tried reading pages like this one on the JSDoc site http://usejsdoc.org/howto-commonjs-modules.html, but it only describes cases of foo.bar, not foo.bar.baz.

Anathematize answered 7/10, 2013 at 17:23 Comment(0)
H
59

You can use a combination of @module or @namespace along with @memberof.

define([], function() {

    /**
     * A test module foo
     * @version 1.0
     * @exports mystuff/foo
     * @namespace foo
     */
    var foo = {
        /**
         * A method in first level, just for test
         * @memberof foo
         * @method testFirstLvl
         */
        testFirstLvl: function(msg) {},
        /**
         * Test child object with child namespace
         * @memberof foo
         * @type {object}
         * @namespace foo.bar
         */
        bar: {
            /**
             * A Test Inner method in child namespace
             * @memberof foo.bar
             * @method baz
             */
            baz: function() { /*...*/ }
        },
        /**
         * Test child object without namespace
         * @memberof foo
         * @type {object}
         * @property {method} baz2 A child method as property defination
         */
        bar2: {
            /**
             * A Test Inner method
             * @memberof foo.bar2
             * @method baz2
             */
            baz2: function() { /*...*/ }
        },
        /**
         * Test child object with namespace and property def.
         * @memberof foo
         * @type {object}
         * @namespace foo.bar3
         * @property {method} baz3 A child method as property defination
         */
        bar3: {
            /**
             * A Test Inner method in child namespace
             * @memberof foo.bar3
             * @method baz3
             */
            baz3: function() { /*...*/ }
        },
        /**
         * Test child object
         * @memberof foo
         * @type {object}
         * @property {method} baz4 A child method
         */
        bar4: {
             /**
             * The @alias and @memberof! tags force JSDoc to document the
             * property as `bar4.baz4` (rather than `baz4`) and to be a member of
             * `Data#`. You can link to the property as {@link foo#bar4.baz4}.
             * @alias bar4.baz4
             * @memberof! foo#
             * @method bar4.baz4
             */
            baz4: function() { /*...*/ }
        }
    };

    return foo;
});

EDIT as per Comment: (Single page solution for module)

bar4 without that ugly property table. ie @property removed from bar4.

define([], function() {

    /**
     * A test module foo
     * @version 1.0
     * @exports mystuff/foo
     * @namespace foo
     */
    var foo = {
        /**
         * A method in first level, just for test
         * @memberof foo
         * @method testFirstLvl
         */
        testFirstLvl: function(msg) {},
        /**
         * Test child object
         * @memberof foo
         * @type {object}
         */
        bar4: {
             /**
             * The @alias and @memberof! tags force JSDoc to document the
             * property as `bar4.baz4` (rather than `baz4`) and to be a member of
             * `Data#`. You can link to the property as {@link foo#bar4.baz4}.
             * @alias bar4.baz4
             * @memberof! foo#
             * @method bar4.baz4
             */
            baz4: function() { /*...*/ },
            /**
             * @memberof! for a memeber
             * @alias bar4.test
             * @memberof! foo#
             * @member bar4.test
             */
             test : true
        }
    };

    return foo;
});

References -

  1. Another Question about nested namespaces
  2. For alternative way of using Namespaces
  3. Documenting literal objects

*Note I haven't tried it myself. Please try and share the results.

Honeysweet answered 12/10, 2013 at 16:8 Comment(9)
Thanks, but that doesn't work at all. I wind up with a foo documentation page with nothing on it (no mention of bar), and a "global" bar page with no baz method :-(Anathematize
Actually, if I use your code literally I don't even get that: I just get a foo page with nothing on it. But if I do something similar to what you suggested with my actual code, I get the equivalent of a foo page with nothing and a "global" bar page with nothing (ie. no baz).Anathematize
i never meant that you have to follow my example literaly. i simply said try to use them. anyways. i did. i am putting two ways of bar, both have different effects.Honeysweet
i ended up adding four bars. there are many ways of documenting the inner objects. Please try and comment. i can explain more on specific approaches. you can run jsdoc on the above code directly.Honeysweet
It worked ... or at least it worked a heck of a lot better! The @namespace foo.bar seems to have been the missing piece that I needed. The only thing that's still less than ideal is that each sub-level (eg. bar, bar2) gets its own page. Ideally instead I'd like to see bar and bar2 appear on foo's page (as I'm trying to generate a documentation page for the foo module, not a bunch of pages for every peice of foo) ... is there any way to make them do that?Anathematize
take a look at the bar4 sample. it will put the object as a member in the foo. and bar4.baz4 at the bottom along with other methods. This way you can detail any property within the same page of any nest level.Honeysweet
Perfect; I've awarded you the bounty, as you certainly earned it! Thanks so much for the help.Anathematize
For those here using JSDoc3 with ES6 classes, I've provided an example in my answer below. Happy hunting!Recusant
Your second edition it's almost perfect. Just remove the unnecessary @method tag memberof! foo# takes care of that already.Vermilion
A
8

Here's a simple way to do it:

/**
 * @module mystuff/foo
 * @version 1.0
 */
define([], function() {

/** @lends module:mystuff/foo */
var foo = {
    /**
     * A method in first level, just for test
     */
    testFirstLvl: function(msg) {},
    /**
     * @namespace
     */
    bar4: {
        /**
         * This is the description for baz4.
         */
        baz4: function() { /*...*/ },
        /**
         * This is the description for test.
         */
        test : true
    }
};

return foo;
});

Note that jsdoc can infer the types baz4.baz4 and test without having to say @method and @member.

As far as having jsdoc3 put documentation for classes and namespaces on the same page as the module that defines them, I don't know how to do it.

I've been using jsdoc3 for months, documenting a small library and a large application with it. I prefer to bend to jsdoc3's will in some areas than have to type reams of @-directives to bend it to my will.

Absent answered 7/11, 2013 at 1:2 Comment(1)
I like this... but the only thing that ever appears off to the right navigation (assuming default template) is the foo. Wish I could have the brevity of this notation plus a full contents!Petie
G
1

You can't document nested functions directly. I didn't like Prongs solution, so I used a different implementation without namespaces (it's JS, not Java!).

Update:

I updated my answer to reflect the exact use case given by the OP (which is fair, since JSdoc is pretty painful to use). Here is how it would work:

/** @module foobar */

/** @function */
function foobarbaz() {
    /* 
     * You can't document properties inside a function as members, like you
     * can for classes. In Javascript, functions are first-class objects. The
     * workaround is to make it a @memberof it's closest parent (the module).
     * manually linking it to the function using (see: {@link ...}), and giving
     * it a @name.
     */

    /**
     * Foo object (see: {@link module:foobar~foobarbaz})
     * @name foo
     * @inner
     * @private
     * @memberof module:foobar
     * @property {Object} foo - The foo object
     * @property {Object} foo.bar - The bar object
     * @property {function} foo.bar.baz - The baz function
     */
    var foo = {

        /* 
         * You can follow the same steps that was done for foo, with bar. Or if the
         * @property description of foo.bar is enough, leave this alone. 
         */
        bar: {

            /*
             * Like the limitation with the foo object, you can only document members 
             * of @classes. Here I used the same technique as foo, except with baz.
             */

            /**
             * Baz function (see: {@link module:foobar~foo})
             * @function
             * @memberof module:foobar
             * @returns {string} Some string
             */
            baz: function() { /*...*/ }
        }
    };

    return foo;
}

Unfortunately JSdoc is a port of Java, so it has a lot of features that make sense for Java but not for JS, and vice-versa. For example, since in JS functions are first-class objects, they can be treated as objects or functions. So doing something like this should work:

/** @function */
function hello() {
  /** @member {Object} */
  var hi = {};
}

But it won't, because JSdoc recognizes it as a function. You would have to use namespaces, my technique with @link, or to make it a class:

/** @class */
function Hello() {
  /** @member {Object} */
  var hi = {};
}

But then that doesn't make sense either. Do classes exist in JS? no, they don't.

I think we really need to find a better documentation solution. I've even seen inconsistencies in the documentation for with how types should be displayed (e.g. {object} vs {Object}).

You can also use my technique to document closures.

Graziano answered 17/12, 2014 at 2:18 Comment(3)
Please put your actual code in the question, instead of only linking it. Also it would help if you could adapt it for the OP's nested foo-bar-baz object literal.Grovel
Namespaces are a universal programming concept. Any given language may be more or less explicit about them, but even in languages like JS that aren't explicit in their use, they are still very much relevant. After all, why else would JSDoc, a documentation standard purely for the JavaScript language (not Java) include such a tag?Anathematize
JS doesn't really have namespaces. It only has two scopes: the global scope and the function scope. What does a namespace for JS look like? It's there because it's a port from Java. JSdoc doesn't support a lot of common JS idioms directly, like documenting JSON properties, closures, nested functions, etc.Graziano
R
0

Just to improve on Prongs's answer a bit for JSDoc3, I was only able to get it to work when I used the @instance annotation in lieu of @member.

ES6 example code follows:

class Test
{
    /**
     * @param {object} something
     */
    constructor(something)
    {
        this.somethingElse = something;

        /**
         * This sub-object contains all sub-class functionality.
         *
         * @type {object}
         */
        this.topology = {
            /**
             * Informative comment here!
             *
             * @alias topology.toJSON
             * @memberof! Test#
             * @instance topology.toJSON
             * 
             * @returns {object} JSON object
             */
            toJSON()
            {
                return deepclone(privatesMap.get(this).innerJSON);
            },


            ...
        }
    }
}
Recusant answered 2/10, 2016 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.