estree-toolkit
Version:
Traverser, scope tracker, and more tools for working with ESTree AST
1,330 lines (1,329 loc) • 34.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.visitorKeys = exports.getFieldsOf = exports.definitions = void 0;
const helpers_1 = require("./helpers");
const a = require("./assert");
const anyValidate = {
validate: a.any
};
exports.definitions = (0, helpers_1.cleanObj)({
Identifier: {
indices: {
name: [0, false]
},
fields: {
name: {
validate: a.chain(a.value('string'), a.validIdentifier(false))
}
},
insertionValidate(node, key, listKey, parent) {
if ((parent.type === 'MemberExpression' && !parent.computed && key === 'property') ||
((parent.type === 'Property' || parent.type === 'MethodDefinition') && !parent.computed && key === 'key') ||
(parent.type === 'ExportSpecifier' && key === 'exported') ||
(parent.type === 'ImportSpecifier' && key === 'imported') ||
(parent.type === 'MetaProperty' && (key === 'meta' && node.name === 'import' || key === 'property' && node.name === 'meta'))) {
return null;
}
if (a.isReserved(node.name)) {
return `${JSON.stringify(node.name)} is not a valid identifier.`;
}
return null;
}
},
Literal: {
indices: {
value: [0, false],
raw: false
},
fields: {
value: {
// ts-expect-error Practically RegExp would never appear here
validate: a.value('string', 'number', 'bigint', 'boolean', 'null')
},
raw: anyValidate
}
},
Program: {
indices: {
body: 0,
sourceType: [1, false],
comments: [2, false]
},
fields: {
body: {
validate: a.arrayOf(a.OR(a.nodeAlias('Statement'), a.nodeAlias('ModuleDeclaration')))
},
sourceType: {
default: 'module',
validate: a.value('string')
},
comments: { default: [], ...anyValidate }
}
},
FunctionDeclaration: {
indices: {
id: 0,
params: 1,
body: 2,
generator: [3, false],
async: [4, false]
},
fields: {
id: {
validate: a.nullable(a.node('Identifier'))
},
params: {
validate: a.arrayOf(a.nodeAlias('Pattern'))
},
body: {
validate: a.node('BlockStatement')
},
generator: {
default: false,
validate: a.value('boolean')
},
async: {
default: false,
validate: a.value('boolean')
}
}
},
FunctionExpression: {
indices: {
id: 0,
params: 1,
body: 2,
generator: [3, false],
async: [4, false]
},
fields: {
id: {
validate: a.nullable(a.node('Identifier'))
},
params: {
validate: a.arrayOf(a.nodeAlias('Pattern'))
},
body: {
validate: a.node('BlockStatement')
},
generator: {
default: false,
validate: a.value('boolean')
},
async: {
default: false,
validate: a.value('boolean')
}
}
},
ArrowFunctionExpression: {
indices: {
params: 0,
body: 1,
expression: [2, false],
async: [3, false],
generator: false
},
fields: {
params: {
validate: a.arrayOf(a.nodeAlias('Pattern'))
},
body: {
validate: a.OR(a.node('BlockStatement'), a.nodeAlias('Expression'))
},
expression: {
default: false,
validate: a.value('boolean')
},
async: {
default: false,
validate: a.value('boolean')
},
generator: anyValidate
}
},
SwitchCase: {
indices: {
test: 0,
consequent: 1
},
fields: {
test: {
validate: a.nullable(a.nodeAlias('Expression'))
},
consequent: {
validate: a.arrayOf(a.nodeAlias('Statement'))
}
}
},
CatchClause: {
indices: {
param: 0,
body: 1
},
fields: {
param: {
validate: a.nullable(a.nodeAlias('Pattern'))
},
body: {
validate: a.node('BlockStatement')
}
}
},
VariableDeclarator: {
indices: {
id: 0,
init: 1
},
fields: {
id: {
validate: a.nodeAlias('Pattern')
},
init: {
default: null,
validate: a.nullable(a.nodeAlias('Expression'))
}
}
},
ExpressionStatement: {
indices: {
expression: 0
},
fields: {
expression: {
validate: a.nodeAlias('Expression')
}
}
},
BlockStatement: {
indices: {
body: 0,
innerComments: false
},
fields: {
body: {
validate: a.arrayOf(a.nodeAlias('Statement'))
},
innerComments: anyValidate
}
},
EmptyStatement: {
indices: {},
fields: {}
},
DebuggerStatement: {
indices: {},
fields: {}
},
WithStatement: {
indices: {
object: 0,
body: 1
},
fields: {
object: {
validate: a.nodeAlias('Expression')
},
body: {
validate: a.nodeAlias('Statement')
}
}
},
ReturnStatement: {
indices: {
argument: 0
},
fields: {
argument: {
default: null,
validate: a.nullable(a.nodeAlias('Expression'))
}
}
},
LabeledStatement: {
indices: {
label: 0,
body: 1
},
fields: {
label: {
validate: a.node('Identifier')
},
body: {
validate: a.nodeAlias('Statement')
}
}
},
BreakStatement: {
indices: {
label: 0
},
fields: {
label: {
default: null,
validate: a.nullable(a.node('Identifier'))
}
}
},
ContinueStatement: {
indices: {
label: 0
},
fields: {
label: {
default: null,
validate: a.nullable(a.node('Identifier'))
}
}
},
IfStatement: {
indices: {
test: 0,
consequent: 1,
alternate: 2
},
fields: {
test: {
validate: a.nodeAlias('Expression')
},
consequent: {
validate: a.nodeAlias('Statement')
},
alternate: {
default: null,
validate: a.nullable(a.nodeAlias('Statement'))
}
}
},
SwitchStatement: {
indices: {
discriminant: 0,
cases: 1
},
fields: {
discriminant: {
validate: a.nodeAlias('Expression')
},
cases: {
validate: a.arrayOf(a.node('SwitchCase'))
}
}
},
ThrowStatement: {
indices: {
argument: 0
},
fields: {
argument: {
validate: a.nodeAlias('Expression')
}
}
},
TryStatement: {
indices: {
block: 0,
handler: 1,
finalizer: 2
},
fields: {
block: {
validate: a.node('BlockStatement')
},
handler: {
validate: a.nullable(a.node('CatchClause'))
},
finalizer: {
default: null,
validate: a.nullable(a.node('BlockStatement'))
}
},
finalValidate(node) {
if (node.handler == null && node.finalizer == null) {
return 'If `handler` is null then `finalizer` must be not null';
}
return null;
}
},
WhileStatement: {
indices: {
test: 0,
body: 1
},
fields: {
test: {
validate: a.nodeAlias('Expression')
},
body: {
validate: a.nodeAlias('Statement')
}
}
},
DoWhileStatement: {
indices: {
test: 0,
body: 1
},
fields: {
test: {
validate: a.nodeAlias('Expression')
},
body: {
validate: a.nodeAlias('Statement')
}
}
},
ForStatement: {
indices: {
init: 0,
test: 1,
update: 2,
body: 3
},
fields: {
init: {
validate: a.nullable(a.OR(a.node('VariableDeclaration'), a.nodeAlias('Expression')))
},
test: {
validate: a.nullable(a.nodeAlias('Expression'))
},
update: {
validate: a.nullable(a.nodeAlias('Expression'))
},
body: {
validate: a.nodeAlias('Statement')
}
}
},
ForInStatement: {
indices: {
left: 0,
right: 1,
body: 2
},
fields: {
left: {
validate: a.OR(a.node('VariableDeclaration'), a.nodeAlias('Pattern'))
},
right: {
validate: a.nodeAlias('Expression')
},
body: {
validate: a.nodeAlias('Statement')
}
}
},
ForOfStatement: {
indices: {
left: 0,
right: 1,
body: 2,
await: [3, false]
},
fields: {
left: {
validate: a.OR(a.node('VariableDeclaration'), a.nodeAlias('Pattern'))
},
right: {
validate: a.nodeAlias('Expression')
},
body: {
validate: a.nodeAlias('Statement')
},
await: {
validate: a.value('boolean')
}
}
},
VariableDeclaration: {
indices: {
kind: [0, false],
declarations: 1
},
fields: {
kind: {
validate: a.oneOf(['var', 'let', 'const'])
},
declarations: {
validate: a.arrayOf(a.node('VariableDeclarator'))
}
}
},
ClassDeclaration: {
indices: {
id: 0,
body: 2,
superClass: [3, 1]
},
fields: {
id: {
validate: a.nullable(a.node('Identifier'))
},
body: {
validate: a.node('ClassBody')
},
superClass: {
default: null,
validate: a.nullable(a.nodeAlias('Expression'))
}
}
},
ThisExpression: {
indices: {},
fields: {}
},
ArrayExpression: {
indices: {
elements: 0
},
fields: {
elements: {
validate: a.arrayOf(a.nullable(a.OR(a.nodeAlias('Expression'), a.node('SpreadElement'))))
}
}
},
ObjectExpression: {
indices: {
properties: 0
},
fields: {
properties: {
validate: a.arrayOf(a.node('Property', 'SpreadElement'))
}
}
},
YieldExpression: {
indices: {
argument: 0,
delegate: [1, false]
},
fields: {
argument: {
validate: a.nullable(a.nodeAlias('Expression'))
},
delegate: {
default: false,
validate: a.value('boolean')
}
}
},
UnaryExpression: {
indices: {
operator: [0, false],
argument: 1,
prefix: [2, false]
},
fields: {
operator: {
validate: a.oneOf(['-', '+', '!', '~', 'typeof', 'void', 'delete'])
},
argument: {
validate: a.nodeAlias('Expression')
},
prefix: {
default: true,
validate: a.value('boolean')
}
}
},
UpdateExpression: {
indices: {
operator: [0, false],
argument: 1,
prefix: [2, false]
},
fields: {
operator: {
validate: a.oneOf(['++', '--'])
},
argument: {
validate: a.nodeAlias('Expression')
},
prefix: {
validate: a.value('boolean')
}
}
},
BinaryExpression: {
indices: {
operator: [0, false],
left: 1,
right: 2
},
fields: {
operator: {
validate: a.oneOf([
'==', '!=', '===', '!==', '<', '<=', '>', '>=', '<<', '>>', '>>>',
'+', '-', '*', '/', '%', '**', '|', '^', '&', 'in', 'instanceof'
])
},
left: {
validate: a.OR(a.nodeAlias('Expression'), a.node('PrivateIdentifier'))
},
right: {
validate: a.nodeAlias('Expression')
}
}
},
AssignmentExpression: {
indices: {
operator: [0, false],
left: 1,
right: 2
},
fields: {
operator: {
validate: a.oneOf([
'=', '+=', '-=', '*=', '/=', '%=', '**=', '<<=', '>>=', '>>>=',
'|=', '^=', '&=', '||=', '&&=', '??=',
])
},
left: {
validate: a.nodeAlias('Pattern')
},
right: {
validate: a.nodeAlias('Expression')
}
}
},
LogicalExpression: {
indices: {
operator: [0, false],
left: 1,
right: 2
},
fields: {
operator: {
validate: a.oneOf(['||', '&&', '??'])
},
left: {
validate: a.nodeAlias('Expression')
},
right: {
validate: a.nodeAlias('Expression')
}
}
},
MemberExpression: {
indices: {
object: 0,
property: 1,
computed: [2, false],
optional: [3, false]
},
fields: {
object: {
validate: a.OR(a.nodeAlias('Expression'), a.node('Super'))
},
property: {
validate: a.OR(a.nodeAlias('Expression'), a.node('PrivateIdentifier'))
},
computed: {
default: false,
validate: a.value('boolean')
},
optional: {
default: false,
validate: a.value('boolean')
}
}
},
ConditionalExpression: {
indices: {
test: 0,
consequent: 1,
alternate: 2
},
fields: {
test: {
validate: a.nodeAlias('Expression')
},
consequent: {
validate: a.nodeAlias('Expression')
},
alternate: {
validate: a.nodeAlias('Expression')
}
}
},
CallExpression: {
indices: {
callee: 0,
arguments: 1,
optional: [2, false]
},
fields: {
callee: {
validate: a.OR(a.nodeAlias('Expression'), a.node('Super'))
},
arguments: {
validate: a.arrayOf(a.OR(a.nodeAlias('Expression'), a.node('SpreadElement')))
},
optional: {
type: 'boolean',
default: false,
validate: a.value('boolean')
}
}
},
NewExpression: {
indices: {
callee: 0,
arguments: 1
},
fields: {
callee: {
validate: a.OR(a.nodeAlias('Expression'), a.node('Super'))
},
arguments: {
validate: a.arrayOf(a.OR(a.nodeAlias('Expression'), a.node('SpreadElement')))
}
}
},
SequenceExpression: {
indices: {
expressions: 0
},
fields: {
expressions: {
validate: a.arrayOf(a.nodeAlias('Expression'))
}
}
},
TemplateLiteral: {
indices: {
quasis: 0,
expressions: 1
},
fields: {
quasis: {
validate: a.arrayOf(a.node('TemplateElement'))
},
expressions: {
validate: a.arrayOf(a.nodeAlias('Expression'))
}
}
},
TaggedTemplateExpression: {
indices: {
tag: 0,
quasi: 1
},
fields: {
tag: {
validate: a.nodeAlias('Expression')
},
quasi: {
validate: a.node('TemplateLiteral')
}
}
},
ClassExpression: {
indices: {
id: 0,
body: 2,
superClass: [3, 1]
},
fields: {
id: {
validate: a.nullable(a.node('Identifier'))
},
body: {
validate: a.node('ClassBody')
},
superClass: {
default: null,
validate: a.nullable(a.nodeAlias('Expression'))
}
}
},
MetaProperty: {
indices: {
meta: 0,
property: 1
},
fields: {
meta: {
validate: a.node('Identifier')
},
property: {
validate: a.node('Identifier')
}
}
},
AwaitExpression: {
indices: {
argument: 0
},
fields: {
argument: {
validate: a.nodeAlias('Expression')
}
}
},
ImportExpression: {
indices: {
source: 0,
options: 1
},
fields: {
source: {
validate: a.nodeAlias('Expression')
},
options: {
validate: a.nullable(a.nodeAlias('Expression')),
default: null
}
}
},
ChainExpression: {
indices: {
expression: 0
},
fields: {
expression: {
validate: a.node('CallExpression', 'MemberExpression')
}
}
},
Property: {
indices: {
kind: [0, false],
key: 1,
value: 2,
computed: [3, false],
shorthand: [4, false],
method: false
},
fields: {
kind: {
validate: a.oneOf(['init', 'get', 'set'])
},
key: {
validate: a.OR(a.nodeAlias('Expression'), a.node('PrivateIdentifier'))
},
value: {
validate: a.OR(a.nodeAlias('Expression'), a.nodeAlias('Pattern'), a.node('Property'))
},
computed: {
default: false,
validate: a.value('boolean')
},
shorthand: {
default: false,
validate: a.value('boolean')
},
method: {
validate: a.value('boolean')
}
}
},
Super: {
indices: {},
fields: {}
},
TemplateElement: {
indices: {
value: [0, false],
tail: [1, false]
},
fields: {
value: {
validate: a.nonNull
},
tail: {
validate: a.value('boolean')
}
}
},
SpreadElement: {
indices: {
argument: 0
},
fields: {
argument: {
validate: a.nodeAlias('Expression')
}
}
},
ObjectPattern: {
indices: {
properties: 0
},
fields: {
properties: {
validate: a.arrayOf(a.node('Property', 'RestElement'))
}
}
},
ArrayPattern: {
indices: {
elements: 0
},
fields: {
elements: {
validate: a.arrayOf(a.nullable(a.nodeAlias('Pattern')))
}
}
},
RestElement: {
indices: {
argument: 0
},
fields: {
argument: {
validate: a.nodeAlias('Pattern')
}
},
insertionValidate(node, key, listKey, parent) {
if ((parent[listKey]).length > key) {
return `RestElement should be the last children of "${listKey}"`;
}
return null;
}
},
AssignmentPattern: {
indices: {
left: 0,
right: 1
},
fields: {
left: {
validate: a.nodeAlias('Pattern')
},
right: {
validate: a.nodeAlias('Expression')
}
}
},
ClassBody: {
indices: {
body: 0
},
fields: {
body: {
validate: a.arrayOf(a.node('StaticBlock', 'PropertyDefinition', 'MethodDefinition'))
}
}
},
MethodDefinition: {
indices: {
kind: [0, false],
key: 1,
value: 2,
computed: [3, false],
static: [4, false]
},
fields: {
kind: {
validate: a.oneOf(['method', 'get', 'set', 'constructor'])
},
key: {
validate: a.OR(a.nodeAlias('Expression'), a.node('PrivateIdentifier'))
},
value: {
validate: a.node('FunctionExpression')
},
computed: {
default: false,
validate: a.value('boolean')
},
static: {
default: false,
validate: a.value('boolean')
}
}
},
ImportDeclaration: {
indices: {
specifiers: 0,
source: 1,
attributes: 2,
},
fields: {
specifiers: {
validate: a.arrayOf(a.node('ImportSpecifier', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier'))
},
source: {
validate: a.node('Literal')
},
attributes: {
default: () => [],
validate: a.arrayOf(a.node('ImportAttribute'))
}
}
},
ExportNamedDeclaration: {
indices: {
declaration: 0,
specifiers: 1,
source: 2,
attributes: 3
},
fields: {
declaration: {
validate: a.nullable(a.nodeAlias('Declaration'))
},
specifiers: {
default: [],
validate: a.arrayOf(a.node('ExportSpecifier'))
},
source: {
default: null,
validate: a.nullable(a.node('Literal'))
},
attributes: {
default: () => [],
validate: a.arrayOf(a.node('ImportAttribute'))
}
}
},
ExportDefaultDeclaration: {
indices: {
// @ts-expect-error the `estree` package made types more complex
declaration: 0
},
fields: {
declaration: {
// @ts-expect-error the `estree` package made types more complex
validate: a.OR(a.nodeAlias('Declaration'), a.nodeAlias('Expression'))
}
}
},
ExportAllDeclaration: {
indices: {
source: 0,
exported: 1,
attributes: 2
},
fields: {
source: {
validate: a.node('Literal')
},
exported: {
default: null,
validate: a.nullable(a.OR(a.node('Identifier'), a.node('Literal')))
},
attributes: {
default: () => [],
validate: a.arrayOf(a.node('ImportAttribute'))
}
}
},
ImportSpecifier: {
indices: {
imported: 0,
local: 1
},
fields: {
imported: {
validate: a.OR(a.node('Identifier'), a.node('Literal'))
},
local: {
default: (node) => {
if (node.imported.type === 'Identifier') {
return { type: 'Identifier', name: node.imported.name };
}
throw new Error('Provide `local` value when `imported` is not an `Identifier`');
},
validate: a.node('Identifier')
}
}
},
ImportDefaultSpecifier: {
indices: {
local: 0
},
fields: {
local: {
validate: a.node('Identifier')
}
}
},
ImportNamespaceSpecifier: {
indices: {
local: 0
},
fields: {
local: {
validate: a.node('Identifier')
}
}
},
ExportSpecifier: {
indices: {
local: 0,
exported: 1
},
fields: {
local: {
validate: a.OR(a.node('Identifier'), a.node('Literal'))
},
exported: {
default: ({ local }) => structuredClone(local),
validate: a.OR(a.node('Identifier'), a.node('Literal'))
}
}
},
PrivateIdentifier: {
indices: {
name: [1, false]
},
fields: {
name: {
validate: a.chain(a.value('string'), a.validIdentifier(false))
}
}
},
PropertyDefinition: {
indices: {
key: 0,
value: 1,
computed: [2, false],
static: [3, false]
},
fields: {
key: {
validate: a.OR(a.nodeAlias('Expression'), a.node('PrivateIdentifier'))
},
value: {
validate: a.nullable(a.nodeAlias('Expression'))
},
computed: {
default: false,
validate: a.value('boolean')
},
static: {
default: false,
validate: a.value('boolean')
}
}
},
StaticBlock: {
indices: {
body: 0,
innerComments: false
},
fields: {
body: {
validate: a.arrayOf(a.nodeAlias('Statement'))
},
innerComments: anyValidate
}
},
ImportAttribute: {
indices: {
key: 0,
value: 1
},
fields: {
key: {
validate: a.node('Identifier', 'Literal')
},
value: {
validate: a.node('Literal'),
}
}
},
/// JSX
JSXIdentifier: {
indices: {
name: [0, false]
},
fields: {
name: {
validate: a.chain(a.value('string'), a.validIdentifier(true))
}
}
},
JSXNamespacedName: {
indices: {
namespace: 0,
name: 1,
},
fields: {
namespace: {
validate: a.node('JSXIdentifier')
},
name: {
validate: a.node('JSXIdentifier')
}
}
},
JSXMemberExpression: {
indices: {
object: 0,
property: 0
},
fields: {
object: {
validate: a.node('JSXIdentifier', 'JSXMemberExpression')
},
property: {
validate: a.node('JSXIdentifier')
}
}
},
JSXEmptyExpression: {
indices: {},
fields: {}
},
JSXExpressionContainer: {
indices: {
expression: 0
},
fields: {
expression: {
validate: a.OR(a.nodeAlias('Expression'), a.node('JSXEmptyExpression'))
}
}
},
JSXSpreadAttribute: {
indices: {
argument: 0
},
fields: {
argument: {
validate: a.nodeAlias('Expression')
}
}
},
JSXAttribute: {
indices: {
name: 0,
value: 1
},
fields: {
name: {
validate: a.node('JSXIdentifier', 'JSXNamespacedName')
},
value: {
validate: a.nullable(a.node('Literal', 'JSXExpressionContainer', 'JSXElement', 'JSXFragment'))
}
}
},
JSXClosingElement: {
indices: {
name: 0
},
fields: {
name: {
validate: a.node('JSXIdentifier', 'JSXNamespacedName', 'JSXMemberExpression')
}
}
},
JSXClosingFragment: {
indices: {},
fields: {}
},
JSXElement: {
indices: {
openingElement: 0,
children: [2, 1],
closingElement: [1, 2]
},
fields: {
openingElement: {
validate: a.node('JSXOpeningElement')
},
children: {
validate: a.arrayOf(a.node('JSXExpressionContainer', 'JSXElement', 'JSXFragment', 'JSXText', 'JSXSpreadChild')),
default: []
},
closingElement: {
validate: a.nullable(a.node('JSXClosingElement'))
}
}
},
JSXFragment: {
indices: {
openingFragment: 0,
children: [2, 1],
closingFragment: [1, 2]
},
fields: {
openingFragment: {
validate: a.node('JSXOpeningFragment')
},
children: {
validate: a.arrayOf(a.node('JSXExpressionContainer', 'JSXElement', 'JSXFragment', 'JSXText', 'JSXSpreadChild')),
default: []
},
closingFragment: {
validate: a.node('JSXClosingFragment')
}
}
},
JSXOpeningElement: {
indices: {
name: 0,
attributes: 1,
selfClosing: [2, false]
},
fields: {
name: {
validate: a.node('JSXIdentifier', 'JSXNamespacedName', 'JSXMemberExpression')
},
attributes: {
validate: a.arrayOf(a.node('JSXSpreadAttribute', 'JSXAttribute')),
default: []
},
selfClosing: {
validate: a.value('boolean'),
default: false
}
}
},
JSXOpeningFragment: {
indices: {},
fields: {}
},
JSXSpreadChild: {
indices: {
expression: 0
},
fields: {
expression: {
validate: a.nodeAlias('Expression')
}
}
},
JSXText: {
indices: {
value: [0, false],
raw: false
},
fields: {
value: {
validate: a.value('string')
},
raw: anyValidate
}
}
});
const getFieldsOf = ({ indices }, type) => {
const fields = [];
Object.keys(indices).forEach((fieldName) => {
const indexValue = indices[fieldName];
if (indexValue === false)
return;
switch (typeof indexValue) {
case 'number':
return fields.push({ name: fieldName, index: indexValue });
case 'object': {
const index = indexValue[type === 'builder' ? 0 : 1];
if (index === false)
return;
return fields.push({ name: fieldName, index: index });
}
}
});
return fields.sort((a, b) => a.index - b.index).map(({ name }) => name);
};
exports.getFieldsOf = getFieldsOf;
exports.visitorKeys = (() => {
const record = Object.create(null);
for (const nodeType in exports.definitions) {
record[nodeType] = (0, exports.getFieldsOf)(exports.definitions[nodeType], 'visitor');
}
return record;
})();