diff --git a/lib/jsdoc/src/astnode.js b/lib/jsdoc/src/astnode.js index cafc932a..37d4bd76 100644 --- a/lib/jsdoc/src/astnode.js +++ b/lib/jsdoc/src/astnode.js @@ -197,13 +197,25 @@ var nodeToValue = exports.nodeToValue = function(node) { if (parent.type === Syntax.ClassExpression) { str = nodeToValue(parent.parent); } + // for the constructor of a module's default export, use a special name + else if (node.kind === 'constructor' && parent.parent && + parent.parent.type === Syntax.ExportDefaultDeclaration) { + str = 'module.exports'; + } + // if the method is a member of a module's default export, ignore the name, because it's + // irrelevant + else if (parent.parent && parent.parent.type === Syntax.ExportDefaultDeclaration) { + str = ''; + } // otherwise, use the class's name else { - str = nodeToValue(parent.id); + str = parent.id ? nodeToValue(parent.id) : ''; } if (node.kind !== 'constructor') { - str += node.static ? name.SCOPE.PUNC.STATIC : name.SCOPE.PUNC.INSTANCE; + if (str) { + str += node.static ? name.SCOPE.PUNC.STATIC : name.SCOPE.PUNC.INSTANCE; + } str += nodeToValue(node.key); } break; diff --git a/lib/jsdoc/src/handlers.js b/lib/jsdoc/src/handlers.js index b98b9fde..13cb3b21 100644 --- a/lib/jsdoc/src/handlers.js +++ b/lib/jsdoc/src/handlers.js @@ -7,6 +7,9 @@ var escape = require('escape-string-regexp'); var jsdoc = { doclet: require('jsdoc/doclet'), name: require('jsdoc/name'), + src: { + syntax: require('jsdoc/src/syntax') + }, util: { logger: require('jsdoc/util/logger') } @@ -16,6 +19,7 @@ var util = require('util'); var currentModule = null; var SCOPE_NAMES = jsdoc.name.SCOPE.NAMES; var SCOPE_PUNC = jsdoc.name.SCOPE.PUNC; +var Syntax = jsdoc.src.syntax.Syntax; var unresolvedName = /^((?:module.)?exports|this)(\.|$)/; function CurrentModule(doclet) { @@ -90,9 +94,21 @@ function setCurrentModule(doclet) { function setModuleScopeMemberOf(doclet) { // handle module symbols that are _not_ assigned to module.exports if (currentModule && currentModule.longname !== doclet.name) { - // if we don't already know the scope, it must be an inner member if (!doclet.scope) { - doclet.addTag('inner'); + // is this a method definition? if so, get the scope from the node directly + if (doclet.meta && doclet.meta.code && doclet.meta.code.node && + doclet.meta.code.node.type === Syntax.MethodDefinition) { + if (doclet.meta.code.node.static) { + doclet.addTag('static'); + } + else { + doclet.addTag('instance'); + } + } + // otherwise, it must be an inner member + else { + doclet.addTag('inner'); + } } // if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof diff --git a/test/fixtures/anonymousclass.js b/test/fixtures/anonymousclass.js index 307beef9..65980f51 100644 --- a/test/fixtures/anonymousclass.js +++ b/test/fixtures/anonymousclass.js @@ -1,4 +1,16 @@ -/** @module */ +/** @module test */ /** Test class */ -export default class { } +export default class { + /** Test constructor */ + constructor(foo) { + /** Test member */ + this.foo = foo; + } + + /** Test method */ + test() {} + + /** Test static method */ + static staticTest() {} +} diff --git a/test/fixtures/moduleisconstructor.js b/test/fixtures/moduleisconstructor.js index c7a22803..c2f87cb4 100644 --- a/test/fixtures/moduleisconstructor.js +++ b/test/fixtures/moduleisconstructor.js @@ -1,19 +1,28 @@ /** - Describe the module here. - @module mymodule/config -*/ + * Describe the module here. + * @module mymodule/config + */ /** - Create a new configuration. - - @classdesc Describe the class here. - @class - @alias module:mymodule/config - @param {string} id -*/ + * Create a new configuration. + * + * @classdesc Describe the class here. + * @class + * @alias module:mymodule/config + * @param {string} id + */ function Config(id) { /** Document me. */ this.id = id; } -module.exports = Config; \ No newline at end of file +/** + * Get the configuration ID. + * + * @return {string} The configuration ID. + */ +Config.prototype.getId = function() { + return this.id; +} + +module.exports = Config; diff --git a/test/fixtures/moduleisconstructor2.js b/test/fixtures/moduleisconstructor2.js new file mode 100644 index 00000000..bd7ff1ea --- /dev/null +++ b/test/fixtures/moduleisconstructor2.js @@ -0,0 +1,22 @@ +/** + * Describe the module here. + * @module mymodule/config + */ + +/** Describe the class here. */ +export default class Config { + /** Create a new configuration. */ + constructor(id) { + /** Document me. */ + this.id = id; + } + + /** + * Get the configuration ID. + * + * @return {string} The configuration ID. + */ + getId() { + return this.id; + } +} diff --git a/test/specs/documentation/anonymousclass.js b/test/specs/documentation/anonymousclass.js index 42384162..f434112d 100644 --- a/test/specs/documentation/anonymousclass.js +++ b/test/specs/documentation/anonymousclass.js @@ -1,11 +1,25 @@ 'use strict'; describe('anonymous class', function() { - it('should not crash JSDoc', function() { - function getDocs() { - return jasmine.getDocSetFromFile('test/fixtures/anonymousclass.js'); - } + var docSet = jasmine.getDocSetFromFile('test/fixtures/anonymousclass.js'); + var klass = docSet.getByLongname('module:test')[2]; + var foo = docSet.getByLongname('module:test#foo')[0]; + var klassTest = docSet.getByLongname('module:test#test')[0]; + var klassStaticTest = docSet.getByLongname('module:test.staticTest')[0]; - expect(getDocs).not.toThrow(); + it('should merge the constructor docs with the class docs', function() { + expect(klass.description).toBe('Test constructor'); + }); + + it('should use the correct longname for instance properties', function() { + expect(foo.description).toBe('Test member'); + }); + + it('should use the correct longname for instance methods', function() { + expect(klassTest.description).toBe('Test method'); + }); + + it('should use the correct longname for static methods', function() { + expect(klassStaticTest.description).toBe('Test static method'); }); }); diff --git a/test/specs/documentation/moduleisconstructor.js b/test/specs/documentation/moduleisconstructor.js index 22e9d360..f6409d89 100644 --- a/test/specs/documentation/moduleisconstructor.js +++ b/test/specs/documentation/moduleisconstructor.js @@ -1,37 +1,99 @@ 'use strict'; describe('module that exports a constructor', function() { - var docSet = jasmine.getDocSetFromFile('test/fixtures/moduleisconstructor.js'); - var modules = docSet.doclets.filter(function(doclet) { - return doclet.kind === 'module'; - }); - var classes = docSet.doclets.filter(function(doclet) { - return doclet.kind === 'class'; - }); + describe('pre-ES2015 module', function() { + var docSet = jasmine.getDocSetFromFile('test/fixtures/moduleisconstructor.js'); + var modules = docSet.doclets.filter(function(doclet) { + return doclet.kind === 'module'; + }); + var classes = docSet.doclets.filter(function(doclet) { + return doclet.kind === 'class'; + }); + var getId = docSet.getByLongname('module:mymodule/config#getId')[0]; + var id = docSet.getByLongname('module:mymodule/config#id')[0]; - it('should include one doclet whose kind is "module"', function() { - expect(modules.length).toBe(1); - expect(modules[0].kind).toBe('module'); - }); - - it('should include one doclet whose kind is "class"', function() { - expect(classes.length).toBe(1); - expect(classes[0].kind).toBe('class'); - }); - - describe('class doclet', function() { - it('should include a "description" property that contains the constructor description', function() { - expect(classes[0].description).toEqual('Create a new configuration.'); + it('should include one doclet whose kind is "module"', function() { + expect(modules.length).toBe(1); + expect(modules[0].kind).toBe('module'); }); - it('should include a "class-description" property', function() { - expect(classes[0].classdesc).toEqual('Describe the class here.'); + it('should include one doclet whose kind is "class"', function() { + expect(classes.length).toBe(1); + expect(classes[0].kind).toBe('class'); + }); + + describe('class doclet', function() { + it('should include a "description" property that contains the constructor description', function() { + expect(classes[0].description).toEqual('Create a new configuration.'); + }); + + it('should include a "classdesc" property', function() { + expect(classes[0].classdesc).toEqual('Describe the class here.'); + }); + }); + + describe('module doclet', function() { + it('should include a "description" property that contains the module description', function() { + expect(modules[0].description).toEqual('Describe the module here.'); + }); + }); + + describe('instance members', function() { + it('should use the correct longname for instance properties', function() { + expect(id.description).toBe('Document me.'); + }); + + it('should use the correct longname for instance methods', function() { + expect(getId.description).toBe('Get the configuration ID.'); + }); }); }); - describe('module doclet', function() { - it('should include a "description" property that contains the module description', function() { - expect(modules[0].description).toEqual('Describe the module here.'); + describe('ES2015 module', function() { + var docSet = jasmine.getDocSetFromFile('test/fixtures/moduleisconstructor2.js'); + var modules = docSet.doclets.filter(function(doclet) { + return doclet.kind === 'module'; + }); + var classes = docSet.doclets.filter(function(doclet) { + return doclet.kind === 'class' && doclet.classdesc && doclet.description; + }); + var getId = docSet.getByLongname('module:mymodule/config#getId')[0]; + var id = docSet.getByLongname('module:mymodule/config#id')[0]; + + it('should include one doclet whose kind is "module"', function() { + expect(modules.length).toBe(1); + expect(modules[0].kind).toBe('module'); + }); + + it('should include one complete class doclet', function() { + expect(classes.length).toBe(1); + expect(classes[0].kind).toBe('class'); + }); + + describe('class doclet', function() { + it('should include a "description" property that contains the constructor description', function() { + expect(classes[0].description).toEqual('Create a new configuration.'); + }); + + it('should include a "classdesc" property', function() { + expect(classes[0].classdesc).toEqual('Describe the class here.'); + }); + }); + + describe('module doclet', function() { + it('should include a "description" property that contains the module description', function() { + expect(modules[0].description).toEqual('Describe the module here.'); + }); + }); + + describe('instance members', function() { + it('should use the correct longname for instance properties', function() { + expect(id.description).toBe('Document me.'); + }); + + it('should use the correct longname for instance methods', function() { + expect(getId.description).toBe('Get the configuration ID.'); + }); }); }); });