mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
718 lines
17 KiB
JavaScript
718 lines
17 KiB
JavaScript
/*
|
|
Copyright 2013 the JSDoc Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* Traversal utilities for ASTs that are compatible with the ESTree API.
|
|
*/
|
|
|
|
import * as astNode from './ast-node.js';
|
|
import { Syntax } from './syntax.js';
|
|
|
|
// TODO: docs
|
|
function getCurrentScope(scopes) {
|
|
return scopes[scopes.length - 1] || null;
|
|
}
|
|
|
|
// TODO: docs
|
|
function moveLeadingComments(source, target, count) {
|
|
if (source.leadingComments) {
|
|
if (count === undefined) {
|
|
count = source.leadingComments.length;
|
|
}
|
|
|
|
target.leadingComments = source.leadingComments.slice(0, count);
|
|
source.leadingComments = source.leadingComments.slice(count);
|
|
}
|
|
}
|
|
|
|
// TODO: docs
|
|
function moveTrailingComments(source, target, count) {
|
|
if (source.trailingComments) {
|
|
if (count === undefined) {
|
|
count = source.trailingComments.length;
|
|
}
|
|
|
|
target.trailingComments = source.trailingComments.slice(
|
|
source.trailingComments.length - count,
|
|
count
|
|
);
|
|
source.trailingComments = source.trailingComments.slice();
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-empty-function, no-unused-vars
|
|
function leafNode(node, parent, state, cb) {}
|
|
|
|
// TODO: docs
|
|
export const walkers = {};
|
|
|
|
walkers[Syntax.ArrayExpression] = (node, parent, state, cb) => {
|
|
for (let element of node.elements) {
|
|
if (element) {
|
|
cb(element, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO: verify correctness
|
|
walkers[Syntax.ArrayPattern] = (node, parent, state, cb) => {
|
|
for (let element of node.elements) {
|
|
// must be an identifier or an expression
|
|
if (element && element.type !== Syntax.Identifier) {
|
|
cb(element, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ArrowFunctionExpression] = (node, parent, state, cb) => {
|
|
if (node.id) {
|
|
cb(node.id, node, state);
|
|
}
|
|
|
|
for (let param of node.params) {
|
|
cb(param, node, state);
|
|
}
|
|
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.AssignmentExpression] = (node, parent, state, cb) => {
|
|
cb(node.left, node, state);
|
|
cb(node.right, node, state);
|
|
};
|
|
|
|
walkers[Syntax.AssignmentPattern] = walkers[Syntax.AssignmentExpression];
|
|
|
|
walkers[Syntax.AwaitExpression] = (node, parent, state, cb) => {
|
|
cb(node.argument, node, state);
|
|
};
|
|
|
|
walkers[Syntax.BigIntLiteral] = leafNode;
|
|
|
|
walkers[Syntax.BinaryExpression] = (node, parent, state, cb) => {
|
|
cb(node.left, node, state);
|
|
cb(node.right, node, state);
|
|
};
|
|
|
|
walkers[Syntax.BindExpression] = (node, parent, state, cb) => {
|
|
if (node.object) {
|
|
cb(node.object, node, state);
|
|
}
|
|
|
|
cb(node.callee, node, state);
|
|
};
|
|
|
|
walkers[Syntax.BlockStatement] = (node, parent, state, cb) => {
|
|
for (let bodyItem of node.body) {
|
|
cb(bodyItem, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.BreakStatement] = leafNode;
|
|
|
|
walkers[Syntax.CallExpression] = function (node, parent, state, cb) {
|
|
cb(node.callee, node, state);
|
|
|
|
if (node.arguments) {
|
|
for (let arg of node.arguments) {
|
|
cb(arg, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.CatchClause] = leafNode;
|
|
|
|
walkers[Syntax.ClassBody] = walkers[Syntax.BlockStatement];
|
|
|
|
walkers[Syntax.ClassDeclaration] = (node, parent, state, cb) => {
|
|
if (node.id) {
|
|
cb(node.id, node, state);
|
|
}
|
|
|
|
if (node.superClass) {
|
|
cb(node.superClass, node, state);
|
|
}
|
|
|
|
if (node.body) {
|
|
cb(node.body, node, state);
|
|
}
|
|
|
|
if (node.decorators) {
|
|
for (let decorator of node.decorators) {
|
|
cb(decorator, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ClassExpression] = walkers[Syntax.ClassDeclaration];
|
|
|
|
// walkers[Syntax.ClassPrivateProperty] is defined later
|
|
|
|
// walkers[Syntax.ClassProperty] is defined later
|
|
|
|
// TODO: verify correctness
|
|
walkers[Syntax.ComprehensionBlock] = walkers[Syntax.AssignmentExpression];
|
|
|
|
// TODO: verify correctness
|
|
walkers[Syntax.ComprehensionExpression] = (node, parent, state, cb) => {
|
|
cb(node.body, node, state);
|
|
|
|
if (node.filter) {
|
|
cb(node.filter, node, state);
|
|
}
|
|
|
|
for (let block of node.blocks) {
|
|
cb(block, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ConditionalExpression] = (node, parent, state, cb) => {
|
|
cb(node.test, node, state);
|
|
cb(node.consequent, node, state);
|
|
cb(node.alternate, node, state);
|
|
};
|
|
|
|
walkers[Syntax.ContinueStatement] = leafNode;
|
|
|
|
walkers[Syntax.DebuggerStatement] = leafNode;
|
|
|
|
walkers[Syntax.Decorator] = (node, parent, state, cb) => {
|
|
cb(node.expression, node, state);
|
|
};
|
|
|
|
walkers[Syntax.DoExpression] = (node, parent, state, cb) => {
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.DoWhileStatement] = (node, parent, state, cb) => {
|
|
cb(node.test, node, state);
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.EmptyStatement] = leafNode;
|
|
|
|
walkers[Syntax.ExperimentalRestProperty] = (node, parent, state, cb) => {
|
|
cb(node.argument, node, state);
|
|
};
|
|
|
|
walkers[Syntax.ExperimentalSpreadProperty] = walkers[Syntax.ExperimentalRestProperty];
|
|
|
|
walkers[Syntax.ExportAllDeclaration] = (node, parent, state, cb) => {
|
|
if (node.source) {
|
|
cb(node.source, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ExportDefaultDeclaration] = (node, parent, state, cb) => {
|
|
// if the declaration target is a class, move leading comments to the declaration target
|
|
if (node.declaration && node.declaration.type === Syntax.ClassDeclaration) {
|
|
moveLeadingComments(node, node.declaration);
|
|
}
|
|
|
|
if (node.declaration) {
|
|
cb(node.declaration, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ExportDefaultSpecifier] = (node, parent, state, cb) => {
|
|
cb(node.exported, node, state);
|
|
};
|
|
|
|
walkers[Syntax.ExportNamedDeclaration] = (node, parent, state, cb) => {
|
|
if (node.declaration) {
|
|
cb(node.declaration, node, state);
|
|
}
|
|
|
|
for (let specifier of node.specifiers) {
|
|
cb(specifier, node, state);
|
|
}
|
|
|
|
if (node.source) {
|
|
cb(node.source, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ExportNamespaceSpecifier] = (node, parent, state, cb) => {
|
|
cb(node.exported, node, state);
|
|
};
|
|
|
|
walkers[Syntax.ExportSpecifier] = (node, parent, state, cb) => {
|
|
if (node.exported) {
|
|
cb(node.exported, node, state);
|
|
}
|
|
|
|
if (node.local) {
|
|
cb(node.local, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ExpressionStatement] = (node, parent, state, cb) => {
|
|
moveLeadingComments(node, node.expression);
|
|
|
|
cb(node.expression, node, state);
|
|
};
|
|
|
|
walkers[Syntax.File] = (node, parent, state, cb) => {
|
|
cb(node.program, node, state);
|
|
};
|
|
|
|
walkers[Syntax.ForInStatement] = (node, parent, state, cb) => {
|
|
cb(node.left, node, state);
|
|
cb(node.right, node, state);
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.ForOfStatement] = walkers[Syntax.ForInStatement];
|
|
|
|
walkers[Syntax.ForStatement] = (node, parent, state, cb) => {
|
|
if (node.init) {
|
|
cb(node.init, node, state);
|
|
}
|
|
|
|
if (node.test) {
|
|
cb(node.test, node, state);
|
|
}
|
|
|
|
if (node.update) {
|
|
cb(node.update, node, state);
|
|
}
|
|
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.FunctionDeclaration] = walkers[Syntax.ArrowFunctionExpression];
|
|
|
|
walkers[Syntax.FunctionExpression] = walkers[Syntax.ArrowFunctionExpression];
|
|
|
|
walkers[Syntax.Identifier] = leafNode;
|
|
|
|
walkers[Syntax.IfStatement] = (node, parent, state, cb) => {
|
|
cb(node.test, node, state);
|
|
cb(node.consequent, node, state);
|
|
if (node.alternate) {
|
|
cb(node.alternate, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.Import] = leafNode;
|
|
|
|
walkers[Syntax.ImportDeclaration] = (node, parent, state, cb) => {
|
|
if (node.specifiers) {
|
|
for (let specifier of node.specifiers) {
|
|
cb(specifier, node, state);
|
|
}
|
|
}
|
|
|
|
if (node.source) {
|
|
cb(node.source, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ImportDefaultSpecifier] = (node, parent, state, cb) => {
|
|
if (node.local) {
|
|
cb(node.local, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ImportNamespaceSpecifier] = walkers[Syntax.ImportDefaultSpecifier];
|
|
|
|
walkers[Syntax.ImportSpecifier] = walkers[Syntax.ExportSpecifier];
|
|
|
|
walkers[Syntax.JSXAttribute] = (node, parent, state, cb) => {
|
|
cb(node.name, node, state);
|
|
|
|
if (node.value) {
|
|
cb(node.value, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.JSXClosingElement] = (node, parent, state, cb) => {
|
|
cb(node.name, node, state);
|
|
};
|
|
|
|
walkers[Syntax.JSXElement] = (node, parent, state, cb) => {
|
|
cb(node.openingElement, node, state);
|
|
|
|
if (node.closingElement) {
|
|
cb(node.closingElement, node, state);
|
|
}
|
|
|
|
for (let child of node.children) {
|
|
cb(child, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.JSXEmptyExpression] = leafNode;
|
|
|
|
walkers[Syntax.JSXExpressionContainer] = (node, parent, state, cb) => {
|
|
cb(node.expression, node, state);
|
|
};
|
|
|
|
walkers[Syntax.JSXIdentifier] = leafNode;
|
|
|
|
walkers[Syntax.JSXMemberExpression] = (node, parent, state, cb) => {
|
|
cb(node.object, node, state);
|
|
|
|
cb(node.property, node, state);
|
|
};
|
|
|
|
walkers[Syntax.JSXNamespacedName] = (node, parent, state, cb) => {
|
|
cb(node.namespace, node, state);
|
|
|
|
cb(node.name, node, state);
|
|
};
|
|
|
|
walkers[Syntax.JSXOpeningElement] = (node, parent, state, cb) => {
|
|
cb(node.name, node, state);
|
|
|
|
for (let attribute of node.attributes) {
|
|
cb(attribute, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.JSXSpreadAttribute] = (node, parent, state, cb) => {
|
|
cb(node.argument, node, state);
|
|
};
|
|
|
|
walkers[Syntax.JSXText] = leafNode;
|
|
|
|
walkers[Syntax.LabeledStatement] = (node, parent, state, cb) => {
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
// TODO: add scope info??
|
|
walkers[Syntax.LetStatement] = (node, parent, state, cb) => {
|
|
for (let headItem of node.head) {
|
|
cb(headItem.id, node, state);
|
|
if (headItem.init) {
|
|
cb(headItem.init, node, state);
|
|
}
|
|
}
|
|
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.Literal] = leafNode;
|
|
|
|
walkers[Syntax.LogicalExpression] = walkers[Syntax.BinaryExpression];
|
|
|
|
walkers[Syntax.MemberExpression] = (node, parent, state, cb) => {
|
|
cb(node.object, node, state);
|
|
if (node.property) {
|
|
cb(node.property, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.MetaProperty] = leafNode;
|
|
|
|
walkers[Syntax.MethodDefinition] = (node, parent, state, cb) => {
|
|
if (node.key) {
|
|
cb(node.key, node, state);
|
|
}
|
|
|
|
if (node.value) {
|
|
cb(node.value, node, state);
|
|
}
|
|
|
|
if (node.decorators) {
|
|
for (let decorator of node.decorators) {
|
|
cb(decorator, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ModuleDeclaration] = (node, parent, state, cb) => {
|
|
if (node.id) {
|
|
cb(node.id, node, state);
|
|
}
|
|
|
|
if (node.source) {
|
|
cb(node.source, node, state);
|
|
}
|
|
|
|
if (node.body) {
|
|
cb(node.body, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.NewExpression] = walkers[Syntax.CallExpression];
|
|
|
|
walkers[Syntax.ObjectExpression] = (node, parent, state, cb) => {
|
|
for (let property of node.properties) {
|
|
cb(property, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ObjectPattern] = walkers[Syntax.ObjectExpression];
|
|
|
|
walkers[Syntax.PrivateName] = (node, parent, state, cb) => {
|
|
cb(node.id, node, state);
|
|
};
|
|
|
|
walkers[Syntax.Program] = (node, parent, state, cb) => {
|
|
// if the first item in the body has multiple leading comments, move all but the last one to
|
|
// this node. this happens, for example, when a file has a /** @module */ standalone comment
|
|
// followed by one or more other comments.
|
|
if (node.body[0] && node.body[0].leadingComments && node.body[0].leadingComments.length > 1) {
|
|
moveLeadingComments(node.body[0], node, node.body[0].leadingComments.length - 1);
|
|
}
|
|
|
|
// if the last item in the body has trailing comments, move them to this node
|
|
if (node.body.length && node.body[node.body.length - 1].trailingComments) {
|
|
moveTrailingComments(node.body[node.body.length - 1], node);
|
|
}
|
|
|
|
for (let bodyItem of node.body) {
|
|
cb(bodyItem, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.Property] = (node, parent, state, cb) => {
|
|
// move leading comments from key to property node
|
|
moveLeadingComments(node.key, node);
|
|
|
|
if (node.value) {
|
|
cb(node.value, node, state);
|
|
}
|
|
|
|
if (node.decorators) {
|
|
for (let decorator of node.decorators) {
|
|
cb(decorator, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ClassPrivateProperty] = (node, parent, state, cb) => {
|
|
// move leading comments from key to property node
|
|
moveLeadingComments(node.key, node);
|
|
|
|
// add `name` property to key, so we don't have to give this type of node special treatment
|
|
// when we resolve its name
|
|
node.key.name = node.key.id.name;
|
|
|
|
if (node.value) {
|
|
cb(node.value, node, state);
|
|
}
|
|
|
|
if (node.decorators) {
|
|
for (let decorator of node.decorators) {
|
|
cb(decorator, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ClassProperty] = walkers[Syntax.Property];
|
|
|
|
walkers[Syntax.RestElement] = (node, parent, state, cb) => {
|
|
if (node.argument) {
|
|
cb(node.argument, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ReturnStatement] = (node, parent, state, cb) => {
|
|
if (node.argument) {
|
|
cb(node.argument, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.SequenceExpression] = (node, parent, state, cb) => {
|
|
for (let expression of node.expressions) {
|
|
cb(expression, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.SpreadElement] = (node, parent, state, cb) => {
|
|
if (node.argument) {
|
|
cb(node.argument, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.Super] = leafNode;
|
|
|
|
walkers[Syntax.SwitchCase] = (node, parent, state, cb) => {
|
|
if (node.test) {
|
|
cb(node.test, node, state);
|
|
}
|
|
|
|
for (let consequentItem of node.consequent) {
|
|
cb(consequentItem, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.SwitchStatement] = (node, parent, state, cb) => {
|
|
cb(node.discriminant, node, state);
|
|
|
|
for (let caseItem of node.cases) {
|
|
cb(caseItem, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.TaggedTemplateExpression] = (node, parent, state, cb) => {
|
|
if (node.tag) {
|
|
cb(node.tag, node, state);
|
|
}
|
|
if (node.quasi) {
|
|
cb(node.quasi, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.TemplateElement] = leafNode;
|
|
|
|
walkers[Syntax.TemplateLiteral] = (node, parent, state, cb) => {
|
|
if (node.quasis && node.quasis.length) {
|
|
for (let quasi of node.quasis) {
|
|
cb(quasi, node, state);
|
|
}
|
|
}
|
|
|
|
if (node.expressions && node.expressions.length) {
|
|
for (let expression of node.expressions) {
|
|
cb(expression, node, state);
|
|
}
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.ThisExpression] = leafNode;
|
|
|
|
walkers[Syntax.ThrowStatement] = (node, parent, state, cb) => {
|
|
cb(node.argument, node, state);
|
|
};
|
|
|
|
walkers[Syntax.TryStatement] = (node, parent, state, cb) => {
|
|
cb(node.block, node, state);
|
|
|
|
if (node.handler) {
|
|
cb(node.handler.body, node, state);
|
|
}
|
|
|
|
if (node.finalizer) {
|
|
cb(node.finalizer, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.UnaryExpression] = (node, parent, state, cb) => {
|
|
cb(node.argument, node, state);
|
|
};
|
|
|
|
walkers[Syntax.UpdateExpression] = walkers[Syntax.UnaryExpression];
|
|
|
|
walkers[Syntax.VariableDeclaration] = (node, parent, state, cb) => {
|
|
// move leading comments to first declarator
|
|
moveLeadingComments(node, node.declarations[0]);
|
|
|
|
for (let declaration of node.declarations) {
|
|
cb(declaration, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.VariableDeclarator] = (node, parent, state, cb) => {
|
|
cb(node.id, node, state);
|
|
|
|
if (node.init) {
|
|
cb(node.init, node, state);
|
|
}
|
|
};
|
|
|
|
walkers[Syntax.WhileStatement] = walkers[Syntax.DoWhileStatement];
|
|
|
|
walkers[Syntax.WithStatement] = (node, parent, state, cb) => {
|
|
cb(node.object, node, state);
|
|
cb(node.body, node, state);
|
|
};
|
|
|
|
walkers[Syntax.YieldExpression] = (node, parent, state, cb) => {
|
|
if (node.argument) {
|
|
cb(node.argument, node, state);
|
|
}
|
|
};
|
|
|
|
function handleNode(node, parent, cbState) {
|
|
let currentScope;
|
|
const isScope = astNode.isScope(node);
|
|
const { walker } = cbState;
|
|
|
|
astNode.addNodeProperties(node);
|
|
node.parent = parent || null;
|
|
|
|
currentScope = getCurrentScope(cbState.scopes);
|
|
if (currentScope) {
|
|
node.enclosingScope = currentScope;
|
|
}
|
|
|
|
if (isScope) {
|
|
cbState.scopes.push(node);
|
|
}
|
|
cbState.nodes.push(node);
|
|
|
|
cbState.moduleType ??= astNode.detectModuleType(node);
|
|
|
|
if (!walker._walkers[node.type]) {
|
|
walker._logUnknownNodeType(node);
|
|
} else {
|
|
walker._walkers[node.type](node, parent, cbState, handleNode);
|
|
}
|
|
|
|
if (isScope) {
|
|
cbState.scopes.pop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A walker that can traverse an ESTree AST.
|
|
*/
|
|
export class Walker {
|
|
// TODO: docs
|
|
constructor(env, walkerFuncs = walkers) {
|
|
this._log = env.log;
|
|
this._walkers = walkerFuncs;
|
|
}
|
|
|
|
_logUnknownNodeType({ type }) {
|
|
this._log.debug(
|
|
`Found a node with unrecognized type ${type}. Ignoring the node and its descendants.`
|
|
);
|
|
}
|
|
|
|
// TODO: docs
|
|
recurse(ast, visitor, filename) {
|
|
let shouldContinue;
|
|
const state = {
|
|
filename: filename,
|
|
moduleType: null,
|
|
nodes: [],
|
|
scopes: [],
|
|
walker: this,
|
|
};
|
|
|
|
handleNode(ast, null, state);
|
|
|
|
if (visitor) {
|
|
for (let node of state.nodes) {
|
|
shouldContinue = visitor.visit(node, filename);
|
|
if (!shouldContinue) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
ast,
|
|
filename: state.filename,
|
|
moduleType: state.moduleType,
|
|
};
|
|
}
|
|
}
|