44

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.

machineghost
  • 28,573
  • 26
  • 128
  • 197

4 Answers4

56

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.

Mohit
  • 2,159
  • 17
  • 29
  • 1
    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 :-( – machineghost Oct 14 '13 at 18:47
  • 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). – machineghost Oct 14 '13 at 18:51
  • 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. – Mohit Oct 14 '13 at 20:23
  • 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. – Mohit Oct 14 '13 at 20:40
  • 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? – machineghost Oct 14 '13 at 23:55
  • 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. – Mohit Oct 15 '13 at 00:02
  • Perfect; I've awarded you the bounty, as you certainly earned it! Thanks so much for the help. – machineghost Oct 15 '13 at 00:37
  • For those here using JSDoc3 with ES6 classes, I've provided an example in my answer below. Happy hunting! – Xunnamius Oct 02 '16 at 14:01
  • Your second edition it's almost perfect. Just remove the unnecessary `@method` tag `memberof! foo#` takes care of that already. – Danielo515 Feb 15 '17 at 19:34
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.

Louis
  • 128,628
  • 25
  • 249
  • 295
  • 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! – Greg Pettit Jun 03 '15 at 19:19
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.

Community
  • 1
  • 1
risto
  • 1,204
  • 8
  • 11
  • 1
    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. – Bergi Dec 17 '14 at 18:21
  • 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? – machineghost Dec 17 '14 at 23:56
  • 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. – risto Dec 18 '14 at 00:09
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);
            },


            ...
        }
    }
}
Xunnamius
  • 423
  • 4
  • 12