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 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) {
var Doclet = jsdoc.doclet.Doclet;
return false;
}
function createDoclet(comment, e) {
var doclet;
var err;
try {
doclet = new Doclet(comment, e);
doclet = new jsdoc.doclet.Doclet(comment, e);
}
catch (error) {
err = new Error( util.format('cannot create a doclet for the comment "%s": %s',
comment.replace(/[\r\n]/g, ''), error.message) );
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;
@ -55,153 +94,165 @@ function setDefaultScopeMemberOf(doclet) {
}
}
function addDoclet(parser, newDoclet) {
var e;
if (newDoclet) {
setCurrentModule(newDoclet);
e = { doclet: newDoclet };
parser.emit('newDoclet', e);
if ( !e.defaultPrevented && !filterByLongname(e.doclet) ) {
parser.addResult(e.doclet);
}
}
}
function processAlias(parser, doclet, astNode) {
var memberofName;
if (doclet.alias === '{@thisClass}') {
memberofName = parser.resolveThis(astNode);
// "class" refers to the owner of the prototype, not the prototype itself
if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) {
memberofName = RegExp.$1;
}
doclet.alias = memberofName;
}
doclet.addTag('name', doclet.alias);
doclet.postProcess();
}
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;
// or /** @module foo */ module.exports.bar = 1;
// but not /** @module foo */ module.exports = 1;
if ( (nameStartsWith === 'exports' || nameStartsWith === 'module.exports') &&
doclet.name !== 'module.exports' && currentModule ) {
memberof = currentModule;
scopePunc = SCOPE_PUNC.STATIC;
}
else if (doclet.name === 'module.exports' && currentModule) {
doclet.addTag('name', currentModule);
doclet.postProcess();
}
else {
// like /** @module foo */ exports = {bar: 1};
// or /** blah */ this.foo = 1;
memberof = parser.resolveThis(astNode);
scopePunc = (nameStartsWith === 'exports') ?
SCOPE_PUNC.STATIC :
SCOPE_PUNC.INSTANCE;
// like /** @module foo */ this.bar = 1;
if (nameStartsWith === 'this' && currentModule && !memberof) {
memberof = currentModule;
scopePunc = SCOPE_PUNC.STATIC;
}
}
return {
memberof: memberof,
scopePunc: scopePunc
};
}
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;
}
// 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 {
memberofInfo = parser.astnodeToMemberof(astNode);
if( Array.isArray(memberofInfo) ) {
basename = memberofInfo[1];
memberof = memberofInfo[0];
}
else {
memberof = memberofInfo;
}
}
// if we found a memberof name, apply it to the doclet
if (memberof) {
doclet.addTag('memberof', memberof);
if (basename) {
doclet.name = (doclet.name || '')
.replace(new RegExp('^' + escape(basename) + '.'), '');
}
}
// otherwise, use the defaults
else {
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();
}
else {
return false;
}
// 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';
}
addDoclet(parser, newDoclet);
e.doclet = 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;
if (newDoclet) {
setCurrentModule(newDoclet);
e = { doclet: newDoclet };
parser.emit('newDoclet', e);
if ( !e.defaultPrevented && !filter(e.doclet) ) {
parser.addResult(e.doclet);
}
}
}
// TODO: for clarity, decompose into smaller functions
function newSymbolDoclet(docletSrc, e) {
var memberofName = null,
newDoclet = getNewDoclet(docletSrc, e);
// 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 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
if ( /^(.+?)(\.prototype|#)$/.test(memberofName) ) {
memberofName = RegExp.$1;
}
newDoclet.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
// `module.exports`, which identifies the module object itself)
if (newDoclet.name !== 'module.exports') {
newDoclet.name = newDoclet.name.replace(moduleRegExp, '');
}
// like /** @module foo */ exports.bar = 1;
// or /** @module foo */ module.exports.bar = 1;
// but not /** @module foo */ module.exports = 1;
if ( (nameStartsWith === 'exports' || nameStartsWith === 'module.exports') &&
newDoclet.name !== 'module.exports' && currentModule ) {
memberofName = currentModule;
scope = 'static';
}
else if (newDoclet.name === 'module.exports' && currentModule) {
newDoclet.addTag('name', currentModule);
newDoclet.postProcess();
}
else {
// like /** @module foo */ exports = {bar: 1};
// or /** blah */ this.foo = 1;
memberofName = parser.resolveThis(e.astnode);
scope = nameStartsWith === 'exports' ? 'static' : 'instance';
// like /** @module foo */ this.bar = 1;
if (nameStartsWith === 'this' && currentModule && !memberofName) {
memberofName = currentModule;
scope = 'static';
}
}
if (memberofName) {
if (newDoclet.name) {
newDoclet.name = memberofName + (scope === 'instance' ? '#' : '.') +
newDoclet.name;
}
else { newDoclet.name = memberofName; }
}
}
else {
memberofName = parser.astnodeToMemberof(e.astnode);
if( Array.isArray(memberofName) ) {
basename = memberofName[1];
memberofName = memberofName[0];
}
}
if (memberofName) {
newDoclet.addTag('memberof', memberofName);
if (basename) {
newDoclet.name = (newDoclet.name || '')
.replace(new RegExp('^' + escape(basename) + '.'), '');
}
}
else {
setDefaultScopeMemberOf(newDoclet);
}
}
newDoclet.postProcess();
}
else {
return false;
}
// 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';
}
addDoclet.call(parser, newDoclet);
e.doclet = newDoclet;
}
// Handle JSDoc "virtual comments" that include one of the following:
//
// + A `@name` tag
@ -211,7 +262,7 @@ exports.attachTo = function(parser) {
var newDoclet;
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
if (!newDoclet.name) {
@ -220,7 +271,7 @@ exports.attachTo = function(parser) {
setDefaultScopeMemberOf(newDoclet);
newDoclet.postProcess();
addDoclet.call(parser, newDoclet);
addDoclet(parser, newDoclet);
e.doclet = newDoclet;
}
@ -231,7 +282,7 @@ exports.attachTo = function(parser) {
var comments = e.comment.split(/@also\b/g);
for (var i = 0, l = comments.length; i < l; i++) {
newSymbolDoclet.call(parser, comments[i], e);
newSymbolDoclet(parser, comments[i], e);
}
});