mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
The processingComplete event fires after all processing has been done. It gets the entire docs as its only parameter.
783 lines
22 KiB
JavaScript
783 lines
22 KiB
JavaScript
/*global env: true, Packages: true */
|
|
/**
|
|
* @module jsdoc/src/parser
|
|
* @requires fs
|
|
* @requires events
|
|
*/
|
|
|
|
var Token = Packages.org.mozilla.javascript.Token;
|
|
var hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
|
/**
|
|
* @class
|
|
* @mixes module:events
|
|
*
|
|
* @example <caption>Create a new parser.</caption>
|
|
* var jsdocParser = new (require('jsdoc/src/parser').Parser)();
|
|
*/
|
|
exports.Parser = function() {
|
|
this._currentSourceName = '';
|
|
this._resultBuffer = [];
|
|
this._comments = {
|
|
original: [],
|
|
modified: []
|
|
};
|
|
//Initialize a global ref to store global members
|
|
this.refs = {
|
|
__global__: {
|
|
meta: {}
|
|
}
|
|
};
|
|
this._visitors = [];
|
|
};
|
|
exports.Parser.prototype = Object.create( require('events').EventEmitter.prototype );
|
|
|
|
/**
|
|
* Parse the given source files for JSDoc comments.
|
|
* @param {Array.<string>} sourceFiles An array of filepaths to the JavaScript sources.
|
|
* @param {string} [encoding=utf8]
|
|
*
|
|
* @fires jsdocCommentFound
|
|
* @fires symbolFound
|
|
* @fires newDoclet
|
|
* @fires fileBegin
|
|
* @fires fileComplete
|
|
*
|
|
* @example <caption>Parse two source files.</caption>
|
|
* var myFiles = ['file1.js', 'file2.js'];
|
|
* var docs = jsdocParser.parse(myFiles);
|
|
*/
|
|
exports.Parser.prototype.parse = function(sourceFiles, encoding) {
|
|
encoding = encoding || env.conf.encoding || 'utf8';
|
|
|
|
const SCHEMA = 'javascript:';
|
|
|
|
var filename = '';
|
|
var sourceCode = '';
|
|
var parsedFiles = [];
|
|
var e = {};
|
|
|
|
if (typeof sourceFiles === 'string') {
|
|
sourceFiles = [sourceFiles];
|
|
}
|
|
|
|
e.sourcefiles = sourceFiles;
|
|
|
|
this.emit('parseBegin', e);
|
|
|
|
for (var i = 0, l = sourceFiles.length; i < l; i++) {
|
|
sourceCode = '';
|
|
|
|
if (sourceFiles[i].indexOf(SCHEMA) === 0) {
|
|
sourceCode = sourceFiles[i].substr(SCHEMA.length);
|
|
filename = '[[string' + i + ']]';
|
|
}
|
|
else {
|
|
filename = sourceFiles[i];
|
|
try {
|
|
sourceCode = require('jsdoc/fs').readFileSync(filename, encoding);
|
|
}
|
|
catch(e) {
|
|
console.log('FILE READ ERROR: in module:jsdoc/parser.parseFiles: "' + filename +
|
|
'" ' + e);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (sourceCode.length) {
|
|
this._parseSourceCode(sourceCode, filename);
|
|
parsedFiles.push(filename);
|
|
}
|
|
}
|
|
|
|
this.emit('parseComplete', {
|
|
sourcefiles: parsedFiles
|
|
});
|
|
|
|
return this._resultBuffer;
|
|
};
|
|
|
|
exports.Parser.prototype.fireProcessingComplete = function(docs) {
|
|
this.emit('processingComplete', docs);
|
|
};
|
|
|
|
/**
|
|
* @returns {Array<Doclet>} The accumulated results of any calls to parse.
|
|
*/
|
|
exports.Parser.prototype.results = function() {
|
|
return this._resultBuffer;
|
|
};
|
|
|
|
/**
|
|
* @param {Object} o The parse result to add to the result buffer.
|
|
*/
|
|
exports.Parser.prototype.addResult = function(o) {
|
|
this._resultBuffer.push(o);
|
|
};
|
|
|
|
/**
|
|
* Empty any accumulated results of calls to parse.
|
|
*/
|
|
exports.Parser.prototype.clear = function() {
|
|
this._currentSourceName = '';
|
|
this._resultBuffer = [];
|
|
this._comments = {
|
|
original: [],
|
|
modified: []
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Adds a node visitor to use in parsing
|
|
*/
|
|
exports.Parser.prototype.addNodeVisitor = function(visitor) {
|
|
this._visitors.push(visitor);
|
|
};
|
|
|
|
/**
|
|
* Get the node visitors used in parsing
|
|
*/
|
|
exports.Parser.prototype.getVisitors = function() {
|
|
return this._visitors;
|
|
};
|
|
|
|
function pretreat(code) {
|
|
return code
|
|
// make starbangstar comments look like real jsdoc comments
|
|
.replace(/\/\*\!\*/g, '/**')
|
|
|
|
// merge adjacent doclets
|
|
.replace(/\*\/\/\*\*+/g, '@also')
|
|
// make lent object literals documentable by giving them a dummy name
|
|
// like return @lends {
|
|
.replace(/(\/\*\*[^\*\/]*?[\*\s]*@lends\s(?:[^\*]|\*(?!\/))*\*\/\s*)\{/g, '$1 ____ = {')
|
|
// like @lends return {
|
|
.replace(/(\/\*\*[^\*\/]*?@lends\b[^\*\/]*?\*\/)(\s*)return(\s*)\{/g,
|
|
'$2$3 return $1 ____ = {');
|
|
}
|
|
|
|
var tkn = {
|
|
NAMEDFUNCTIONSTATEMENT: -1001
|
|
};
|
|
exports.Parser.tkn = tkn;
|
|
|
|
/** @private */
|
|
function parserFactory() {
|
|
var cx = Packages.org.mozilla.javascript.Context.getCurrentContext();
|
|
|
|
var ce = new Packages.org.mozilla.javascript.CompilerEnvirons();
|
|
ce.setRecordingComments(true);
|
|
ce.setRecordingLocalJsDocComments(true);
|
|
ce.setLanguageVersion(180);
|
|
|
|
ce.initFromContext(cx);
|
|
return new Packages.org.mozilla.javascript.Parser(ce, ce.getErrorReporter());
|
|
}
|
|
|
|
/** @private
|
|
@memberof module:src/parser.Parser
|
|
*/
|
|
function getTypeName(node) {
|
|
var type = '';
|
|
|
|
if (node) {
|
|
type = '' + Packages.org.mozilla.javascript.Token.typeToName(node.getType());
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/** @private
|
|
@memberof module:src/parser.Parser
|
|
*/
|
|
function nodeToString(node) {
|
|
var str;
|
|
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
if (node.type === Token.GETPROP) {
|
|
str = [nodeToString(node.target), node.property.string].join('.');
|
|
}
|
|
else if (node.type === Token.VAR) {
|
|
str = nodeToString(node.target);
|
|
}
|
|
else if (node.type === Token.NAME) {
|
|
str = node.string;
|
|
}
|
|
else if (node.type === Token.STRING) {
|
|
str = node.value;
|
|
}
|
|
else if (node.type === Token.NUMBER) {
|
|
str = node.value;
|
|
}
|
|
else if (node.type === Token.THIS) {
|
|
str = 'this';
|
|
}
|
|
else if (node.type === Token.GETELEM) {
|
|
str = node.toSource(); // like: Foo['Bar']
|
|
}
|
|
else if (node.type === Token.NEG || node.type === Token.TRUE || node.type === Token.FALSE) {
|
|
str = node.toSource(); // like -1
|
|
}
|
|
else {
|
|
str = getTypeName(node);
|
|
}
|
|
|
|
return '' + str;
|
|
}
|
|
|
|
/**
|
|
* Attempts to find the name and type of the given node.
|
|
* @private
|
|
* @memberof module:src/parser.Parser
|
|
*/
|
|
function aboutNode(node) {
|
|
var about = {};
|
|
|
|
if (node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) {
|
|
about.name = node.type == tkn.NAMEDFUNCTIONSTATEMENT? '' : '' + node.name;
|
|
about.type = 'function';
|
|
about.node = node;
|
|
}
|
|
else if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
|
|
about.name = nodeToString(node.target);
|
|
if (node.initializer) { // like var i = 0;
|
|
about.node = node.initializer;
|
|
about.value = nodeToString(about.node);
|
|
about.type = getTypeName(node.initializer);
|
|
if (about.type === 'FUNCTION' && about.node.name) {
|
|
about.node.type = tkn.NAMEDFUNCTIONSTATEMENT;
|
|
}
|
|
}
|
|
else { // like var i;
|
|
about.node = node.target;
|
|
about.value = nodeToString(about.node);
|
|
about.type = 'undefined';
|
|
}
|
|
}
|
|
else if (node.type === Token.ASSIGN || node.type === Token.COLON ||
|
|
node.type === Token.GET || node.type === Token.SET) {
|
|
about.name = nodeToString(node.left);
|
|
if (node.type === Token.COLON) {
|
|
|
|
// objlit keys with unsafe variable-name characters must be quoted
|
|
if (!/^[$_a-z][$_a-z0-9]*$/i.test(about.name) ) {
|
|
about.name = '"'+about.name.replace(/"/g, '\\"')+'"';
|
|
}
|
|
}
|
|
about.node = node.right;
|
|
about.value = nodeToString(about.node);
|
|
|
|
// Getter and setter functions should be treated as properties
|
|
if (node.type === Token.GET || node.type === Token.SET) {
|
|
about.type = getTypeName(node);
|
|
} else {
|
|
about.type = getTypeName(node.right);
|
|
}
|
|
|
|
if (about.type === 'FUNCTION' && about.node.name) {
|
|
about.node.type = tkn.NAMEDFUNCTIONSTATEMENT;
|
|
}
|
|
}
|
|
else if (node.type === Token.GETPROP) {
|
|
about.node = node;
|
|
about.name = nodeToString(about.node);
|
|
about.type = getTypeName(node);
|
|
}
|
|
else {
|
|
// type 39 (NAME)
|
|
var string = nodeToString(node);
|
|
if (string) {
|
|
about.name = string;
|
|
}
|
|
}
|
|
|
|
// get names of the formal parameters declared for this function
|
|
if (about.node && about.node.getParamCount) {
|
|
var paramCount = about.node.getParamCount();
|
|
if (typeof paramCount === 'number') {
|
|
about.node.flattenSymbolTable(true);
|
|
var paramNames = [];
|
|
for (var i = 0, l = paramCount; i < l; i++) {
|
|
paramNames.push( '' + about.node.getParamOrVarName(i) );
|
|
}
|
|
about.paramnames = paramNames;
|
|
}
|
|
}
|
|
|
|
return about;
|
|
}
|
|
|
|
/** @private
|
|
@memberof module:src/parser.Parser
|
|
*/
|
|
function isValidJsdoc(commentSrc) {
|
|
/*** ignore comments that start with many stars ***/
|
|
return commentSrc && commentSrc.indexOf('/***') !== 0;
|
|
}
|
|
|
|
/** @private
|
|
* @memberof module:src/parser.Parser
|
|
*/
|
|
function makeVarsFinisher(funcDoc) {
|
|
return function(e) {
|
|
//no need to evaluate all things related to funcDoc again, just use it
|
|
if (funcDoc && e.doclet && e.doclet.alias) {
|
|
funcDoc.meta.vars[e.code.name] = e.doclet.longname;
|
|
}
|
|
};
|
|
}
|
|
|
|
/** @private
|
|
* @memberof module:src/parser.Parser
|
|
* @param {string} name Full symbol name.
|
|
* @return {string} Basename.
|
|
*/
|
|
function getBasename(name) {
|
|
if (name !== undefined) {
|
|
return name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/** @private
|
|
* @memberof module:src/parser.Parser
|
|
* @param {object} node
|
|
* @return {Array.<number>} Start and end lines.
|
|
*/
|
|
function getRange(node) {
|
|
var range = [];
|
|
|
|
range[0] = parseInt(String(node.getAbsolutePosition()), 10);
|
|
range[1] = range[0] + parseInt(String(node.getLength()), 10);
|
|
|
|
return range;
|
|
}
|
|
|
|
/** @private
|
|
* @memberof module:src/parser.Parser
|
|
*/
|
|
exports.Parser.prototype._makeEvent = function(node, extras) {
|
|
extras = extras || {};
|
|
|
|
// fill in default values as needed. if we're overriding a property, don't execute the default
|
|
// code for that property, since it might blow up.
|
|
var result = {
|
|
id: extras.id || 'astnode' + node.hashCode(),
|
|
comment: extras.comment || String(node.getJsDoc() || '@undocumented'),
|
|
lineno: extras.lineno || node.left.getLineno(),
|
|
range: extras.range || getRange(node),
|
|
filename: extras.filename || this._currentSourceName,
|
|
astnode: extras.astnode || node,
|
|
code: extras.code || aboutNode(node),
|
|
event: extras.event || 'symbolFound',
|
|
finishers: extras.finishers || [this.addDocletRef]
|
|
};
|
|
|
|
// use the modified version of the comment
|
|
var idx = this._comments.original.indexOf(result.comment);
|
|
if (idx !== -1) {
|
|
result.comment = this._comments.modified[idx];
|
|
}
|
|
|
|
// make sure the result includes extras that don't have default values
|
|
Object.keys(extras).forEach(function(prop) {
|
|
result[prop] = extras[prop];
|
|
});
|
|
|
|
return result;
|
|
};
|
|
|
|
/** @private
|
|
* @memberof module:src/parser.Parser
|
|
*/
|
|
exports.Parser.prototype._trackVars = function(node, e) {
|
|
// keep track of vars in a function or global scope
|
|
var func = '__global__';
|
|
var funcDoc = null;
|
|
|
|
if (node.enclosingFunction) {
|
|
func = 'astnode' + node.enclosingFunction.hashCode();
|
|
}
|
|
|
|
funcDoc = this.refs[func];
|
|
if (funcDoc) {
|
|
funcDoc.meta.vars = funcDoc.meta.vars || {};
|
|
funcDoc.meta.vars[e.code.name] = false;
|
|
e.finishers.push(makeVarsFinisher(funcDoc));
|
|
}
|
|
};
|
|
|
|
/** @private */
|
|
exports.Parser.prototype._visitComment = function(comment) {
|
|
var e;
|
|
var original = String( comment.toSource() );
|
|
var modified;
|
|
|
|
if ( original && isValidJsdoc(original) ) {
|
|
this._comments.original.push(original);
|
|
|
|
e = {
|
|
comment: original,
|
|
lineno: comment.getLineno(),
|
|
filename: this._currentSourceName,
|
|
range: getRange(comment)
|
|
};
|
|
|
|
this.emit('jsdocCommentFound', e, this);
|
|
|
|
if (e.comment !== original) {
|
|
modified = e.comment;
|
|
}
|
|
|
|
this._comments.modified.push(modified || original);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/** @private */
|
|
exports.Parser.prototype._visitNode = function(node) {
|
|
var e,
|
|
extras,
|
|
basename,
|
|
func,
|
|
funcDoc,
|
|
i,
|
|
l;
|
|
|
|
if (node.type === Token.ASSIGN) {
|
|
e = this._makeEvent(node);
|
|
|
|
basename = getBasename(e.code.name);
|
|
|
|
if (basename !== 'this') {
|
|
e.code.funcscope = this.resolveVar(node, basename);
|
|
}
|
|
}
|
|
else if (node.type === Token.COLON) { // assignment within an object literal
|
|
extras = {
|
|
comment: String(node.left.getJsDoc() || '@undocumented'),
|
|
finishers: [this.addDocletRef, this.resolveEnum]
|
|
};
|
|
e = this._makeEvent(node, extras);
|
|
}
|
|
else if (node.type === Token.GET || node.type === Token.SET) { // assignment within an object literal
|
|
extras = {
|
|
comment: String(node.left.getJsDoc() || '@undocumented')
|
|
};
|
|
e = this._makeEvent(node, extras);
|
|
}
|
|
else if (node.type === Token.GETPROP) { // like 'obj.prop' in '/** @typedef {string} */ obj.prop;'
|
|
// this COULD be a Closure Compiler-style typedef, but it's probably not; to avoid filling
|
|
// the parse results with junk, only fire an event if there's a JSDoc comment attached
|
|
extras = {
|
|
lineno: node.getLineno()
|
|
};
|
|
if ( node.getJsDoc() ) {
|
|
e = this._makeEvent(node, extras);
|
|
}
|
|
}
|
|
else if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
|
|
|
|
if (node.variables) {
|
|
return true; // we'll get each var separately on future visits
|
|
}
|
|
|
|
if (node.parent.variables.toArray()[0] === node) { // like /** blah */ var a=1, b=2, c=3;
|
|
// the first var assignment gets any jsDoc before the whole var series
|
|
if (typeof node.setJsDoc !== 'undefined') { node.setJsDoc( node.parent.getJsDoc() ); }
|
|
}
|
|
|
|
extras = {
|
|
lineno: node.getLineno()
|
|
};
|
|
e = this._makeEvent(node, extras);
|
|
|
|
this._trackVars(node, e);
|
|
}
|
|
else if (node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) {
|
|
extras = {
|
|
lineno: node.getLineno()
|
|
};
|
|
e = this._makeEvent(node, extras);
|
|
|
|
e.code.name = (node.type == tkn.NAMEDFUNCTIONSTATEMENT)? '' : String(node.name) || '';
|
|
|
|
this._trackVars(node, e);
|
|
|
|
basename = getBasename(e.code.name);
|
|
e.code.funcscope = this.resolveVar(node, basename);
|
|
}
|
|
|
|
if (!e) {
|
|
e = {
|
|
finishers: []
|
|
};
|
|
}
|
|
|
|
for (i = 0, l = this._visitors.length; i < l; i++) {
|
|
this._visitors[i].visitNode(node, e, this, this._currentSourceName);
|
|
if (e.stopPropagation) { break; }
|
|
}
|
|
|
|
if (!e.preventDefault && isValidJsdoc(e.comment)) {
|
|
this.emit(e.event, e, this);
|
|
}
|
|
|
|
for (i = 0, l = e.finishers.length; i < l; i++) {
|
|
e.finishers[i].call(this, e);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/** @private */
|
|
exports.Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
|
|
var NodeVisitor = Packages.org.mozilla.javascript.ast.NodeVisitor;
|
|
|
|
var ast;
|
|
var e = {
|
|
filename: sourceName
|
|
};
|
|
|
|
this.emit('fileBegin', e);
|
|
|
|
if (!e.defaultPrevented) {
|
|
e = {
|
|
filename: sourceName,
|
|
source: sourceCode
|
|
};
|
|
this.emit('beforeParse', e);
|
|
sourceCode = e.source;
|
|
this._currentSourceName = sourceName = e.filename;
|
|
|
|
sourceCode = pretreat(e.source);
|
|
|
|
ast = parserFactory().parse(sourceCode, sourceName, 1);
|
|
|
|
ast.visitComments(
|
|
new NodeVisitor({
|
|
visit: this._visitComment.bind(this)
|
|
})
|
|
);
|
|
|
|
ast.visit(
|
|
new NodeVisitor({
|
|
visit: this._visitNode.bind(this)
|
|
})
|
|
);
|
|
}
|
|
|
|
this.emit('fileComplete', e);
|
|
|
|
this._currentSourceName = '';
|
|
};
|
|
|
|
/**
|
|
* Given a node, determine what the node is a member of.
|
|
* @param {astnode} node
|
|
* @returns {string} The long name of the node that this is a member of.
|
|
*/
|
|
exports.Parser.prototype.astnodeToMemberof = function(node) {
|
|
var id,
|
|
doclet,
|
|
alias;
|
|
|
|
if (node.type === Token.VAR || node.type === Token.FUNCTION ||
|
|
node.type == tkn.NAMEDFUNCTIONSTATEMENT) {
|
|
if (node.enclosingFunction) { // an inner var or func
|
|
id = 'astnode' + node.enclosingFunction.hashCode();
|
|
doclet = this.refs[id];
|
|
if (!doclet) {
|
|
return '<anonymous>~';
|
|
}
|
|
return (doclet.longname || doclet.name) + '~';
|
|
}
|
|
}
|
|
else {
|
|
// check local references for aliases
|
|
var scope = node,
|
|
basename = getBasename(nodeToString(node.left));
|
|
while(scope.enclosingFunction) {
|
|
id = 'astnode' + scope.enclosingFunction.hashCode();
|
|
doclet = this.refs[id];
|
|
if ( doclet && doclet.meta.vars && hasOwnProp.call(doclet.meta.vars, basename) ) {
|
|
return [doclet.meta.vars[basename], basename];
|
|
}
|
|
// move up
|
|
scope = scope.enclosingFunction;
|
|
}
|
|
// First check to see if we have a global scope alias
|
|
doclet = this.refs.__global__;
|
|
if ( doclet && doclet.meta.vars && hasOwnProp.call(doclet.meta.vars, basename) ) {
|
|
return [doclet.meta.vars[basename], basename];
|
|
}
|
|
|
|
id = 'astnode' + node.parent.hashCode();
|
|
doclet = this.refs[id];
|
|
if (!doclet) {
|
|
return ''; // global?
|
|
}
|
|
return doclet.longname || doclet.name;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Resolve what "this" refers to relative to a node.
|
|
* @param {astnode} node - The "this" node
|
|
* @returns {string} The longname of the enclosing node.
|
|
*/
|
|
exports.Parser.prototype.resolveThis = function(node) {
|
|
var memberof = {};
|
|
var parent;
|
|
|
|
if (node.type !== Token.COLON && node.enclosingFunction) {
|
|
// get documentation for the enclosing function
|
|
memberof.id = 'astnode' + node.enclosingFunction.hashCode();
|
|
memberof.doclet = this.refs[memberof.id];
|
|
|
|
if (!memberof.doclet) {
|
|
return '<anonymous>'; // TODO handle global this?
|
|
}
|
|
|
|
if (memberof.doclet['this']) {
|
|
return memberof.doclet['this'];
|
|
}
|
|
// like: Foo.constructor = function(n) { /** blah */ this.name = n; }
|
|
else if (memberof.doclet.kind === 'function' && memberof.doclet.memberof) {
|
|
return memberof.doclet.memberof;
|
|
}
|
|
// walk up to the closest class we can find
|
|
else if (memberof.doclet.kind === 'class' || memberof.doclet.kind === 'module') {
|
|
return memberof.doclet.longname || memberof.doclet.name;
|
|
}
|
|
else {
|
|
if (node.enclosingFunction){
|
|
// memberof.doclet.meta.code.val
|
|
return this.resolveThis(node.enclosingFunction);
|
|
}
|
|
else {
|
|
return ''; // TODO handle global this?
|
|
}
|
|
}
|
|
}
|
|
else if (node.parent) {
|
|
if (node.parent.type === Token.COLON) {
|
|
parent = node.parent.parent;
|
|
}
|
|
else {
|
|
parent = node.parent;
|
|
}
|
|
|
|
memberof.id = 'astnode' + parent.hashCode();
|
|
memberof.doclet = this.refs[memberof.id];
|
|
|
|
if (!memberof.doclet) {
|
|
return ''; // global?
|
|
}
|
|
|
|
return memberof.doclet.longname || memberof.doclet.name;
|
|
}
|
|
else {
|
|
return ''; // global?
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Given 'var foo = { x: 1 }', find foo from x.
|
|
*/
|
|
exports.Parser.prototype.resolvePropertyParent = function(node) {
|
|
var memberof = {};
|
|
var parent;
|
|
|
|
if (node.parent && node.parent.type === Token.COLON) {
|
|
parent = node.parent.parent;
|
|
}
|
|
else {
|
|
parent = node.parent;
|
|
}
|
|
|
|
if (parent) {
|
|
memberof.id = 'astnode' + parent.hashCode();
|
|
memberof.doclet = this.refs[memberof.id];
|
|
|
|
if (memberof.doclet) {
|
|
return memberof;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Resolve what function a var is limited to.
|
|
* @param {astnode} node
|
|
* @param {string} basename The leftmost name in the long name: in foo.bar.zip the basename is foo.
|
|
*/
|
|
exports.Parser.prototype.resolveVar = function(node, basename) {
|
|
var doclet;
|
|
var enclosingFunction = node.enclosingFunction;
|
|
|
|
if (!enclosingFunction) {
|
|
return ''; // global
|
|
}
|
|
|
|
doclet = this.refs['astnode'+enclosingFunction.hashCode()];
|
|
if (doclet && doclet.meta.vars && basename in doclet.meta.vars) {
|
|
return doclet.longname;
|
|
}
|
|
|
|
return this.resolveVar(enclosingFunction, basename);
|
|
};
|
|
|
|
exports.Parser.prototype.addDocletRef = function(e) {
|
|
var node = e.code.node;
|
|
// allow lookup from value => doclet
|
|
if (e.doclet) {
|
|
this.refs['astnode' + node.hashCode()] = e.doclet;
|
|
}
|
|
// keep references to undocumented anonymous functions, too, as they might have scoped vars
|
|
else if ((node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) &&
|
|
!this.refs['astnode' + node.hashCode()]) {
|
|
this.refs['astnode' + node.hashCode()] = {
|
|
longname: '<anonymous>',
|
|
meta: {
|
|
code: e.code
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
exports.Parser.prototype.resolveEnum = function(e) {
|
|
var parent = this.resolvePropertyParent(e.code.node);
|
|
|
|
if (parent && parent.doclet.isEnum) {
|
|
if (!parent.doclet.properties) {
|
|
parent.doclet.properties = [];
|
|
}
|
|
|
|
// members of an enum inherit the enum's type
|
|
if (parent.doclet.type && !e.doclet.type) {
|
|
e.doclet.type = parent.doclet.type;
|
|
|
|
}
|
|
|
|
delete e.doclet.undocumented;
|
|
e.doclet.defaultvalue = e.doclet.meta.code.value;
|
|
|
|
// add the doclet to the parent's properties
|
|
// use a copy of the doclet to avoid circular references
|
|
parent.doclet.properties.push( require('jsdoc/util/doop').doop(e.doclet) );
|
|
}
|
|
};
|
|
|
|
/**
|
|
Fired whenever the parser encounters a JSDoc comment in the current source code.
|
|
@event jsdocCommentFound
|
|
@memberof module:jsdoc/src/parser.Parser
|
|
@param {event} e
|
|
@param {string} e.comment The text content of the JSDoc comment
|
|
@param {number} e.lineno The line number associated with the found comment.
|
|
@param {string} e.filename The file name associated with the found comment.
|
|
*/ |