PHP WebShell
Текущая директория: /opt/BitGoJS/node_modules/jsdoc/lib/jsdoc/src
Просмотр файла: visitor.js
/**
* @module jsdoc/src/visitor
*/
// TODO: consider exporting more stuff so users can override it
const 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')
}
};
const Syntax = jsdoc.src.syntax.Syntax;
/**
* Get the raw comment string for a block comment node.
*
* @private
* @param {!Object} comment - A comment node with `type` and `value` properties.
*/
function getRawComment({value}) {
return `/*${value}*/`;
}
/**
* Check whether a comment node represents a block comment.
*
* @param {!Object} comment - A comment node with `type` and `value` properties.
* @return {boolean} `true` if the comment is a block comment, `false` otherwise.
*/
function isBlockComment({type}) {
return type === 'CommentBlock';
}
/**
* Verify that a block comment exists; that it is a JSDoc comment; 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.length > 4 && commentSrc.indexOf('/**') === 0 &&
commentSrc.indexOf('/***') !== 0;
}
// TODO: docs
function getLeadingJsdocComment(node) {
let comment = null;
let leadingComments = node.leadingComments;
if (Array.isArray(leadingComments) && leadingComments.length) {
// the attached comments may include line comments, which we don't want
leadingComments = leadingComments.filter(isBlockComment);
if (leadingComments.length) {
// treat the comment closest to the node as the leading comment
comment = getRawComment(leadingComments[leadingComments.length - 1]);
if ( !isValidJsdoc(comment) ) {
comment = null;
}
}
}
return comment;
}
// TODO: docs
function makeVarsFinisher(scopeDoclet) {
return ({doclet, code}) => {
// no need to evaluate all things related to scopeDoclet again, just use it
if ( scopeDoclet && doclet && (doclet.alias || doclet.memberof) ) {
scopeDoclet.meta.vars[code.name] = doclet.longname;
}
};
}
// Given an event, get the parent node's doclet.
function getParentDocletFromEvent(parser, {doclet}) {
if (doclet && doclet.meta && doclet.meta.code && doclet.meta.code.node &&
doclet.meta.code.node.parent) {
return parser._getDocletById(doclet.meta.code.node.parent.nodeId);
}
return null;
}
/**
* 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 e => {
let documentedParams;
let knownParams;
let param;
let parentDoclet;
let i = 0;
parentDoclet = getParentDocletFromEvent(parser, e);
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++;
}
};
}
/**
* Given an array of nodes that represent function parameters, find the node for the rest parameter,
* if any.
*
* @private
* @param {Array.<Object>} params - An array of nodes that represent function parameters.
* @return {Object?} The node for the rest parameter.
*/
function findRestParam(params) {
let restParam = null;
params.some(param => {
if (param.type === Syntax.RestElement) {
restParam = param;
return true;
}
return false;
});
return restParam;
}
/**
* For functions that may include a rest parameter, create a function that will automatically update
* the rest parameter's documentation to indicate that the parameter is repeatable. If the parameter
* is not documented, the function's doclet will remain unchanged.
*
* @private
* @return {function} A function that updates the rest parameter's documentation to indicate that
* the parameter is repeatable.
*/
function makeRestParamFinisher() {
return e => {
const doclet = e.doclet;
let documentedParams;
let restNode;
if (!doclet) {
return;
}
documentedParams = doclet.params = doclet.params || [];
restNode = findRestParam(e.code.node.params ||
(e.code.node.value && e.code.node.value.params) ||
(e.code.node.init && e.code.node.init.params) ||
[]);
if (restNode) {
for (let i = documentedParams.length - 1; i >= 0; i--) {
if (documentedParams[i].name === restNode.argument.name) {
documentedParams[i].variable = true;
break;
}
}
}
};
}
/**
* Given an array of nodes that represent function parameters, find the nodes for the default
* parameters, if any.
*
* @private
* @param {Array.<Object>} params - An array of nodes that represent function parameters.
* @return {Array.<Object>} The nodes for the default parameters.
*/
function findDefaultParams(params) {
const defaultParams = [];
params.forEach(param => {
if (param.type === Syntax.AssignmentPattern) {
defaultParams.push(param);
}
else {
defaultParams.push(null);
}
});
return defaultParams;
}
/**
* For functions that may have at least one parameter with default values, create a function that
* will automatically add the parameters' default values to the function's documentation. If any
* default value is already documented, the function's doclet will remain unchanged.
*
* This function is only intended to handle default parameters whose node type is `Syntax.Literal`
* (string, numeric, and boolean literals). This is because more complex default values may include,
* for example, references to internal variables, which it may not make sense to include in
* documentation.
*
* @private
* @return {function} A function that updates the function doclet to include the default values of
* parameters.
*/
function makeDefaultParamFinisher() {
return e => {
let defaultValues;
const doclet = e.doclet;
let documentedParams;
let paramName;
let params;
if (!doclet) {
return;
}
documentedParams = doclet.params = doclet.params || [];
params = e.code.node.params || (e.code.node.value && e.code.node.value.params) || [];
defaultValues = findDefaultParams(params);
for (let i = 0, j = 0, l = params.length; i < l; i++) {
// bail out if we ran out of documented params
if (!documentedParams[j]) {
break;
}
// if the current parameter doesn't appear to be documented, move to the next one
paramName = params[i].type === Syntax.AssignmentPattern ?
params[i].left.name :
params[i].name;
if (paramName !== documentedParams[j].name) {
continue;
}
// add the default value iff a) a literal default value is defined in the code,
// b) no default value is documented, and c) the default value is not an empty string
if (defaultValues[i] &&
defaultValues[i].right &&
defaultValues[i].right.type === Syntax.Literal &&
typeof documentedParams[j].defaultvalue === 'undefined' &&
defaultValues[i].right.value !== '') {
documentedParams[j].defaultvalue =
jsdoc.src.astnode.nodeToValue(defaultValues[i].right);
}
// move to the next documented param
j++;
}
};
}
/**
* For method definitions that are constructors, create a function that will merge portions of the
* constructor's doclet into the class's doclet, provided that a doclet exists for the class.
* Merging the constructor's documentation allows ES 2015 classes to be documented in a natural way,
* with separate JSDoc comments for the class and its constructor.
*
* @private
* @param {module:jsdoc/src/parser.Parser} parser - The JSDoc parser.
* @return {function} A function that merges the constructor's doclet into the class's doclet.
*/
function makeConstructorFinisher(parser) {
return e => {
let combined;
const doclet = e.doclet;
let parentDoclet;
// for class declarations that are named module exports, the node that's documented is the
// ExportNamedDeclaration, not the ClassDeclaration
if (e.code.node.parent.parent.parent &&
e.code.node.parent.parent.parent.type === Syntax.ExportNamedDeclaration) {
parentDoclet = parser._getDocletById(e.code.node.parent.parent.parent.nodeId);
}
// otherwise, we want the ClassDeclaration
else {
parentDoclet = parser._getDocletById(e.code.node.parent.parent.nodeId);
}
if (!doclet || !parentDoclet || parentDoclet.undocumented) {
return;
}
// We prefer the parent doclet because it has the correct kind, longname, and memberof.
// The child doclet might or might not have the correct kind, longname, and memberof.
combined = jsdoc.doclet.combine(parentDoclet, doclet);
parser.addResult(combined);
parentDoclet.undocumented = doclet.undocumented = true;
};
}
/**
* Create a function that will add an `async` property to the doclet for async functions.
*
* @private
* @return {function} A function that adds an `async` property to the doclet of async functions.
*/
function makeAsyncFunctionFinisher() {
return e => {
const doclet = e.doclet;
if (!doclet) {
return;
}
if ( e.code.node.async || (e.code.node.value && e.code.node.value.async) ||
(e.code.node.init && e.code.node.init.async) ) {
doclet.async = true;
}
};
}
/**
* Create a function that will mark a doclet as private.
*
* @private
* @return {function} A function that marks a doclet as private.
*/
function makePrivatePropertyFinisher() {
return ({doclet}) => {
doclet.access = 'private';
};
}
/**
* Create a function that will mark a doclet as a generator function.
*
* @private
* @return {function} A function that marks a doclet as a generator function.
*/
function makeGeneratorFinisher() {
return e => {
const doclet = e.doclet;
if (!doclet) {
return;
}
if ( e.code.node.generator || (e.code.node.init && e.code.node.init.generator) ||
(e.code.node.value && e.code.node.value.generator) ) {
doclet.generator = true;
}
};
}
// TODO: docs
class SymbolFound {
// TODO: docs
constructor(node, filename, extras = {}) {
this.id = extras.id || node.nodeId;
this.comment = extras.comment || getLeadingJsdocComment(node) || '@undocumented';
this.lineno = extras.lineno || node.loc.start.line;
this.columnno = extras.columnno || node.loc.start.column;
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(key => {
this[key] = extras[key];
});
}
}
// TODO: docs
class JsdocCommentFound {
// TODO: docs
constructor({loc, range}, rawComment, filename) {
this.comment = rawComment;
this.lineno = loc.start.line;
this.columnno = loc.start.column;
this.filename = filename;
this.range = range;
Object.defineProperty(this, 'event', {
value: 'jsdocCommentFound'
});
}
}
// TODO: docs
function hasComments(node) {
return (node && node.leadingComments && node.leadingComments.length) ||
(node && node.trailingComments && node.trailingComments.length) ||
(node && node.innerComments && node.innerComments.length);
}
// TODO: docs
function removeCommentDelimiters(comment) {
return comment.substring(2, comment.length - 2);
}
// TODO: docs
function updateCommentNode(commentNode, comment) {
commentNode.value = removeCommentDelimiters(comment);
}
// TODO: docs
// TODO: note that it's essential to call this function before you try to resolve names!
function trackVars(parser, {enclosingScope}, {code, finishers}) {
let doclet;
const enclosingScopeId = enclosingScope ? enclosingScope.nodeId : null;
if (enclosingScopeId) {
doclet = parser._getDocletById(enclosingScopeId);
}
else {
doclet = parser._getDocletByLongname(jsdoc.name.LONGNAMES.GLOBAL);
}
if (doclet) {
doclet.meta.vars = doclet.meta.vars || {};
doclet.meta.vars[code.name] = null;
finishers.push( makeVarsFinisher(doclet) );
}
}
// TODO: docs
function makeSymbolFoundEvent(node, parser, filename) {
let e;
let basename;
let parent;
const 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 = jsdoc.name.getBasename(e.code.name);
if (basename !== 'this') {
e.code.funcscope = parser.resolveVar(node, basename);
}
break;
// like `bar='baz'` in: function foo(bar='baz') {}
case Syntax.AssignmentPattern:
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: class foo {}
case Syntax.ClassDeclaration:
// falls through
// like: let MyClass = class {}
case Syntax.ClassExpression:
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
basename = jsdoc.name.getBasename(e.code.name);
break;
// like `#b = 1` in: class A { #b = 1; }
case Syntax.ClassPrivateProperty:
extras.finishers = [
parser.resolveEnum,
makePrivatePropertyFinisher()
];
e = new SymbolFound(node, filename, extras);
break;
// like `b = 1` in: class A { b = 1; }
case Syntax.ClassProperty:
extras.finishers = [parser.resolveEnum];
e = new SymbolFound(node, filename, extras);
break;
// like: export * from 'foo'
case Syntax.ExportAllDeclaration:
e = new SymbolFound(node, filename, extras);
break;
// like: export default 'foo'
case Syntax.ExportDefaultDeclaration:
// falls through
// like: export var foo;
// or: export {foo}
case Syntax.ExportNamedDeclaration:
// falls through
// like `foo as bar` in: export {foo as bar}
case Syntax.ExportSpecifier:
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
break;
// like: var foo = () => {};
case Syntax.ArrowFunctionExpression:
// falls through
// like: function foo() {}
case Syntax.FunctionDeclaration:
// falls through
// like: var foo = function() {};
case Syntax.FunctionExpression:
extras.finishers = [
// handle cases where at least one parameter has a default value
makeDefaultParamFinisher(),
// handle rest parameters
makeRestParamFinisher(),
// handle async functions
makeAsyncFunctionFinisher(),
// handle generator functions
makeGeneratorFinisher()
];
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
basename = jsdoc.name.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 in other cases.
case Syntax.Identifier:
parent = node.parent;
// function parameters with inline comments
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: foo() {}
// or: constructor() {}
case Syntax.MethodDefinition:
extras.finishers = [
// handle cases where at least one parameter has a default value
makeDefaultParamFinisher(),
// handle rest parameters
makeRestParamFinisher(),
// handle async functions
makeAsyncFunctionFinisher(),
// handle generator functions
makeGeneratorFinisher()
];
// for constructors, we attempt to merge the constructor's docs into the class's docs
if (node.kind === 'constructor') {
extras.finishers.push( makeConstructorFinisher(parser) );
}
e = new SymbolFound(node, filename, extras);
break;
// like `{}` 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' && node.kind !== 'set') {
extras.finishers = [parser.resolveEnum];
}
e = new SymbolFound(node, filename, extras);
break;
// like `...bar` in: function foo(...bar) {}
case Syntax.RestElement:
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: var i = 0;
case Syntax.VariableDeclarator:
extras.finishers = [
// handle cases where at least one parameter has a default value
makeDefaultParamFinisher(),
// handle rest parameters
makeRestParamFinisher(),
// handle async functions
makeAsyncFunctionFinisher(),
// handle generator functions
makeGeneratorFinisher()
];
e = new SymbolFound(node, filename, extras);
trackVars(parser, node, e);
basename = jsdoc.name.getBasename(e.code.name);
// auto-detect constants
if (node.parent.kind === 'const') {
e.code.kind = 'constant';
}
break;
default:
// ignore
}
if (!e) {
e = {
finishers: []
};
}
return e;
}
// TODO: docs
class Visitor {
// TODO: docs
constructor() {
this._parser = null;
// Mozilla Parser API node visitors added by plugins
this._nodeVisitors = [];
// built-in visitors
this._visitors = [
this.visitNodeComments,
this.visitNode
];
}
/**
* Set the parser instance that visitors can use.
*
* @param {module:jsdoc/src/parser.Parser} parser - The parser instance.
*/
setParser(parser) {
this._parser = parser;
}
// TODO: docs
addAstNodeVisitor(visitor) {
this._nodeVisitors.push(visitor);
}
// TODO: docs
removeAstNodeVisitor(visitor) {
const idx = this._nodeVisitors.indexOf(visitor);
if (idx !== -1) {
this._nodeVisitors.splice(idx, 1);
}
}
// TODO: docs
getAstNodeVisitors() {
return this._nodeVisitors;
}
// TODO: docs; visitor signature is (node, parser, filename)
visit(node, filename) {
for (let visitor of this._visitors) {
visitor.call(this, node, this._parser, filename);
}
return true;
}
/* eslint-disable class-methods-use-this */
// TODO: docs
visitNodeComments(node, parser, filename) {
let comments;
let e;
const isBlock = isBlockComment(node);
let lastTrailingComment;
let nextProgramNode;
let nextProgramNodeIndex;
let rawComment;
function addComments(source) {
comments = comments.concat( source.slice(0) );
}
if ( !hasComments(node) && (!node.type || !isBlock) ) {
return true;
}
comments = isBlock ? [node] : [];
if (node.leadingComments && node.leadingComments.length) {
addComments(node.leadingComments);
}
// trailing comments are always duplicates of leading comments unless they're attached to the
// Program node...
if (node.type === Syntax.Program && node.trailingComments && node.trailingComments.length) {
addComments(node.trailingComments);
}
// ...or if they were comments from the end of the file that were erroneously attached to a
// `'use strict';` declaration (https://github.com/babel/babel/issues/6688).
if (node.type === Syntax.ExpressionStatement && node.directive === 'use strict' &&
node.trailingComments && node.trailingComments.length) {
// to be safe, we verify that the trailing comments came after the next node in the Program
// body, which means the comments were attached to the wrong node
if (node.parent.body.length > 1) {
nextProgramNodeIndex = node.parent.body.indexOf(node) + 1;
nextProgramNode = node.parent.body[nextProgramNodeIndex];
lastTrailingComment = node.trailingComments[node.trailingComments.length - 1];
if (lastTrailingComment.start > nextProgramNode.end) {
addComments(node.trailingComments);
}
}
}
if (node.innerComments && node.innerComments.length) {
addComments(node.innerComments);
}
for (let comment of comments) {
rawComment = getRawComment(comment);
if ( isValidJsdoc(rawComment) ) {
e = new JsdocCommentFound(comment, rawComment, filename);
parser.emit(e.event, e, parser);
if (e.comment !== rawComment) {
updateCommentNode(comment, e.comment);
}
}
}
return true;
}
/* eslint-enable class-methods-use-this */
// TODO: docs
visitNode(node, parser, filename) {
const e = makeSymbolFoundEvent(node, parser, filename);
if (this._nodeVisitors && this._nodeVisitors.length) {
for (let visitor of this._nodeVisitors) {
visitor.visitNode(node, e, parser, filename);
if (e.stopPropagation) {
break;
}
}
}
if (!e.preventDefault) {
parser.emit(e.event, e, parser);
}
// add the node to the parser's lookup table
parser.addDocletRef(e);
for (let finisher of e.finishers) {
finisher.call(parser, e);
}
return true;
}
}
exports.Visitor = Visitor;
Выполнить команду
Для локальной разработки. Не используйте в интернете!