2025-04-19 23:12:19 -04:00

183 lines
5.6 KiB
JavaScript

'use strict';
/* eslint-disable jsdoc/valid-types -- doesn't allow `readonly`.
TODO: remove eslint-disable when https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/164 is fixed
*/
/**
* @typedef {{ readonly [type: string]: ReadonlyArray<string> }} VisitorKeys
*/
/* eslint-enable jsdoc/valid-types -- doesn't allow `readonly string[]`. TODO: check why */
/**
* @type {VisitorKeys}
*/
const KEYS = {
ArrayExpression: ['elements'],
ArrayPattern: ['elements'],
ArrowFunctionExpression: ['params', 'body'],
AssignmentExpression: ['left', 'right'],
AssignmentPattern: ['left', 'right'],
AwaitExpression: ['argument'],
BinaryExpression: ['left', 'right'],
BlockStatement: ['body'],
BreakStatement: ['label'],
CallExpression: ['callee', 'arguments'],
CatchClause: ['param', 'body'],
ChainExpression: ['expression'],
ClassBody: ['body'],
ClassDeclaration: ['id', 'superClass', 'body'],
ClassExpression: ['id', 'superClass', 'body'],
ConditionalExpression: ['test', 'consequent', 'alternate'],
ContinueStatement: ['label'],
DebuggerStatement: [],
DoWhileStatement: ['body', 'test'],
EmptyStatement: [],
ExperimentalRestProperty: ['argument'],
ExperimentalSpreadProperty: ['argument'],
ExportAllDeclaration: ['exported', 'source', 'attributes'],
ExportDefaultDeclaration: ['declaration'],
ExportNamedDeclaration: ['declaration', 'specifiers', 'source', 'attributes'],
ExportSpecifier: ['exported', 'local'],
ExpressionStatement: ['expression'],
ForInStatement: ['left', 'right', 'body'],
ForOfStatement: ['left', 'right', 'body'],
ForStatement: ['init', 'test', 'update', 'body'],
FunctionDeclaration: ['id', 'params', 'body'],
FunctionExpression: ['id', 'params', 'body'],
Identifier: [],
IfStatement: ['test', 'consequent', 'alternate'],
ImportAttribute: ['key', 'value'],
ImportDeclaration: ['specifiers', 'source', 'attributes'],
ImportDefaultSpecifier: ['local'],
ImportExpression: ['source', 'options'],
ImportNamespaceSpecifier: ['local'],
ImportSpecifier: ['imported', 'local'],
JSXAttribute: ['name', 'value'],
JSXClosingElement: ['name'],
JSXClosingFragment: [],
JSXElement: ['openingElement', 'children', 'closingElement'],
JSXEmptyExpression: [],
JSXExpressionContainer: ['expression'],
JSXFragment: ['openingFragment', 'children', 'closingFragment'],
JSXIdentifier: [],
JSXMemberExpression: ['object', 'property'],
JSXNamespacedName: ['namespace', 'name'],
JSXOpeningElement: ['name', 'attributes'],
JSXOpeningFragment: [],
JSXSpreadAttribute: ['argument'],
JSXSpreadChild: ['expression'],
JSXText: [],
LabeledStatement: ['label', 'body'],
Literal: [],
LogicalExpression: ['left', 'right'],
MemberExpression: ['object', 'property'],
MetaProperty: ['meta', 'property'],
MethodDefinition: ['key', 'value'],
NewExpression: ['callee', 'arguments'],
ObjectExpression: ['properties'],
ObjectPattern: ['properties'],
PrivateIdentifier: [],
Program: ['body'],
Property: ['key', 'value'],
PropertyDefinition: ['key', 'value'],
RestElement: ['argument'],
ReturnStatement: ['argument'],
SequenceExpression: ['expressions'],
SpreadElement: ['argument'],
StaticBlock: ['body'],
Super: [],
SwitchCase: ['test', 'consequent'],
SwitchStatement: ['discriminant', 'cases'],
TaggedTemplateExpression: ['tag', 'quasi'],
TemplateElement: [],
TemplateLiteral: ['quasis', 'expressions'],
ThisExpression: [],
ThrowStatement: ['argument'],
TryStatement: ['block', 'handler', 'finalizer'],
UnaryExpression: ['argument'],
UpdateExpression: ['argument'],
VariableDeclaration: ['declarations'],
VariableDeclarator: ['id', 'init'],
WhileStatement: ['test', 'body'],
WithStatement: ['object', 'body'],
YieldExpression: ['argument'],
};
// Types.
const NODE_TYPES = Object.keys(KEYS);
// Freeze the keys.
for (const type of NODE_TYPES) {
Object.freeze(KEYS[type]);
}
Object.freeze(KEYS);
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
/**
* @typedef {import('./visitor-keys.js').VisitorKeys} VisitorKeys
*/
// List to ignore keys.
const KEY_BLACKLIST = new Set([
'parent',
'leadingComments',
'trailingComments',
]);
/**
* Check whether a given key should be used or not.
* @param {string} key The key to check.
* @returns {boolean} `true` if the key should be used.
*/
function filterKey(key) {
return !KEY_BLACKLIST.has(key) && key[0] !== '_';
}
/* eslint-disable jsdoc/valid-types -- doesn't allow `readonly`.
TODO: remove eslint-disable when https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/164 is fixed
*/
/**
* Get visitor keys of a given node.
* @param {Object} node The AST node to get keys.
* @returns {readonly string[]} Visitor keys of the node.
*/
function getKeys(node) {
return Object.keys(node).filter(filterKey);
}
/* eslint-enable jsdoc/valid-types -- doesn't allow `readonly` */
/**
* Make the union set with `KEYS` and given keys.
* @param {VisitorKeys} additionalKeys The additional keys.
* @returns {VisitorKeys} The union set.
*/
function unionWith(additionalKeys) {
const retv =
/** @type {{ [type: string]: ReadonlyArray<string> }} */
(Object.assign({}, KEYS));
for (const type of Object.keys(additionalKeys)) {
if (Object.hasOwn(retv, type)) {
const keys = new Set(additionalKeys[type]);
for (const key of retv[type]) {
keys.add(key);
}
retv[type] = Object.freeze(Array.from(keys));
} else {
retv[type] = Object.freeze(Array.from(additionalKeys[type]));
}
}
return Object.freeze(retv);
}
exports.KEYS = KEYS;
exports.getKeys = getKeys;
exports.unionWith = unionWith;