support modules that export a single non-constructor function (#384)

This commit is contained in:
Jeff Williams 2013-04-06 16:53:20 -07:00
parent 450f3944bc
commit e5be860cc4
7 changed files with 127 additions and 20 deletions

View File

@ -54,6 +54,7 @@ exports.attachTo = function(parser) {
}
});
// TODO: for clarity, decompose into smaller functions
function newSymbolDoclet(docletSrc, e) {
var memberofName = null,
newDoclet = getNewDoclet(parser, docletSrc, e);
@ -132,9 +133,16 @@ exports.attachTo = function(parser) {
}
}
else {
if (currentModule) {
if (!newDoclet.scope){newDoclet.addTag( 'inner');}
if (!newDoclet.memberof && newDoclet.scope !== 'global'){newDoclet.addTag( 'memberof', currentModule);}
// add @inner and @memberof tags unless the current module exports only this symbol
if (currentModule && currentModule !== newDoclet.name) {
// add @inner unless the current module exports only this symbol
if (!newDoclet.scope) {
newDoclet.addTag('inner');
}
if (!newDoclet.memberof && newDoclet.scope !== 'global') {
newDoclet.addTag('memberof', currentModule);
}
}
}
}
@ -147,7 +155,9 @@ exports.attachTo = function(parser) {
//resolveProperties(newDoclet);
if (!newDoclet.memberof) {
// set the scope to global unless a) the doclet is a memberof something or b) the current
// module exports only this symbol
if (!newDoclet.memberof && currentModule !== newDoclet.name) {
newDoclet.scope = 'global';
}

View File

@ -186,6 +186,20 @@ var find = exports.find = function(data, spec) {
return data(spec).get();
};
/**
* Check whether a symbol is a function and is the only symbol exported by a module (as in
* `module.exports = function() {};`).
*
* @private
* @param {module:jsdoc/doclet.Doclet} doclet - The doclet for the symbol.
* @return {boolean} `true` if the symbol is a function and is the only symbol exported by a module;
* otherwise, `false`.
*/
function isModuleFunction(doclet) {
return doclet.longname && doclet.longname === doclet.name &&
doclet.longname.indexOf('module:') === 0 && doclet.kind === 'function';
}
/**
* Retrieve all of the following types of members from a set of doclets:
*
@ -201,7 +215,7 @@ var find = exports.find = function(data, spec) {
* `events`, and `namespaces` properties. Each property contains an array of objects.
*/
exports.getMembers = function(data) {
return {
var members = {
classes: find( data, {kind: 'class'} ),
externals: find( data, {kind: 'external'} ),
events: find( data, {kind: 'event'} ),
@ -213,6 +227,17 @@ exports.getMembers = function(data) {
modules: find( data, {kind: 'module'} ),
namespaces: find( data, {kind: 'namespace'} )
};
// functions that are also modules (as in "module.exports = function() {};") are not globals
members.globals = members.globals.filter(function(doclet) {
if ( isModuleFunction(doclet) ) {
return false;
}
return true;
});
return members;
};
/**
@ -565,16 +590,16 @@ exports.createLink = function(doclet) {
var longname;
var filename;
if (containers.indexOf(doclet.kind) < 0) {
if ( containers.indexOf(doclet.kind) !== -1 || isModuleFunction(doclet) ) {
longname = doclet.longname;
url = getFilename(longname);
}
else {
longname = doclet.longname;
filename = getFilename(doclet.memberof || exports.globalName);
url = filename + '#' + getNamespace(doclet.kind) + doclet.name;
}
else {
longname = doclet.longname;
url = getFilename(longname);
}
return url;
};

View File

@ -149,6 +149,33 @@ function generateSourceFiles(sourceFiles) {
});
}
/**
* Look for classes or functions with the same name as modules (which indicates that the module
* exports only that class or function), then attach the classes or functions to the `module`
* property of the appropriate module doclets. The name of each class or function is also updated
* for display purposes. This function mutates the original arrays.
*
* @private
* @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
* check.
* @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
*/
function attachModuleSymbols(doclets, modules) {
var symbols = {};
// build a lookup table
doclets.forEach(function(symbol) {
symbols[symbol.longname] = symbol;
});
return modules.map(function(module) {
if (symbols[module.longname]) {
module.module = symbols[module.longname];
module.module.name = module.module.name.replace('module:', 'require("') + '")';
}
});
}
/**
* Create the navigation sidebar.
* @param {object} members The members that will be used to create the sidebar.
@ -193,22 +220,14 @@ function buildNav(members) {
}
if (members.classes.length) {
members.classes.forEach(function(c) {
var moduleSameName = find({kind: 'module', longname: c.longname});
if (moduleSameName.length) {
c.name = c.name.replace('module:', 'require("')+'")';
moduleSameName[0].module = c;
}
if ( !hasOwnProp.call(seen, c.longname) ) {
hasClassList = true;
classNav += '<li>'+linkto(c.longname, c.name)+'</li>';
}
seen[c.longname] = true;
});
if (hasClassList) {
if (classNav !== '') {
nav += '<h3>Classes</h3><ul>';
nav += classNav;
nav += '</ul>';
@ -427,6 +446,8 @@ exports.publish = function(taffyData, opts, tutorials) {
// once for all
view.nav = buildNav(members);
attachModuleSymbols( find({ kind: ['class', 'function'], longname: {left: 'module:'} }),
members.modules );
// only output pretty-printed source files if requested; do this before generating any other
// pages, so the other pages can link to the source files

10
test/fixtures/moduleisfunction.js vendored Normal file
View File

@ -0,0 +1,10 @@
/**
* This is a module called foo.
* @module foo
*/
/**
* The module exports a single function.
* @param {string} bar
*/
module.exports = function(bar) {};

View File

@ -0,0 +1,5 @@
/*global describe: true, expect: true, it: true, xdescribe: true */
xdescribe('module that exports a constructor', function() {
// TODO
});

View File

@ -0,0 +1,23 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe('module that exports a function that is not a constructor', function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/moduleisfunction.js');
var functions = docSet.doclets.filter(function(doclet) {
return doclet.kind === 'function';
});
it('should include one doclet whose kind is "function"', function() {
expect(functions.length).toBe(1);
expect(functions[0].kind).toBe('function');
});
describe('function doclet', function() {
it('should not include a "scope" property', function() {
expect(functions[0].scope).not.toBeDefined();
});
it('should not include a "memberof" property', function() {
expect(functions[0].memberof).not.toBeDefined();
});
});
});

View File

@ -379,6 +379,7 @@ describe("jsdoc/util/templateHelper", function() {
{kind: 'constant'}, // global
{kind: 'typedef'}, // global
{kind: 'constant', memberof: 'module:one/two'}, // not global
{kind: 'function', name: 'module:foo', longname: 'module:foo'} // not global
];
var array = classes.concat(externals.concat(events.concat(mixins.concat(modules.concat(namespaces.concat(misc))))));
var data = require('taffydb').taffy(array);
@ -439,7 +440,7 @@ describe("jsdoc/util/templateHelper", function() {
});
it("globals are detected", function() {
compareObjectArrays(misc.slice(0, -1), members.globals);
compareObjectArrays(misc.slice(0, -2), members.globals);
});
});
@ -1259,6 +1260,18 @@ describe("jsdoc/util/templateHelper", function() {
expect(url).toEqual('_.html#"*foo"');
});
it('should create a url for a function that is the only symbol exported by a module.',
function() {
var mockDoclet = {
kind: 'function',
longname: 'module:bar',
name: 'module:bar'
};
var url = helper.createLink(mockDoclet);
expect(url).toEqual('module-bar.html');
});
});
describe("resolveAuthorLinks", function() {