From 73998951b176960bcb2dee4a335d747ea4a3b8ad Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Fri, 19 Sep 2014 09:01:14 -0700 Subject: [PATCH] fix `exports` tag when the module object is passed to an AMD function (#642) - Do not mark the doclet for the module object as undocumented. - Track variables (including aliases) within the parent scope, so the members are resolved against the alias instead of the name used in the code. --- lib/jsdoc/src/visitor.js | 16 +++++++++++---- test/fixtures/exportstag6.js | 18 +++++++++++++++++ test/specs/tags/exportstag.js | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/exportstag6.js diff --git a/lib/jsdoc/src/visitor.js b/lib/jsdoc/src/visitor.js index 7e97fa15..218deb09 100644 --- a/lib/jsdoc/src/visitor.js +++ b/lib/jsdoc/src/visitor.js @@ -69,6 +69,12 @@ function makeInlineParamsFinisher(parser) { return; } + // we only want to use the doclet if it's param-specific (but not, for example, if it's + // a param tagged with `@exports` in an AMD module) + if (e.doclet.kind !== 'param') { + return; + } + parentDoclet.params = parentDoclet.params || []; documentedParams = parentDoclet.params; knownParams = parentDoclet.meta.code.paramnames; @@ -76,9 +82,8 @@ function makeInlineParamsFinisher(parser) { while (true) { param = documentedParams[i]; - // is the param already documented? if so, we're done + // is the param already documented? if so, we don't need to use the doclet if (param && param.name === e.doclet.name) { - // the doclet is no longer needed e.doclet.undocumented = true; break; } @@ -351,20 +356,23 @@ Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) { break; // like "bar" in: function foo(/** @type {string} */ bar) {} + // or "module" in: define("MyModule", function(/** @exports MyModule */ module) {} // This is an extremely common type of node; we only care about function parameters with - // inline type annotations. No need to fire events unless they're already commented. + // inline comments. No need to fire an event unless the node is already commented. case Syntax.Identifier: parent = node.parent; if ( node.leadingComments && parent && jsdoc.src.astnode.isFunction(parent) ) { extras.finishers = [makeInlineParamsFinisher(parser)]; e = new SymbolFound(node, filename, extras); + + trackVars(parser, node, e); } break; // like "obj.prop" in: /** @typedef {string} */ obj.prop; // Closure Compiler uses this pattern extensively for enums. - // No need to fire events for them unless they're already commented. + // No need to fire an event unless the node is already commented. case Syntax.MemberExpression: if (node.leadingComments) { e = new SymbolFound(node, filename, extras); diff --git a/test/fixtures/exportstag6.js b/test/fixtures/exportstag6.js new file mode 100644 index 00000000..18bca55e --- /dev/null +++ b/test/fixtures/exportstag6.js @@ -0,0 +1,18 @@ +define(function( +/** + * A module representing a shirt. + * @exports my/shirt + * @version 1.0 + */ +shirtModule) { + /** A property of the module. */ + shirtModule.color = 'black'; + + /** @constructor */ + shirtModule.Turtleneck = function(size) { + /** A property of the class. */ + this.size = size; + }; + + return shirtModule; +}); diff --git a/test/specs/tags/exportstag.js b/test/specs/tags/exportstag.js index 360c660d..b24918e5 100644 --- a/test/specs/tags/exportstag.js +++ b/test/specs/tags/exportstag.js @@ -13,6 +13,7 @@ describe('@exports tag', function() { it('When an objlit symbol has an @exports tag, the doclet is aliased to "module:" + the tag value.', function() { expect(typeof shirt).toEqual('object'); expect(shirt.alias).toEqual('my/shirt'); + expect(shirt.undocumented).not.toBeDefined(); }); it('When an objlit symbol has an @exports tag, the doclet\'s longname includes the "module:" namespace.', function() { @@ -28,7 +29,10 @@ describe('@exports tag', function() { expect(color.memberof).toEqual('module:my/shirt'); expect(typeof tneck).toEqual('object'); + expect(tneck.memberof).toEqual('module:my/shirt'); + expect(typeof size).toEqual('object'); + expect(size.memberof).toEqual('module:my/shirt.Turtleneck'); }); }); @@ -96,4 +100,37 @@ describe('@exports tag', function() { expect(method.description).toBe('This should be in the Foo module doc.'); }); }); + + describe("'exports' object as a parameter to 'define'", function() { + var docSet = jasmine.getDocSetFromFile('test/fixtures/exportstag6.js'); + var shirt = docSet.getByLongname('module:my/shirt')[0]; + var color = docSet.getByLongname('module:my/shirt.color')[0]; + var tneck = docSet.getByLongname('module:my/shirt.Turtleneck')[0]; + var size = docSet.getByLongname('module:my/shirt.Turtleneck#size')[0]; + + it('When a param has an @exports tag, the doclet is aliased to "module:" + the tag value.', function() { + expect(typeof shirt).toBe('object'); + expect(shirt.alias).toBe('my/shirt'); + expect(shirt.undocumented).not.toBeDefined(); + }); + + it('When a param has an @exports tag, the doclet\'s longname includes the "module:" namespace.', function() { + expect(shirt.longname).toBe('module:my/shirt'); + }); + + it('When a param has an @exports tag, the doclet kind is set to module.', function() { + expect(shirt.kind).toEqual('module'); + }); + + it('When a param has an @exports tag, the properties added to the param are documented as members of the module.', function() { + expect(typeof color).toBe('object'); + expect(color.memberof).toBe('module:my/shirt'); + + expect(typeof tneck).toBe('object'); + expect(tneck.memberof).toBe('module:my/shirt'); + + expect(typeof size).toBe('object'); + expect(size.memberof).toBe('module:my/shirt.Turtleneck'); + }); + }); });