mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-07 13:57:48 +00:00
427 lines
12 KiB
JavaScript
427 lines
12 KiB
JavaScript
/**
|
|
* @module jsdoc/src/visitor
|
|
*/
|
|
'use strict';
|
|
|
|
// TODO: consider exporting more stuff so users can override it
|
|
|
|
var jsdoc = {
|
|
doclet: require('jsdoc/doclet'),
|
|
name: require('jsdoc/name'),
|
|
src: {
|
|
astnode: require('jsdoc/src/astnode'),
|
|
syntax: require('jsdoc/src/syntax')
|
|
},
|
|
util: {
|
|
logger: require('jsdoc/util/logger')
|
|
}
|
|
};
|
|
var util = require('util');
|
|
|
|
var hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var Syntax = jsdoc.src.syntax.Syntax;
|
|
|
|
// TODO: docs
|
|
function getLeadingComment(node) {
|
|
var comment = null;
|
|
var leadingComments = node.leadingComments;
|
|
|
|
if (Array.isArray(leadingComments) && leadingComments.length && leadingComments[0].raw) {
|
|
comment = leadingComments[0].raw;
|
|
}
|
|
|
|
return comment;
|
|
}
|
|
|
|
// TODO: docs
|
|
function makeVarsFinisher(scopeDoclet) {
|
|
return function(e) {
|
|
// no need to evaluate all things related to scopeDoclet again, just use it
|
|
if (scopeDoclet && e.doclet && e.doclet.alias) {
|
|
scopeDoclet.meta.vars[e.code.name] = e.doclet.longname;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* For function parameters that have inline documentation, create a function that will merge the
|
|
* inline documentation into the function's doclet. If the parameter is already documented in the
|
|
* function's doclet, the inline documentation will be ignored.
|
|
*
|
|
* @private
|
|
* @param {module:jsdoc/src/parser.Parser} parser - The JSDoc parser.
|
|
* @return {function} A function that merges a parameter's inline documentation into the function's
|
|
* doclet.
|
|
*/
|
|
function makeInlineParamsFinisher(parser) {
|
|
return function(e) {
|
|
var documentedParams;
|
|
var knownParams;
|
|
var param;
|
|
var parentDoclet;
|
|
|
|
var i = 0;
|
|
|
|
if (e.doclet && e.doclet.meta && e.doclet.meta.code && e.doclet.meta.code.node &&
|
|
e.doclet.meta.code.node.parent) {
|
|
parentDoclet = parser._getDoclet(e.doclet.meta.code.node.parent.nodeId);
|
|
}
|
|
if (!parentDoclet) {
|
|
return;
|
|
}
|
|
|
|
// we only want to use the doclet if it's param-specific (but not, for example, if it's
|
|
// a param tagged with `@exports` in an AMD module)
|
|
if (e.doclet.kind !== 'param') {
|
|
return;
|
|
}
|
|
|
|
parentDoclet.params = parentDoclet.params || [];
|
|
documentedParams = parentDoclet.params;
|
|
knownParams = parentDoclet.meta.code.paramnames;
|
|
|
|
while (true) {
|
|
param = documentedParams[i];
|
|
|
|
// is the param already documented? if so, we don't need to use the doclet
|
|
if (param && param.name === e.doclet.name) {
|
|
e.doclet.undocumented = true;
|
|
break;
|
|
}
|
|
|
|
// if we ran out of documented params, or we're at the parameter's actual position,
|
|
// splice in the param at the current index
|
|
if ( !param || i === knownParams.indexOf(e.doclet.name) ) {
|
|
documentedParams.splice(i, 0, {
|
|
type: e.doclet.type,
|
|
description: '',
|
|
name: e.doclet.name
|
|
});
|
|
|
|
// the doclet is no longer needed
|
|
e.doclet.undocumented = true;
|
|
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
};
|
|
}
|
|
|
|
// TODO: docs
|
|
function SymbolFound(node, filename, extras) {
|
|
var self = this;
|
|
extras = extras || {};
|
|
|
|
this.id = extras.id || node.nodeId;
|
|
this.comment = extras.comment || getLeadingComment(node) || '@undocumented';
|
|
this.lineno = extras.lineno || node.loc.start.line;
|
|
this.range = extras.range || node.range;
|
|
this.filename = extras.filename || filename;
|
|
this.astnode = extras.astnode || node;
|
|
this.code = extras.code;
|
|
this.event = extras.event || 'symbolFound';
|
|
this.finishers = extras.finishers || [];
|
|
|
|
// make sure the event includes properties that don't have default values
|
|
Object.keys(extras).forEach(function(key) {
|
|
self[key] = extras[key];
|
|
});
|
|
}
|
|
|
|
// TODO: docs
|
|
function JsdocCommentFound(comment, filename) {
|
|
this.comment = comment.raw;
|
|
this.lineno = comment.loc.start.line;
|
|
this.filename = filename;
|
|
this.range = comment.range;
|
|
|
|
Object.defineProperty(this, 'event', {
|
|
value: 'jsdocCommentFound'
|
|
});
|
|
}
|
|
|
|
// TODO: docs
|
|
var Visitor = exports.Visitor = function(parser) {
|
|
this._parser = parser;
|
|
|
|
// Mozilla Parser API node visitors added by plugins
|
|
this._nodeVisitors = [];
|
|
// built-in visitors
|
|
this._visitors = [
|
|
this.visitNodeComments,
|
|
this.visitNode
|
|
];
|
|
};
|
|
|
|
// TODO: docs
|
|
Visitor.prototype.addAstNodeVisitor = function(visitor) {
|
|
this._nodeVisitors.push(visitor);
|
|
};
|
|
|
|
// TODO: docs
|
|
Visitor.prototype.removeAstNodeVisitor = function(visitor) {
|
|
var idx = this._nodeVisitors.indexOf(visitor);
|
|
if (idx !== -1) {
|
|
this._nodeVisitors.splice(idx, 1);
|
|
}
|
|
};
|
|
|
|
// TODO: docs
|
|
Visitor.prototype.getAstNodeVisitors = function() {
|
|
return this._nodeVisitors;
|
|
};
|
|
|
|
// TODO: docs; visitor signature is (node, parser, filename)
|
|
Visitor.prototype.visit = function(node, filename) {
|
|
var i;
|
|
var l;
|
|
|
|
for (i = 0, l = this._visitors.length; i < l; i++) {
|
|
this._visitors[i].call(this, node, this._parser, filename);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// TODO: docs
|
|
/**
|
|
* Verify that a block comment exists and that its leading delimiter does not contain three or more
|
|
* asterisks.
|
|
*
|
|
* @private
|
|
* @memberof module:jsdoc/src/parser.Parser
|
|
*/
|
|
function isValidJsdoc(commentSrc) {
|
|
return commentSrc && commentSrc.indexOf('/***') !== 0;
|
|
}
|
|
|
|
// TODO: docs
|
|
function hasJsdocComments(node) {
|
|
return (node && node.leadingComments && node.leadingComments.length) ||
|
|
(node && node.trailingComments && node.trailingComments.length);
|
|
}
|
|
|
|
// TODO: docs
|
|
function removeCommentDelimiters(comment) {
|
|
return comment.substring(2, comment.length - 2);
|
|
}
|
|
|
|
// TODO: docs
|
|
function updateCommentNode(commentNode, comment) {
|
|
commentNode.raw = comment;
|
|
commentNode.value = removeCommentDelimiters(comment);
|
|
}
|
|
|
|
// TODO: docs
|
|
Visitor.prototype.visitNodeComments = function(node, parser, filename) {
|
|
var comment;
|
|
var comments;
|
|
var e;
|
|
|
|
var BLOCK_COMMENT = 'Block';
|
|
|
|
if ( !hasJsdocComments(node) && (!node.type || node.type !== BLOCK_COMMENT) ) {
|
|
return true;
|
|
}
|
|
|
|
comments = (node.type === BLOCK_COMMENT) ? [node] : [];
|
|
|
|
if (node.leadingComments && node.leadingComments.length) {
|
|
comments = comments.concat( node.leadingComments.slice(0) );
|
|
}
|
|
|
|
if (node.trailingComments && node.trailingComments.length) {
|
|
comments = comments.concat( node.trailingComments.slice(0) );
|
|
}
|
|
|
|
for (var i = 0, l = comments.length; i < l; i++) {
|
|
comment = comments[i];
|
|
if ( isValidJsdoc(comment.raw) ) {
|
|
e = new JsdocCommentFound(comment, filename);
|
|
|
|
parser.emit(e.event, e, parser);
|
|
|
|
if (e.comment !== comment.raw) {
|
|
updateCommentNode(comment, e.comment);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// TODO: docs
|
|
Visitor.prototype.visitNode = function(node, parser, filename) {
|
|
var i;
|
|
var l;
|
|
|
|
var e = this.makeSymbolFoundEvent(node, parser, filename);
|
|
|
|
if (this._nodeVisitors && this._nodeVisitors.length) {
|
|
for (i = 0, l = this._nodeVisitors.length; i < l; i++) {
|
|
this._nodeVisitors[i].visitNode(node, e, parser, filename);
|
|
if (e.stopPropagation) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!e.preventDefault && e.comment && isValidJsdoc(e.comment)) {
|
|
parser.emit(e.event, e, parser);
|
|
}
|
|
|
|
// add the node to the parser's lookup table
|
|
parser.addDocletRef(e);
|
|
|
|
for (i = 0, l = e.finishers.length; i < l; i++) {
|
|
e.finishers[i].call(parser, e);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// TODO: docs
|
|
// TODO: note that it's essential to call this function before you try to resolve names!
|
|
function trackVars(parser, node, e) {
|
|
var enclosingScopeId = node.enclosingScope ? node.enclosingScope.nodeId :
|
|
jsdoc.name.LONGNAMES.GLOBAL;
|
|
var doclet = parser.refs[enclosingScopeId];
|
|
|
|
if (doclet) {
|
|
doclet.meta.vars = doclet.meta.vars || {};
|
|
doclet.meta.vars[e.code.name] = null;
|
|
e.finishers.push( makeVarsFinisher(doclet) );
|
|
}
|
|
}
|
|
|
|
// TODO: docs
|
|
Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) {
|
|
var logger = jsdoc.util.logger;
|
|
|
|
var e;
|
|
var basename;
|
|
var i;
|
|
var l;
|
|
var parent;
|
|
|
|
var extras = {
|
|
code: jsdoc.src.astnode.getInfo(node)
|
|
};
|
|
|
|
switch (node.type) {
|
|
// like: i = 0;
|
|
case Syntax.AssignmentExpression:
|
|
e = new SymbolFound(node, filename, extras);
|
|
|
|
trackVars(parser, node, e);
|
|
|
|
basename = parser.getBasename(e.code.name);
|
|
if (basename !== 'this') {
|
|
e.code.funcscope = parser.resolveVar(node, basename);
|
|
}
|
|
|
|
break;
|
|
|
|
// like: function foo() {}
|
|
case Syntax.FunctionDeclaration:
|
|
// falls through
|
|
|
|
// like: var foo = function() {};
|
|
case Syntax.FunctionExpression:
|
|
e = new SymbolFound(node, filename, extras);
|
|
|
|
trackVars(parser, node, e);
|
|
|
|
basename = parser.getBasename(e.code.name);
|
|
e.code.funcscope = parser.resolveVar(node, basename);
|
|
|
|
break;
|
|
|
|
// like "bar" in: function foo(/** @type {string} */ bar) {}
|
|
// or "module" in: define("MyModule", function(/** @exports MyModule */ module) {}
|
|
// This is an extremely common type of node; we only care about function parameters with
|
|
// inline comments. No need to fire an event unless the node is already commented.
|
|
case Syntax.Identifier:
|
|
parent = node.parent;
|
|
if ( node.leadingComments && parent && jsdoc.src.astnode.isFunction(parent) ) {
|
|
extras.finishers = [makeInlineParamsFinisher(parser)];
|
|
e = new SymbolFound(node, filename, extras);
|
|
|
|
trackVars(parser, node, e);
|
|
}
|
|
|
|
break;
|
|
|
|
// like "obj.prop" in: /** @typedef {string} */ obj.prop;
|
|
// Closure Compiler uses this pattern extensively for enums.
|
|
// No need to fire an event unless the node is already commented.
|
|
case Syntax.MemberExpression:
|
|
if (node.leadingComments) {
|
|
e = new SymbolFound(node, filename, extras);
|
|
}
|
|
|
|
break;
|
|
|
|
// like the object literal in: function Foo = Class.create(/** @lends Foo */ {});
|
|
case Syntax.ObjectExpression:
|
|
e = new SymbolFound(node, filename, extras);
|
|
|
|
break;
|
|
|
|
// like "bar: true" in: var foo = { bar: true };
|
|
// like "get bar() {}" in: var foo = { get bar() {} };
|
|
case Syntax.Property:
|
|
if ( node.kind !== ('get' || 'set') ) {
|
|
extras.finishers = [parser.resolveEnum];
|
|
}
|
|
|
|
e = new SymbolFound(node, filename, extras);
|
|
|
|
break;
|
|
|
|
// like: var i = 0;
|
|
case Syntax.VariableDeclarator:
|
|
e = new SymbolFound(node, filename, extras);
|
|
|
|
trackVars(parser, node, e);
|
|
|
|
basename = parser.getBasename(e.code.name);
|
|
|
|
break;
|
|
|
|
// for now, log a warning for all ES6 nodes, since we don't do anything useful with them
|
|
case Syntax.ArrowFunctionExpression:
|
|
case Syntax.ClassBody:
|
|
case Syntax.ClassDeclaration:
|
|
case Syntax.ClassExpression:
|
|
case Syntax.ExportBatchSpecifier:
|
|
case Syntax.ExportDeclaration:
|
|
case Syntax.ExportSpecifier:
|
|
case Syntax.ImportDeclaration:
|
|
case Syntax.ImportSpecifier:
|
|
case Syntax.MethodDefinition:
|
|
case Syntax.ModuleDeclaration:
|
|
case Syntax.SpreadElement:
|
|
case Syntax.TaggedTemplateExpression:
|
|
case Syntax.TemplateElement:
|
|
case Syntax.TemplateLiteral:
|
|
logger.warn('JSDoc does not currently handle %s nodes. Source file: %s, line %s',
|
|
node.type, filename, (node.loc && node.loc.start) ? node.loc.start.line : '??');
|
|
|
|
break;
|
|
|
|
default:
|
|
// ignore
|
|
}
|
|
|
|
if (!e) {
|
|
e = {
|
|
finishers: []
|
|
};
|
|
}
|
|
|
|
return e;
|
|
};
|