This commit is contained in:
Jeff Williams 2014-08-14 14:20:25 -07:00
parent 17fb5be064
commit 9be79c198e

View File

@ -14,22 +14,61 @@ var jsdoc = {
var util = require('util'); var util = require('util');
var currentModule = null; var currentModule = null;
var SCOPE_PUNC = jsdoc.name.SCOPE.PUNC;
var unresolvedName = /^((?:module.)?exports|this)(\.|$)/;
var moduleRegExp = /^((?:module.)?exports|this)(\.|$)/; function filterByLongname(doclet) {
// you can't document prototypes
if ( /#$/.test(doclet.longname) ) {
return true;
}
function getNewDoclet(comment, e) { return false;
var Doclet = jsdoc.doclet.Doclet; }
function createDoclet(comment, e) {
var doclet; var doclet;
var err; var err;
try { try {
doclet = new Doclet(comment, e); doclet = new jsdoc.doclet.Doclet(comment, e);
} }
catch (error) { catch (error) {
err = new Error( util.format('cannot create a doclet for the comment "%s": %s', err = new Error( util.format('cannot create a doclet for the comment "%s": %s',
comment.replace(/[\r\n]/g, ''), error.message) ); comment.replace(/[\r\n]/g, ''), error.message) );
jsdoc.util.logger.error(err); jsdoc.util.logger.error(err);
doclet = new Doclet('', e); doclet = new jsdoc.doclet.Doclet('', e);
}
return doclet;
}
/**
* Create a doclet for a `symbolFound` event. The doclet represents an actual symbol that is defined
* in the code.
*
* Here's why this function is useful. A JSDoc comment can define a symbol name by including:
*
* + A `@name` tag
* + Another tag that accepts a name, such as `@function`
*
* When the JSDoc comment defines a symbol name, we treat it as a "virtual comment" for a symbol
* that isn't actually present in the code. And if a virtual comment is attached to a symbol, it's
* possible that the comment and symbol have nothing to do with one another.
*
* To handle this case, this function checks the new doclet to see if we've already added a name
* property by parsing the JSDoc comment. If so, this method creates a replacement doclet that
* ignores the attached JSDoc comment and only looks at the code.
*
* @private
*/
function createSymbolDoclet(comment, e) {
var doclet = createDoclet(comment, e);
if (doclet.name) {
// try again, without the comment
e.comment = '@undocumented';
doclet = createDoclet(e.comment, e);
} }
return doclet; return doclet;
@ -55,135 +94,142 @@ function setDefaultScopeMemberOf(doclet) {
} }
} }
/** function addDoclet(parser, newDoclet) {
* Attach these event handlers to a particular instance of a parser.
* @param parser
*/
exports.attachTo = function(parser) {
function filter(doclet) {
// you can't document prototypes
if ( /#$/.test(doclet.longname) ) {
return true;
}
return false;
}
function addDoclet(newDoclet) {
var e; var e;
if (newDoclet) { if (newDoclet) {
setCurrentModule(newDoclet); setCurrentModule(newDoclet);
e = { doclet: newDoclet }; e = { doclet: newDoclet };
parser.emit('newDoclet', e); parser.emit('newDoclet', e);
if ( !e.defaultPrevented && !filter(e.doclet) ) { if ( !e.defaultPrevented && !filterByLongname(e.doclet) ) {
parser.addResult(e.doclet); parser.addResult(e.doclet);
} }
} }
} }
// TODO: for clarity, decompose into smaller functions function processAlias(parser, doclet, astNode) {
function newSymbolDoclet(docletSrc, e) { var memberofName;
var memberofName = null,
newDoclet = getNewDoclet(docletSrc, e);
// A JSDoc comment can define a symbol name by including: if (doclet.alias === '{@thisClass}') {
// memberofName = parser.resolveThis(astNode);
// + A `@name` tag
// + Another tag that accepts a name, such as `@function`
//
// When the JSDoc comment defines a symbol name, we treat it as a "virtual comment" for a
// symbol that isn't actually present in the code. And if a virtual comment is attached to
// a symbol, it's quite possible that the comment and symbol have nothing to do with one
// another.
//
// As a result, if we create a doclet for a `symbolFound` event, and we've already added a
// name attribute by parsing the JSDoc comment, we need to create a new doclet that ignores
// the attached JSDoc comment and only looks at the code.
if (newDoclet.name) {
// try again, without the comment
e.comment = '@undocumented';
newDoclet = getNewDoclet(e.comment, e);
}
if (newDoclet.alias) {
if (newDoclet.alias === '{@thisClass}') {
memberofName = parser.resolveThis(e.astnode);
// "class" refers to the owner of the prototype, not the prototype itself // "class" refers to the owner of the prototype, not the prototype itself
if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) { if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) {
memberofName = RegExp.$1; memberofName = RegExp.$1;
} }
newDoclet.alias = memberofName; doclet.alias = memberofName;
} }
newDoclet.addTag('name', newDoclet.alias);
newDoclet.postProcess();
}
else if (e.code && e.code.name) { // we need to get the symbol name from code
newDoclet.addTag('name', e.code.name);
if (!newDoclet.memberof && e.astnode) {
var basename = null,
scope = '';
if ( moduleRegExp.test(newDoclet.name) ) {
var nameStartsWith = RegExp.$1;
// remove stuff that indicates module membership (but don't touch the name doclet.addTag('name', doclet.alias);
// `module.exports`, which identifies the module object itself) doclet.postProcess();
if (newDoclet.name !== 'module.exports') { }
newDoclet.name = newDoclet.name.replace(moduleRegExp, '');
function findModuleMemberof(parser, doclet, astNode, nameStartsWith) {
var memberof = '';
var scopePunc = '';
// remove stuff that indicates module membership (but don't touch the name `module.exports`,
// which identifies the module object itself)
if (doclet.name !== 'module.exports') {
doclet.name = doclet.name.replace(unresolvedName, '');
} }
// like /** @module foo */ exports.bar = 1; // like /** @module foo */ exports.bar = 1;
// or /** @module foo */ module.exports.bar = 1; // or /** @module foo */ module.exports.bar = 1;
// but not /** @module foo */ module.exports = 1; // but not /** @module foo */ module.exports = 1;
if ( (nameStartsWith === 'exports' || nameStartsWith === 'module.exports') && if ( (nameStartsWith === 'exports' || nameStartsWith === 'module.exports') &&
newDoclet.name !== 'module.exports' && currentModule ) { doclet.name !== 'module.exports' && currentModule ) {
memberofName = currentModule; memberof = currentModule;
scope = 'static'; scopePunc = SCOPE_PUNC.STATIC;
} }
else if (newDoclet.name === 'module.exports' && currentModule) { else if (doclet.name === 'module.exports' && currentModule) {
newDoclet.addTag('name', currentModule); doclet.addTag('name', currentModule);
newDoclet.postProcess(); doclet.postProcess();
} }
else { else {
// like /** @module foo */ exports = {bar: 1}; // like /** @module foo */ exports = {bar: 1};
// or /** blah */ this.foo = 1; // or /** blah */ this.foo = 1;
memberofName = parser.resolveThis(e.astnode); memberof = parser.resolveThis(astNode);
scope = nameStartsWith === 'exports' ? 'static' : 'instance'; scopePunc = (nameStartsWith === 'exports') ?
SCOPE_PUNC.STATIC :
SCOPE_PUNC.INSTANCE;
// like /** @module foo */ this.bar = 1; // like /** @module foo */ this.bar = 1;
if (nameStartsWith === 'this' && currentModule && !memberofName) { if (nameStartsWith === 'this' && currentModule && !memberof) {
memberofName = currentModule; memberof = currentModule;
scope = 'static'; scopePunc = SCOPE_PUNC.STATIC;
} }
} }
if (memberofName) { return {
if (newDoclet.name) { memberof: memberof,
newDoclet.name = memberofName + (scope === 'instance' ? '#' : '.') + scopePunc: scopePunc
newDoclet.name; };
}
function addSymbolMemberof(parser, doclet, astNode) {
var basename;
var memberof;
var memberofInfo;
var scopePunc;
var unresolved;
// TODO: is this the correct behavior, given that we don't always use the AST node?
if (!astNode) {
return;
} }
else { newDoclet.name = memberofName; }
// check to see if the doclet name is an unresolved reference to the module wrapper
unresolved = unresolvedName.exec(doclet.name);
if (unresolved) {
memberofInfo = findModuleMemberof(parser, doclet, astNode, unresolved[1]);
memberof = memberofInfo.memberof;
scopePunc = memberofInfo.scopePunc;
if (memberof) {
doclet.name = doclet.name ?
memberof + scopePunc + doclet.name :
memberof;
} }
} }
else { else {
memberofName = parser.astnodeToMemberof(e.astnode); memberofInfo = parser.astnodeToMemberof(astNode);
if( Array.isArray(memberofName) ) { if( Array.isArray(memberofInfo) ) {
basename = memberofName[1]; basename = memberofInfo[1];
memberofName = memberofName[0]; memberof = memberofInfo[0];
}
else {
memberof = memberofInfo;
} }
} }
if (memberofName) { // if we found a memberof name, apply it to the doclet
newDoclet.addTag('memberof', memberofName); if (memberof) {
doclet.addTag('memberof', memberof);
if (basename) { if (basename) {
newDoclet.name = (newDoclet.name || '') doclet.name = (doclet.name || '')
.replace(new RegExp('^' + escape(basename) + '.'), ''); .replace(new RegExp('^' + escape(basename) + '.'), '');
} }
} }
// otherwise, use the defaults
else { else {
setDefaultScopeMemberOf(newDoclet); setDefaultScopeMemberOf(doclet);
} }
}
function newSymbolDoclet(parser, docletSrc, e) {
var memberofName = null;
var newDoclet = createSymbolDoclet(docletSrc, e);
// if there's an alias, use that as the symbol name
if (newDoclet.alias) {
processAlias(parser, newDoclet, e.astnode);
}
// otherwise, get the symbol name from the code
else if (e.code && e.code.name) {
newDoclet.addTag('name', e.code.name);
if (!newDoclet.memberof) {
addSymbolMemberof(parser, newDoclet, e.astnode);
} }
newDoclet.postProcess(); newDoclet.postProcess();
@ -198,10 +244,15 @@ exports.attachTo = function(parser) {
newDoclet.scope = 'global'; newDoclet.scope = 'global';
} }
addDoclet.call(parser, newDoclet); addDoclet(parser, newDoclet);
e.doclet = newDoclet; e.doclet = newDoclet;
} }
/**
* Attach these event handlers to a particular instance of a parser.
* @param parser
*/
exports.attachTo = function(parser) {
// Handle JSDoc "virtual comments" that include one of the following: // Handle JSDoc "virtual comments" that include one of the following:
// //
// + A `@name` tag // + A `@name` tag
@ -211,7 +262,7 @@ exports.attachTo = function(parser) {
var newDoclet; var newDoclet;
for (var i = 0, l = comments.length; i < l; i++) { for (var i = 0, l = comments.length; i < l; i++) {
newDoclet = getNewDoclet(comments[i], e); newDoclet = createDoclet(comments[i], e);
// we're only interested in virtual comments here // we're only interested in virtual comments here
if (!newDoclet.name) { if (!newDoclet.name) {
@ -220,7 +271,7 @@ exports.attachTo = function(parser) {
setDefaultScopeMemberOf(newDoclet); setDefaultScopeMemberOf(newDoclet);
newDoclet.postProcess(); newDoclet.postProcess();
addDoclet.call(parser, newDoclet); addDoclet(parser, newDoclet);
e.doclet = newDoclet; e.doclet = newDoclet;
} }
@ -231,7 +282,7 @@ exports.attachTo = function(parser) {
var comments = e.comment.split(/@also\b/g); var comments = e.comment.split(/@also\b/g);
for (var i = 0, l = comments.length; i < l; i++) { for (var i = 0, l = comments.length; i < l; i++) {
newSymbolDoclet.call(parser, comments[i], e); newSymbolDoclet(parser, comments[i], e);
} }
}); });