2024-12-04 02:59:39 +01:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
|
2024-12-06 23:26:11 +01:00
|
|
|
// @ts-check
|
|
|
|
|
2024-12-04 02:59:39 +01:00
|
|
|
import { core } from "ext:core/mod.js";
|
|
|
|
const {
|
2024-12-03 13:45:31 +01:00
|
|
|
op_lint_get_rule,
|
|
|
|
op_lint_get_source,
|
|
|
|
op_lint_report,
|
2024-12-04 02:59:39 +01:00
|
|
|
} = core.ops;
|
2024-12-01 04:53:47 +01:00
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
/** @typedef {{ plugins: Deno.LintPlugin[], installedPlugins: Set<string> }} LintState */
|
2024-12-04 12:42:55 +01:00
|
|
|
|
|
|
|
/** @type {LintState} */
|
2024-12-04 03:29:05 +01:00
|
|
|
const state = {
|
2024-12-04 12:42:55 +01:00
|
|
|
plugins: [],
|
2024-12-10 04:33:33 +01:00
|
|
|
installedPlugins: new Set(),
|
2024-12-04 03:29:05 +01:00
|
|
|
};
|
|
|
|
|
2024-12-07 00:24:36 +01:00
|
|
|
/** @implements {Deno.LintRuleContext} */
|
2024-12-01 04:53:47 +01:00
|
|
|
export class Context {
|
|
|
|
id;
|
|
|
|
|
|
|
|
fileName;
|
|
|
|
|
2024-12-04 12:42:55 +01:00
|
|
|
#source = null;
|
|
|
|
|
2024-12-06 23:26:11 +01:00
|
|
|
/**
|
|
|
|
* @param {string} id
|
|
|
|
* @param {string} fileName
|
|
|
|
*/
|
2024-12-01 04:53:47 +01:00
|
|
|
constructor(id, fileName) {
|
|
|
|
this.id = id;
|
|
|
|
this.fileName = fileName;
|
|
|
|
}
|
|
|
|
|
2024-12-03 13:45:31 +01:00
|
|
|
source() {
|
2024-12-04 12:42:55 +01:00
|
|
|
if (this.#source === null) {
|
|
|
|
this.#source = op_lint_get_source();
|
|
|
|
}
|
2024-12-07 00:24:36 +01:00
|
|
|
return /** @type {*} */ (this.#source);
|
2024-12-03 13:45:31 +01:00
|
|
|
}
|
|
|
|
|
2024-12-02 02:27:30 +01:00
|
|
|
report(data) {
|
2024-12-13 00:33:44 +01:00
|
|
|
const range = data.node ? data.node.range : data.range ? data.range : null;
|
|
|
|
if (range == null) {
|
2024-12-03 03:11:26 +01:00
|
|
|
throw new Error(
|
|
|
|
"Either `node` or `span` must be provided when reporting an error",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const start = range[0] - 1;
|
|
|
|
const end = range[1] - 1;
|
|
|
|
|
2024-12-02 18:29:30 +01:00
|
|
|
op_lint_report(
|
|
|
|
this.id,
|
|
|
|
this.fileName,
|
|
|
|
data.message,
|
2024-12-03 03:11:26 +01:00
|
|
|
start,
|
|
|
|
end,
|
2024-12-02 18:29:30 +01:00
|
|
|
);
|
2024-12-01 04:53:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
/**
|
|
|
|
* @param {Deno.LintPlugin} plugin
|
|
|
|
*/
|
2024-12-04 04:30:32 +01:00
|
|
|
export function installPlugin(plugin) {
|
|
|
|
if (typeof plugin !== "object") {
|
|
|
|
throw new Error("Linter plugin must be an object");
|
|
|
|
}
|
|
|
|
if (typeof plugin.name !== "string") {
|
|
|
|
throw new Error("Linter plugin name must be a string");
|
|
|
|
}
|
|
|
|
if (typeof plugin.rules !== "object") {
|
|
|
|
throw new Error("Linter plugin rules must be an object");
|
|
|
|
}
|
2024-12-10 04:33:33 +01:00
|
|
|
if (state.installedPlugins.has(plugin.name)) {
|
2024-12-04 04:30:32 +01:00
|
|
|
throw new Error(`Linter plugin ${plugin.name} has already been registered`);
|
|
|
|
}
|
2024-12-10 04:33:33 +01:00
|
|
|
state.plugins.push(plugin);
|
|
|
|
state.installedPlugins.add(plugin.name);
|
2024-12-04 03:29:05 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 21:17:02 +01:00
|
|
|
// Keep in sync with Rust
|
|
|
|
/**
|
|
|
|
* @enum {number}
|
|
|
|
*/
|
|
|
|
const Flags = {
|
|
|
|
ProgramModule: 0b00000001,
|
|
|
|
FnAsync: 0b00000001,
|
|
|
|
FnGenerator: 0b00000010,
|
|
|
|
FnDeclare: 0b00000100,
|
2024-12-12 15:20:36 +01:00
|
|
|
FnOptional: 0b00001000,
|
2024-12-10 21:17:02 +01:00
|
|
|
MemberComputed: 0b00000001,
|
2024-12-12 15:20:36 +01:00
|
|
|
MemberOptional: 0b00000010,
|
2024-12-10 21:17:02 +01:00
|
|
|
PropShorthand: 0b00000001,
|
|
|
|
PropComputed: 0b00000010,
|
|
|
|
PropGetter: 0b00000100,
|
|
|
|
PropSetter: 0b00001000,
|
|
|
|
PropMethod: 0b00010000,
|
|
|
|
VarVar: 0b00000001,
|
|
|
|
VarConst: 0b00000010,
|
|
|
|
VarLet: 0b00000100,
|
|
|
|
VarDeclare: 0b00001000,
|
2024-12-10 22:48:54 +01:00
|
|
|
ExportType: 0b000000001,
|
|
|
|
TplTail: 0b000000001,
|
2024-12-10 23:05:38 +01:00
|
|
|
ForAwait: 0b000000001,
|
|
|
|
LogicalOr: 0b000000001,
|
|
|
|
LogicalAnd: 0b000000010,
|
|
|
|
LogicalNullishCoalescin: 0b000000100,
|
2024-12-11 12:47:45 +01:00
|
|
|
JSXSelfClosing: 0b000000001,
|
2024-12-11 17:10:05 +01:00
|
|
|
|
|
|
|
BinEqEq: 1,
|
|
|
|
BinNotEq: 2,
|
|
|
|
BinEqEqEq: 3,
|
|
|
|
BinNotEqEq: 4,
|
|
|
|
BinLt: 5,
|
|
|
|
BinLtEq: 6,
|
|
|
|
BinGt: 7,
|
|
|
|
BinGtEq: 8,
|
|
|
|
BinLShift: 9,
|
|
|
|
BinRShift: 10,
|
|
|
|
BinZeroFillRShift: 11,
|
|
|
|
BinAdd: 12,
|
|
|
|
BinSub: 13,
|
|
|
|
BinMul: 14,
|
|
|
|
BinDiv: 15,
|
|
|
|
BinMod: 16,
|
|
|
|
BinBitOr: 17,
|
|
|
|
BinBitXor: 18,
|
|
|
|
BinBitAnd: 19,
|
|
|
|
BinIn: 20,
|
|
|
|
BinInstanceOf: 21,
|
|
|
|
BinExp: 22,
|
|
|
|
|
|
|
|
UnaryMinus: 1,
|
|
|
|
UnaryPlus: 2,
|
|
|
|
UnaryBang: 3,
|
|
|
|
UnaryTilde: 4,
|
|
|
|
UnaryTypeOf: 5,
|
|
|
|
UnaryVoid: 6,
|
|
|
|
UnaryDelete: 7,
|
2024-12-11 17:41:33 +01:00
|
|
|
|
|
|
|
UpdatePrefix: 0b000000001,
|
|
|
|
UpdatePlusPlus: 0b000000010,
|
|
|
|
UpdateMinusMinus: 0b000000100,
|
2024-12-11 17:47:46 +01:00
|
|
|
|
|
|
|
YieldDelegate: 1,
|
2024-12-12 01:35:57 +01:00
|
|
|
ParamOptional: 1,
|
|
|
|
|
|
|
|
ClassDeclare: 0b000000001,
|
|
|
|
ClassAbstract: 0b000000010,
|
|
|
|
ClassConstructor: 0b000000100,
|
|
|
|
ClassMethod: 0b000001000,
|
|
|
|
ClassPublic: 0b001000000,
|
|
|
|
ClassProtected: 0b010000000,
|
|
|
|
ClassPrivate: 0b100000000,
|
2024-12-10 21:17:02 +01:00
|
|
|
};
|
|
|
|
|
2024-12-05 01:18:08 +01:00
|
|
|
// Keep in sync with Rust
|
2024-12-06 23:26:11 +01:00
|
|
|
/**
|
|
|
|
* @enum {number}
|
|
|
|
*/
|
2024-12-10 04:33:33 +01:00
|
|
|
const AstType = {
|
2024-12-05 01:18:08 +01:00
|
|
|
Invalid: 0,
|
|
|
|
Program: 1,
|
|
|
|
|
|
|
|
Import: 2,
|
2024-12-10 04:33:33 +01:00
|
|
|
ImportDecl: 3,
|
|
|
|
ExportDecl: 4,
|
|
|
|
ExportNamed: 5,
|
|
|
|
ExportDefaultDecl: 6,
|
|
|
|
ExportDefaultExpr: 7,
|
|
|
|
ExportAll: 8,
|
|
|
|
TSImportEquals: 9,
|
|
|
|
TSExportAssignment: 10,
|
|
|
|
TSNamespaceExport: 11,
|
2024-12-05 01:18:08 +01:00
|
|
|
|
|
|
|
// Decls
|
2024-12-12 01:35:57 +01:00
|
|
|
ClassDeclaration: 12,
|
2024-12-11 17:10:05 +01:00
|
|
|
FunctionDeclaration: 13,
|
2024-12-10 21:17:02 +01:00
|
|
|
VariableDeclaration: 14,
|
2024-12-10 04:33:33 +01:00
|
|
|
Using: 15,
|
|
|
|
TsInterface: 16,
|
|
|
|
TsTypeAlias: 17,
|
|
|
|
TsEnum: 18,
|
|
|
|
TsModule: 19,
|
2024-12-05 01:18:08 +01:00
|
|
|
|
|
|
|
// Statements
|
2024-12-06 23:26:11 +01:00
|
|
|
BlockStatement: 20,
|
2024-12-05 01:18:08 +01:00
|
|
|
Empty: 21,
|
2024-12-06 23:26:11 +01:00
|
|
|
DebuggerStatement: 22,
|
|
|
|
WithStatement: 23,
|
|
|
|
ReturnStatement: 24,
|
|
|
|
LabeledStatement: 25,
|
|
|
|
BreakStatement: 26,
|
|
|
|
ContinueStatement: 27,
|
|
|
|
IfStatement: 28,
|
|
|
|
SwitchStatement: 29,
|
2024-12-05 01:18:08 +01:00
|
|
|
SwitchCase: 30,
|
2024-12-06 23:26:11 +01:00
|
|
|
ThrowStatement: 31,
|
|
|
|
TryStatement: 32,
|
|
|
|
WhileStatement: 33,
|
|
|
|
DoWhileStatement: 34,
|
|
|
|
ForStatement: 35,
|
|
|
|
ForInStatement: 36,
|
|
|
|
ForOfStatement: 37,
|
2024-12-05 01:18:08 +01:00
|
|
|
Decl: 38,
|
2024-12-06 23:26:11 +01:00
|
|
|
ExpressionStatement: 39,
|
2024-12-05 01:18:08 +01:00
|
|
|
|
|
|
|
// Expressions
|
|
|
|
This: 40,
|
2024-12-06 23:26:11 +01:00
|
|
|
ArrayExpression: 41,
|
|
|
|
ObjectExpression: 42,
|
|
|
|
FunctionExpression: 43,
|
2024-12-11 17:10:05 +01:00
|
|
|
UnaryExpression: 44,
|
2024-12-11 17:41:33 +01:00
|
|
|
UpdateExpression: 45,
|
2024-12-06 23:26:11 +01:00
|
|
|
BinaryExpression: 46,
|
|
|
|
AssignmentExpression: 47,
|
|
|
|
MemberExpression: 48,
|
2024-12-07 00:24:36 +01:00
|
|
|
Super: 49,
|
2024-12-06 23:26:11 +01:00
|
|
|
ConditionalExpression: 50,
|
|
|
|
CallExpression: 51,
|
|
|
|
NewExpression: 52,
|
2024-12-10 04:33:33 +01:00
|
|
|
ParenthesisExpression: 53,
|
|
|
|
SequenceExpression: 54,
|
|
|
|
Identifier: 55,
|
|
|
|
TemplateLiteral: 56,
|
|
|
|
TaggedTemplateExpression: 57,
|
|
|
|
ArrowFunctionExpression: 58,
|
|
|
|
ClassExpr: 59,
|
2024-12-11 17:47:46 +01:00
|
|
|
YieldExpression: 60,
|
2024-12-10 04:33:33 +01:00
|
|
|
MetaProperty: 61,
|
|
|
|
AwaitExpression: 62,
|
|
|
|
LogicalExpression: 63,
|
|
|
|
TSTypeAssertion: 64,
|
|
|
|
TSConstAssertion: 65,
|
|
|
|
TSNonNull: 66,
|
|
|
|
TSAs: 67,
|
|
|
|
TSInstantiation: 68,
|
|
|
|
TSSatisfies: 69,
|
|
|
|
PrivateIdentifier: 70,
|
2024-12-12 15:20:36 +01:00
|
|
|
ChainExpression: 71,
|
2024-12-10 04:33:33 +01:00
|
|
|
|
|
|
|
StringLiteral: 72,
|
|
|
|
BooleanLiteral: 73,
|
|
|
|
NullLiteral: 74,
|
|
|
|
NumericLiteral: 75,
|
|
|
|
BigIntLiteral: 76,
|
|
|
|
RegExpLiteral: 77,
|
2024-12-05 01:18:08 +01:00
|
|
|
|
|
|
|
// Custom
|
2024-12-11 12:47:45 +01:00
|
|
|
EmptyExpr: 78,
|
|
|
|
SpreadElement: 79,
|
|
|
|
Property: 80,
|
|
|
|
VariableDeclarator: 81,
|
|
|
|
CatchClause: 82,
|
|
|
|
RestElement: 83,
|
|
|
|
ExportSpecifier: 84,
|
|
|
|
TemplateElement: 85,
|
2024-12-12 01:35:57 +01:00
|
|
|
MethodDefinition: 86,
|
2024-12-10 21:17:02 +01:00
|
|
|
|
|
|
|
// Patterns
|
2024-12-12 01:35:57 +01:00
|
|
|
ArrayPattern: 87,
|
|
|
|
AssignmentPattern: 88,
|
|
|
|
ObjectPattern: 89,
|
2024-12-07 00:24:36 +01:00
|
|
|
|
|
|
|
// JSX
|
2024-12-12 01:35:57 +01:00
|
|
|
JSXAttribute: 90,
|
|
|
|
JSXClosingElement: 91,
|
|
|
|
JSXClosingFragment: 92,
|
|
|
|
JSXElement: 93,
|
|
|
|
JSXEmptyExpression: 94,
|
|
|
|
JSXExpressionContainer: 95,
|
|
|
|
JSXFragment: 96,
|
|
|
|
JSXIdentifier: 97,
|
|
|
|
JSXMemberExpression: 98,
|
|
|
|
JSXNamespacedName: 99,
|
|
|
|
JSXOpeningElement: 100,
|
|
|
|
JSXOpeningFragment: 101,
|
|
|
|
JSXSpreadAttribute: 102,
|
|
|
|
JSXSpreadChild: 103,
|
|
|
|
JSXText: 104,
|
2024-12-04 16:21:17 +01:00
|
|
|
};
|
|
|
|
|
2024-12-12 22:16:49 +01:00
|
|
|
const AstTypeName = Object.keys(AstType);
|
|
|
|
|
|
|
|
// Keep in sync with Rust
|
|
|
|
const AstProp = [
|
|
|
|
// Base
|
|
|
|
"parent",
|
|
|
|
"range",
|
|
|
|
"type",
|
|
|
|
"_InternalFlags", // Internal
|
|
|
|
|
|
|
|
// Node
|
|
|
|
"alternate",
|
|
|
|
"argument",
|
|
|
|
"arguments",
|
|
|
|
"async",
|
|
|
|
"attributes",
|
|
|
|
"await",
|
|
|
|
"block",
|
|
|
|
"body",
|
|
|
|
"callee",
|
|
|
|
"cases",
|
|
|
|
"children",
|
|
|
|
"closingElement",
|
|
|
|
"closingFragment",
|
|
|
|
"computed",
|
|
|
|
"consequent",
|
2024-12-13 00:33:44 +01:00
|
|
|
"cooked",
|
2024-12-12 22:16:49 +01:00
|
|
|
"declarations",
|
|
|
|
"declare",
|
|
|
|
"definite",
|
|
|
|
"delegate",
|
2024-12-13 00:33:44 +01:00
|
|
|
"discriminant",
|
2024-12-12 22:16:49 +01:00
|
|
|
"elements",
|
2024-12-13 00:33:44 +01:00
|
|
|
"elementTypes",
|
2024-12-12 22:16:49 +01:00
|
|
|
"expression",
|
|
|
|
"expressions",
|
2024-12-13 00:33:44 +01:00
|
|
|
"exported",
|
2024-12-12 22:16:49 +01:00
|
|
|
"finalizer",
|
|
|
|
"flags",
|
|
|
|
"generator",
|
|
|
|
"handler",
|
|
|
|
"id",
|
|
|
|
"init",
|
|
|
|
"key",
|
|
|
|
"kind",
|
|
|
|
"label",
|
|
|
|
"left",
|
2024-12-13 00:33:44 +01:00
|
|
|
"local",
|
2024-12-12 22:16:49 +01:00
|
|
|
"members",
|
|
|
|
"meta",
|
|
|
|
"method",
|
|
|
|
"name",
|
|
|
|
"namespace",
|
|
|
|
"object",
|
|
|
|
"openingElement",
|
|
|
|
"openingFragment",
|
|
|
|
"operator",
|
|
|
|
"optional",
|
2024-12-13 00:33:44 +01:00
|
|
|
"param",
|
2024-12-12 22:16:49 +01:00
|
|
|
"params",
|
|
|
|
"pattern",
|
|
|
|
"prefix",
|
|
|
|
"properties",
|
|
|
|
"property",
|
|
|
|
"quasi",
|
|
|
|
"quasis",
|
|
|
|
"raw",
|
|
|
|
"returnType",
|
|
|
|
"right",
|
|
|
|
"selfClosing",
|
|
|
|
"shorthand",
|
|
|
|
"source",
|
|
|
|
"specifiers",
|
|
|
|
"tag",
|
|
|
|
"tail",
|
|
|
|
"test",
|
|
|
|
"typeAnnotation",
|
|
|
|
"typeArguments",
|
|
|
|
"typeParameters",
|
2024-12-13 00:33:44 +01:00
|
|
|
"types",
|
2024-12-12 22:16:49 +01:00
|
|
|
"update",
|
|
|
|
"value",
|
|
|
|
];
|
|
|
|
const AST_PROP_PARENT = AstProp.indexOf("parent");
|
|
|
|
const AST_PROP_TYPE = AstProp.indexOf("type");
|
2024-12-13 00:33:44 +01:00
|
|
|
const AST_PROP_BODY = AstProp.indexOf("body");
|
|
|
|
const AST_PROP_CHILDREN = AstProp.indexOf("children");
|
|
|
|
const AST_PROP_ELEMENTS = AstProp.indexOf("elements");
|
|
|
|
const AST_PROP_PROPERTIES = AstProp.indexOf("properties");
|
|
|
|
const AST_PROP_ARGUMENTS = AstProp.indexOf("arguments");
|
|
|
|
const AST_PROP_PARAMS = AstProp.indexOf("params");
|
|
|
|
const AST_PROP_EXPRESSIONS = AstProp.indexOf("expressions");
|
|
|
|
const AST_PROP_QUASIS = AstProp.indexOf("quasis");
|
2024-12-12 22:16:49 +01:00
|
|
|
const AST_PROP_RANGE = AstProp.indexOf("range");
|
|
|
|
const AST_PROP_INTERNAL_FLAGS = AstProp.indexOf("_InternalFlags");
|
|
|
|
const AST_PROP_ATTRIBUTE = AstProp.indexOf("attribute");
|
2024-12-13 00:33:44 +01:00
|
|
|
const AST_PROP_CONSEQUENT = AstProp.indexOf("consequent");
|
|
|
|
const AST_PROP_CASES = AstProp.indexOf("cases");
|
|
|
|
const AST_PROP_SPECIFIERS = AstProp.indexOf("specifiers");
|
|
|
|
const AST_PROP_DECLARATIONS = AstProp.indexOf("declarations");
|
|
|
|
const AST_PROP_MEMBERS = AstProp.indexOf("members");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} type
|
|
|
|
* @param {number} propId
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
function isArrayProp(type, propId) {
|
|
|
|
switch (type) {
|
|
|
|
case AstType.Program:
|
|
|
|
case AstType.BlockStatement:
|
|
|
|
// case AstType.StaticBlock:
|
|
|
|
return propId === AST_PROP_BODY;
|
|
|
|
case AstType.JSXOpeningElement:
|
|
|
|
return propId === AST_PROP_ATTRIBUTE;
|
|
|
|
case AstType.SwitchCase:
|
|
|
|
return propId === AST_PROP_CONSEQUENT;
|
|
|
|
default:
|
|
|
|
return propId === AST_PROP_CHILDREN || propId === AST_PROP_ELEMENTS ||
|
|
|
|
propId === AST_PROP_PROPERTIES || propId === AST_PROP_PARAMS ||
|
|
|
|
propId === AST_PROP_ARGUMENTS || propId === AST_PROP_EXPRESSIONS ||
|
|
|
|
propId === AST_PROP_QUASIS || propId === AST_PROP_CASES ||
|
|
|
|
propId === AST_PROP_SPECIFIERS ||
|
|
|
|
propId === AST_PROP_DECLARATIONS || propId === AST_PROP_MEMBERS;
|
|
|
|
}
|
|
|
|
}
|
2024-12-10 04:33:33 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
2024-12-12 22:16:49 +01:00
|
|
|
* @param {number} offset
|
|
|
|
* @param {number} propId
|
2024-12-10 04:33:33 +01:00
|
|
|
*/
|
2024-12-12 22:16:49 +01:00
|
|
|
function readValue(ctx, offset, propId) {
|
|
|
|
const { buf } = ctx;
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-12 22:16:49 +01:00
|
|
|
if (propId === AST_PROP_TYPE) {
|
|
|
|
const type = buf[offset];
|
|
|
|
return AstTypeName[type];
|
|
|
|
} else if (propId === AST_PROP_RANGE) {
|
|
|
|
const start = readU32(buf, offset + 1 + 4);
|
|
|
|
const end = readU32(buf, offset + 1 + 4 + 4);
|
|
|
|
return [start, end];
|
|
|
|
} else if (propId === AST_PROP_PARENT) {
|
|
|
|
const id = readU32(buf, offset + 1);
|
|
|
|
if (id === 0) return null;
|
|
|
|
|
|
|
|
const target = ctx.idTable[id];
|
|
|
|
return new Node(ctx, target);
|
|
|
|
} else if (propId === AST_PROP_INTERNAL_FLAGS) {
|
2024-12-13 00:33:44 +01:00
|
|
|
// FIXME
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const type = buf[offset];
|
|
|
|
|
|
|
|
// type + parentId + SpanLo + SpanHi
|
|
|
|
offset += 1 + 4 + 4 + 4;
|
|
|
|
|
|
|
|
const propCount = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
|
|
|
|
for (let i = 0; i < propCount; i++) {
|
|
|
|
const searchProp = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
|
|
|
|
const isArray = isArrayProp(type, searchProp);
|
|
|
|
if (searchProp === propId) {
|
|
|
|
// Check for arrays
|
|
|
|
if (isArray) {
|
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
const nodes = new Array(len).fill(null);
|
|
|
|
for (let j = 0; j < len; j++) {
|
|
|
|
nodes.push(new Node(ctx, offset));
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
return nodes;
|
|
|
|
}
|
2024-12-12 22:16:49 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const id = readU32(buf, offset);
|
|
|
|
if (id === 0) return null;
|
2024-12-12 22:16:49 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const target = ctx.idTable[id];
|
|
|
|
return new Node(ctx, target);
|
2024-12-12 22:16:49 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
|
|
|
|
offset += 1;
|
|
|
|
} else if (isArray) {
|
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4 + (len * 4);
|
|
|
|
} else {
|
|
|
|
offset += 4;
|
|
|
|
}
|
2024-12-12 22:16:49 +01:00
|
|
|
}
|
2024-12-13 00:33:44 +01:00
|
|
|
|
|
|
|
return 0;
|
2024-12-10 04:33:33 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const INTERNAL_CTX = Symbol("ctx");
|
|
|
|
const INTERNAL_OFFSET = Symbol("offset");
|
|
|
|
|
2024-12-12 22:16:49 +01:00
|
|
|
class Node {
|
2024-12-13 00:33:44 +01:00
|
|
|
[INTERNAL_CTX];
|
|
|
|
[INTERNAL_OFFSET];
|
2024-12-10 04:33:33 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
2024-12-12 22:16:49 +01:00
|
|
|
* @param {number} offset
|
2024-12-10 04:33:33 +01:00
|
|
|
*/
|
2024-12-12 22:16:49 +01:00
|
|
|
constructor(ctx, offset) {
|
2024-12-13 00:33:44 +01:00
|
|
|
this[INTERNAL_CTX] = ctx;
|
|
|
|
this[INTERNAL_OFFSET] = offset;
|
2024-12-10 04:33:33 +01:00
|
|
|
}
|
|
|
|
}
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
for (let i = 0; i < AstProp.length; i++) {
|
|
|
|
const name = AstProp[i];
|
|
|
|
Object.defineProperty(Node.prototype, name, {
|
|
|
|
get() {
|
|
|
|
return readValue(this[INTERNAL_CTX], this[INTERNAL_OFFSET], i);
|
|
|
|
},
|
|
|
|
});
|
2024-12-12 22:16:49 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.AssignmentExpression} */
|
|
|
|
// class AssignmentExpression extends Node {
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// operator;
|
2024-12-06 23:26:11 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// constructor(ctx, parentId, range, flags, leftId, rightId) {
|
|
|
|
// super(ctx, parentId);
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.#leftId = leftId;
|
|
|
|
// this.#rightId = rightId;
|
|
|
|
// this.range = range;
|
|
|
|
// this.operator = getAssignOperator(flags);
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {number} n
|
|
|
|
* @returns {Deno.AssignmentExpression["operator"]}
|
|
|
|
*/
|
|
|
|
function getAssignOperator(n) {
|
|
|
|
switch (n) {
|
|
|
|
case 0:
|
|
|
|
return "=";
|
|
|
|
case 1:
|
|
|
|
return "+=";
|
|
|
|
case 2:
|
|
|
|
return "-=";
|
|
|
|
case 3:
|
|
|
|
return "*=";
|
|
|
|
case 4:
|
|
|
|
return "/=";
|
|
|
|
case 5:
|
|
|
|
return "%=";
|
|
|
|
case 6:
|
|
|
|
return "<<=";
|
|
|
|
case 7:
|
|
|
|
return ">>=";
|
|
|
|
case 8:
|
|
|
|
return ">>>=";
|
|
|
|
case 9:
|
|
|
|
return "|=";
|
|
|
|
case 10:
|
|
|
|
return "^=";
|
|
|
|
case 11:
|
|
|
|
return "&=";
|
|
|
|
case 12:
|
|
|
|
return "**=";
|
|
|
|
case 13:
|
|
|
|
return "&&=";
|
|
|
|
case 14:
|
|
|
|
return "||=";
|
|
|
|
case 15:
|
|
|
|
return "??=";
|
|
|
|
default:
|
|
|
|
throw new Error(`Unknown operator: ${n}`);
|
2024-12-11 17:41:33 +01:00
|
|
|
}
|
2024-12-13 00:33:44 +01:00
|
|
|
}
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.UpdateExpression} */
|
|
|
|
// class UpdateExpression extends Node {
|
|
|
|
// type = /** @type {const} */ ("UpdateExpression");
|
|
|
|
// range;
|
|
|
|
// prefix;
|
|
|
|
// operator;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} flags
|
|
|
|
// * @param {number} argId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, flags, argId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.range = range;
|
|
|
|
|
|
|
|
// this.prefix = (flags & Flags.UpdatePrefix) !== 0;
|
|
|
|
// this.operator = (flags & Flags.UpdatePlusPlus) !== 0
|
|
|
|
// ? /** @type {const} */ ("++")
|
|
|
|
// : /** @type {const} */ ("--");
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.BinaryExpression} */
|
|
|
|
// class BinaryExpression extends Node {
|
|
|
|
// type = /** @type {const} */ ("BinaryExpression");
|
|
|
|
|
|
|
|
// operator;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} flags
|
|
|
|
// * @param {number} leftId
|
|
|
|
// * @param {number} rightId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, flags, leftId, rightId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.operator = getBinaryOperator(flags);
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {number} n
|
|
|
|
* @returns {Deno.BinaryExpression["operator"]}
|
|
|
|
*/
|
|
|
|
function getBinaryOperator(n) {
|
|
|
|
switch (n) {
|
|
|
|
case 1:
|
|
|
|
return "==";
|
|
|
|
case 2:
|
|
|
|
return "!=";
|
|
|
|
case 3:
|
|
|
|
return "===";
|
|
|
|
case 4:
|
|
|
|
return "!==";
|
|
|
|
case 5:
|
|
|
|
return "<";
|
|
|
|
case 6:
|
|
|
|
return "<=";
|
|
|
|
case 7:
|
|
|
|
return ">";
|
|
|
|
case 8:
|
|
|
|
return ">=";
|
|
|
|
case 9:
|
|
|
|
return "<<";
|
|
|
|
case 10:
|
|
|
|
return ">>";
|
|
|
|
case 11:
|
|
|
|
return ">>>";
|
|
|
|
case 12:
|
|
|
|
return "+";
|
|
|
|
case 13:
|
|
|
|
return "-";
|
|
|
|
case 14:
|
|
|
|
return "*";
|
|
|
|
case 15:
|
|
|
|
return "/";
|
|
|
|
case 16:
|
|
|
|
return "%";
|
|
|
|
case 17:
|
|
|
|
return "|";
|
|
|
|
case 18:
|
|
|
|
return "^";
|
|
|
|
case 19:
|
|
|
|
return "&";
|
|
|
|
case 20:
|
|
|
|
return "in";
|
|
|
|
case 21:
|
|
|
|
return "instanceof";
|
|
|
|
case 22:
|
|
|
|
return "**";
|
|
|
|
default:
|
|
|
|
throw new Error(`Unknown operator: ${n}`);
|
2024-12-11 17:41:33 +01:00
|
|
|
}
|
2024-12-13 00:33:44 +01:00
|
|
|
}
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.CallExpression} */
|
|
|
|
// class CallExpression extends Node {
|
|
|
|
// this.optional = (flags & Flags.FnOptional) !== 0;
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.#calleeId = calleeId;
|
|
|
|
// this.range = range;
|
|
|
|
// this.#typeArgId = typeArgId;
|
|
|
|
// this.#argumentIds = argumentIds;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.FunctionExpression} */
|
|
|
|
// class FunctionExpression extends Node {
|
|
|
|
// constructor(
|
|
|
|
// ) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.async = (flags & Flags.FnAsync) !== 0;
|
|
|
|
// this.generator = (flags & Flags.FnGenerator) !== 0;
|
|
|
|
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.Identifier} */
|
|
|
|
// class Identifier extends Node {
|
|
|
|
// type = /** @type {const} */ ("Identifier");
|
|
|
|
// range;
|
|
|
|
// name = "";
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} nameId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, nameId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.name = getString(ctx, nameId);
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.LogicalExpression} */
|
|
|
|
// class LogicalExpression extends Node {
|
|
|
|
// type = /** @type {const} */ ("LogicalExpression");
|
|
|
|
|
|
|
|
// constructor(ctx, parentId, range, flags, leftId, rightId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.operator = getLogicalOperator(flags);
|
|
|
|
// this.#leftId = leftId;
|
|
|
|
// this.#rightId = rightId;
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {number} n
|
|
|
|
* @returns {Deno.LogicalExpression["operator"]}
|
|
|
|
*/
|
|
|
|
function getLogicalOperator(n) {
|
|
|
|
if ((n & Flags.LogicalAnd) !== 0) {
|
|
|
|
return "&&";
|
|
|
|
} else if ((n & Flags.LogicalOr) !== 0) {
|
|
|
|
return "||";
|
|
|
|
} else if ((n & Flags.LogicalNullishCoalescin) !== 0) {
|
|
|
|
return "??";
|
2024-12-11 17:41:33 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
throw new Error(`Unknown operator: ${n}`);
|
|
|
|
}
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.MemberExpression} */
|
|
|
|
// class MemberExpression extends Node {
|
|
|
|
// constructor(ctx, parentId, range, flags, objId, propId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.computed = (flags & Flags.MemberComputed) !== 0;
|
|
|
|
// this.optional = (flags & Flags.MemberOptional) !== 0;
|
|
|
|
// this.#objId = objId;
|
|
|
|
// this.#propId = propId;
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.PrivateIdentifier} */
|
|
|
|
// class PrivateIdentifier extends Node {
|
|
|
|
// type = /** @type {const} */ ("PrivateIdentifier");
|
|
|
|
// range;
|
|
|
|
// #ctx;
|
|
|
|
// name;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} nameId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, nameId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.range = range;
|
|
|
|
// this.name = getString(ctx, nameId);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.Property} */
|
|
|
|
// class Property extends Node {
|
|
|
|
// type = /** @type {const} */ ("Property");
|
|
|
|
// range;
|
|
|
|
|
|
|
|
// constructor(ctx, parentId, range, keyId, valueId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// // FIXME flags
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.range = range;
|
|
|
|
// this.#keyId = keyId;
|
|
|
|
// this.#valueId = valueId;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.UnaryExpression} */
|
|
|
|
// class UnaryExpression extends Node {
|
|
|
|
// type = /** @type {const} */ ("UnaryExpression");
|
|
|
|
// range;
|
|
|
|
|
|
|
|
// constructor(ctx, parentId, range, flags, exprId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.range = range;
|
|
|
|
// this.operator = getUnaryOperator(flags);
|
|
|
|
// this.#exprId = exprId;
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {number} n
|
|
|
|
* @returns {Deno.UnaryExpression["operator"]}
|
|
|
|
*/
|
|
|
|
function getUnaryOperator(n) {
|
|
|
|
switch (n) {
|
|
|
|
case 1:
|
|
|
|
return "-";
|
|
|
|
case 2:
|
|
|
|
return "+";
|
|
|
|
case 3:
|
|
|
|
return "!";
|
|
|
|
case 4:
|
|
|
|
return "~";
|
|
|
|
case 5:
|
|
|
|
return "typeof";
|
|
|
|
case 6:
|
|
|
|
return "void";
|
|
|
|
case 7:
|
|
|
|
return "delete";
|
2024-12-11 17:41:33 +01:00
|
|
|
}
|
|
|
|
}
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.YieldExpression} */
|
|
|
|
// class YieldExpression extends Node {
|
|
|
|
// type = /** @type {const} */ ("YieldExpression");
|
|
|
|
// range;
|
|
|
|
// get argument() {
|
|
|
|
// return /** @type {Deno.Expression} */ (createAstNode(
|
|
|
|
// this.#ctx,
|
|
|
|
// this.#argId,
|
|
|
|
// ));
|
|
|
|
// }
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// delegate = false;
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// #ctx;
|
|
|
|
// #argId;
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// constructor(ctx, parentId, range, flags, argId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.#argId = argId;
|
|
|
|
// this.range = range;
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// this.delegate = (flags & Flags.YieldDelegate) !== 0;
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// Literals
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.BooleanLiteral} */
|
|
|
|
// class BooleanLiteral extends Node {
|
|
|
|
// type = /** @type {const} */ ("BooleanLiteral");
|
|
|
|
// range;
|
|
|
|
// value = false;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} flags
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, flags) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.value = flags === 1;
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.BigIntLiteral} */
|
|
|
|
// class BigIntLiteral extends Node {
|
|
|
|
// type = /** @type {const} */ ("BigIntLiteral");
|
|
|
|
// range;
|
|
|
|
// value;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} strId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, strId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.range = range;
|
|
|
|
// this.value = BigInt(getString(ctx, strId));
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.NullLiteral} */
|
|
|
|
// class NullLiteral extends Node {
|
|
|
|
// type = /** @type {const} */ ("NullLiteral");
|
|
|
|
// range;
|
|
|
|
// value = null;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.NumericLiteral} */
|
|
|
|
// class NumericLiteral extends Node {
|
|
|
|
// type = /** @type {const} */ ("NumericLiteral");
|
|
|
|
// range;
|
|
|
|
// value = 0;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} strId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, strId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.range = range;
|
|
|
|
// this.value = Number(getString(ctx, strId));
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.RegExpLiteral} */
|
|
|
|
// class RegExpLiteral extends Node {
|
|
|
|
// type = /** @type {const} */ ("RegExpLiteral");
|
|
|
|
// range;
|
|
|
|
// pattern = "";
|
|
|
|
// flags = "";
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} patternId
|
|
|
|
// * @param {number} flagsId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, patternId, flagsId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.range = range;
|
|
|
|
// this.pattern = getString(ctx, patternId);
|
|
|
|
// this.flags = getString(ctx, flagsId);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.StringLiteral} */
|
|
|
|
// class StringLiteral extends Node {
|
|
|
|
// type = /** @type {const} */ ("StringLiteral");
|
|
|
|
// range;
|
|
|
|
// value = "";
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} strId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, strId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
// this.range = range;
|
|
|
|
// this.value = getString(ctx, strId);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.TemplateElement} */
|
|
|
|
// class TemplateElement extends Node {
|
|
|
|
// type = /** @type {const} */ ("TemplateElement");
|
|
|
|
// range;
|
|
|
|
|
|
|
|
// tail = false;
|
|
|
|
// value;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} rawId
|
|
|
|
// * @param {number} cookedId
|
|
|
|
// * @param {boolean} tail
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, rawId, cookedId, tail) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// const raw = getString(ctx, rawId);
|
|
|
|
// this.value = {
|
|
|
|
// raw,
|
|
|
|
// cooked: cookedId === 0 ? raw : getString(ctx, cookedId),
|
|
|
|
// };
|
|
|
|
// this.tail = tail;
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// Patterns
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// /** @implements {Deno.ArrayPattern} */
|
|
|
|
// class ArrayPattern extends Node {
|
|
|
|
// constructor(ctx, parentId, range, flags, elemIds, typeId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.range = range;
|
|
|
|
|
|
|
|
// this.optional = (flags & Flags.ParamOptional) !== 0;
|
|
|
|
// this.#elemIds = elemIds;
|
|
|
|
// this.#typeId = typeId;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.ObjectPattern} */
|
|
|
|
// class ObjectPattern extends Node {
|
|
|
|
// type = /** @type {const} */ ("ObjectPattern");
|
|
|
|
// range;
|
|
|
|
|
|
|
|
// constructor(ctx, parentId, range, flags, elemIds, typeId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.range = range;
|
|
|
|
|
|
|
|
// this.optional = (flags & Flags.ParamOptional) !== 0;
|
|
|
|
// this.#elemIds = elemIds;
|
|
|
|
// this.#typeId = typeId;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.JSXIdentifier} */
|
|
|
|
// class JSXIdentifier extends Node {
|
|
|
|
// type = /** @type {const} */ ("JSXIdentifier");
|
|
|
|
// range;
|
|
|
|
// name;
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * @param {AstContext} ctx
|
|
|
|
// * @param {number} parentId
|
|
|
|
// * @param {Deno.Range} range
|
|
|
|
// * @param {number} nameId
|
|
|
|
// */
|
|
|
|
// constructor(ctx, parentId, range, nameId) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.range = range;
|
|
|
|
// this.name = getString(ctx, nameId);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// /** @implements {Deno.JSXOpeningElement} */
|
|
|
|
// class JSXOpeningElement extends Node {
|
|
|
|
// constructor(ctx, parentId, range, isSelfClosing, nameId, attrIds) {
|
|
|
|
// super(ctx, parentId);
|
|
|
|
|
|
|
|
// this.#ctx = ctx;
|
|
|
|
// this.selfClosing = isSelfClosing;
|
|
|
|
// this.#nameId = nameId;
|
|
|
|
// this.#attrIds = attrIds;
|
|
|
|
// this.range = range;
|
|
|
|
// }
|
|
|
|
// }
|
2024-12-10 21:17:02 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const DECODER = new TextDecoder();
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @typedef {{
|
|
|
|
* buf: Uint8Array,
|
|
|
|
* strTable: Map<number, string>,
|
|
|
|
* idTable: number[],
|
|
|
|
* rootId: number
|
|
|
|
* }} AstContext
|
|
|
|
*/
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {Uint8Array} buf
|
|
|
|
* @param {number} i
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readU32(buf, i) {
|
|
|
|
return (buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) +
|
|
|
|
buf[i + 3];
|
2024-12-05 01:18:08 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} id
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
function getString(ctx, id) {
|
|
|
|
const name = ctx.strTable.get(id);
|
|
|
|
if (name === undefined) {
|
|
|
|
throw new Error(`Missing string id: ${id}`);
|
2024-12-06 23:26:11 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
return name;
|
|
|
|
}
|
2024-12-06 23:26:11 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
/**
|
|
|
|
* @param {Uint8Array} buf
|
|
|
|
* @param {AstContext} buf
|
|
|
|
*/
|
|
|
|
function createAstContext(buf) {
|
|
|
|
/** @type {Map<number, string>} */
|
|
|
|
const strTable = new Map();
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// console.log(JSON.stringify(buf, null, 2));
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const strTableOffset = readU32(buf, buf.length - 12);
|
|
|
|
const idTableOffset = readU32(buf, buf.length - 8);
|
|
|
|
const rootId = readU32(buf, buf.length - 4);
|
|
|
|
// console.log({ strTableOffset, idTableOffset, rootId });
|
|
|
|
|
|
|
|
let offset = strTableOffset;
|
|
|
|
const stringCount = readU32(buf, offset);
|
|
|
|
offset += 4;
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
let id = 0;
|
|
|
|
for (let i = 0; i < stringCount; i++) {
|
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4;
|
2024-12-06 23:26:11 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const strBytes = buf.slice(offset, offset + len);
|
|
|
|
offset += len;
|
|
|
|
const s = DECODER.decode(strBytes);
|
|
|
|
strTable.set(id, s);
|
|
|
|
id++;
|
2024-12-05 01:18:08 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
// console.log({ stringCount, strTable });
|
2024-12-05 11:21:34 +01:00
|
|
|
|
|
|
|
if (strTable.size !== stringCount) {
|
|
|
|
throw new Error(
|
|
|
|
`Could not deserialize string table. Expected ${stringCount} items, but got ${strTable.size}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
// Build id table
|
2024-12-12 20:53:08 +01:00
|
|
|
const idCount = readU32(buf, idTableOffset);
|
2024-12-10 04:33:33 +01:00
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
const idTable = new Array(idCount);
|
|
|
|
|
|
|
|
for (let i = 0; i < idCount; i++) {
|
|
|
|
const id = readU32(buf, offset);
|
|
|
|
idTable[i] = id;
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (idTable.length !== idCount) {
|
|
|
|
throw new Error(
|
|
|
|
`Could not deserialize id table. Expected ${idCount} items, but got ${idTable.length}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-12-07 00:24:36 +01:00
|
|
|
/** @type {AstContext} */
|
2024-12-12 20:53:08 +01:00
|
|
|
const ctx = { buf, idTable, strTable, rootId };
|
|
|
|
|
|
|
|
// console.log({ strTable, idTable });
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-07 00:24:36 +01:00
|
|
|
return ctx;
|
2024-12-04 16:21:17 +01:00
|
|
|
}
|
|
|
|
|
2024-12-07 00:24:36 +01:00
|
|
|
/**
|
|
|
|
* @param {string} fileName
|
|
|
|
* @param {Uint8Array} serializedAst
|
|
|
|
*/
|
2024-12-05 11:21:34 +01:00
|
|
|
export function runPluginsForFile(fileName, serializedAst) {
|
2024-12-07 00:24:36 +01:00
|
|
|
const ctx = createAstContext(serializedAst);
|
2024-12-10 21:17:02 +01:00
|
|
|
// console.log(JSON.stringify(ctx, null, 2));
|
2024-12-04 03:29:05 +01:00
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
/** @type {Record<string, (node: any) => void>} */
|
2024-12-04 12:42:55 +01:00
|
|
|
const mergedVisitor = {};
|
|
|
|
const destroyFns = [];
|
|
|
|
|
2024-12-10 21:17:02 +01:00
|
|
|
// console.log(state);
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-04 12:42:55 +01:00
|
|
|
// Instantiate and merge visitors. This allows us to only traverse
|
|
|
|
// the AST once instead of per plugin.
|
2024-12-07 00:24:36 +01:00
|
|
|
for (let i = 0; i < state.plugins.length; i++) {
|
2024-12-04 12:42:55 +01:00
|
|
|
const plugin = state.plugins[i];
|
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
for (const name of Object.keys(plugin.rules)) {
|
2024-12-04 12:42:55 +01:00
|
|
|
const rule = plugin.rules[name];
|
2024-12-07 00:24:36 +01:00
|
|
|
const id = `${plugin.name}/${name}`;
|
2024-12-04 12:42:55 +01:00
|
|
|
const ctx = new Context(id, fileName);
|
|
|
|
const visitor = rule.create(ctx);
|
|
|
|
|
2024-12-10 21:17:02 +01:00
|
|
|
// console.log({ visitor });
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-04 12:42:55 +01:00
|
|
|
for (const name in visitor) {
|
|
|
|
const prev = mergedVisitor[name];
|
|
|
|
mergedVisitor[name] = (node) => {
|
|
|
|
if (typeof prev === "function") {
|
|
|
|
prev(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
visitor[name](node);
|
|
|
|
} catch (err) {
|
2024-12-10 21:17:02 +01:00
|
|
|
// FIXME: console here doesn't support error cause
|
|
|
|
console.log(err);
|
2024-12-04 12:42:55 +01:00
|
|
|
throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
|
|
|
|
cause: err,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof rule.destroy === "function") {
|
2024-12-10 04:33:33 +01:00
|
|
|
const destroyFn = rule.destroy.bind(rule);
|
2024-12-04 12:42:55 +01:00
|
|
|
destroyFns.push(() => {
|
|
|
|
try {
|
2024-12-10 04:33:33 +01:00
|
|
|
destroyFn(ctx);
|
2024-12-04 12:42:55 +01:00
|
|
|
} catch (err) {
|
|
|
|
throw new Error(`Destroy hook of "${id}" errored`, { cause: err });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2024-12-04 03:29:05 +01:00
|
|
|
}
|
2024-12-04 12:42:55 +01:00
|
|
|
}
|
2024-12-04 03:29:05 +01:00
|
|
|
|
2024-12-04 12:42:55 +01:00
|
|
|
// Traverse ast with all visitors at the same time to avoid traversing
|
|
|
|
// multiple times.
|
2024-12-07 00:24:36 +01:00
|
|
|
try {
|
|
|
|
traverse(ctx, mergedVisitor);
|
|
|
|
} finally {
|
|
|
|
// Optional: Destroy rules
|
|
|
|
for (let i = 0; i < destroyFns.length; i++) {
|
|
|
|
destroyFns[i]();
|
|
|
|
}
|
2024-12-04 03:29:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-04 12:42:55 +01:00
|
|
|
/**
|
2024-12-07 00:24:36 +01:00
|
|
|
* @param {AstContext} ctx
|
2024-12-04 12:42:55 +01:00
|
|
|
* @param {*} visitor
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2024-12-07 00:24:36 +01:00
|
|
|
function traverse(ctx, visitor) {
|
2024-12-10 04:33:33 +01:00
|
|
|
const visitTypes = new Map();
|
2024-12-07 00:24:36 +01:00
|
|
|
|
|
|
|
// TODO: create visiting types
|
2024-12-10 04:33:33 +01:00
|
|
|
for (const name in visitor) {
|
|
|
|
const id = AstType[name];
|
|
|
|
visitTypes.set(id, name);
|
|
|
|
}
|
|
|
|
|
2024-12-11 13:35:37 +01:00
|
|
|
console.log("buffer len", ctx.buf.length, ctx.buf.byteLength);
|
2024-12-10 22:48:54 +01:00
|
|
|
console.log("merged visitor", visitor);
|
|
|
|
console.log("visiting types", visitTypes);
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-12 20:53:08 +01:00
|
|
|
traverseInner(ctx, visitTypes, visitor, ctx.rootId);
|
2024-12-10 04:33:33 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const SKIP_CHILD_TRAVERSAL = new Set([
|
|
|
|
AstType.BooleanLiteral,
|
|
|
|
AstType.BigIntLiteral,
|
|
|
|
AstType.DebuggerStatement,
|
|
|
|
AstType.Identifier,
|
|
|
|
AstType.JSXClosingFragment,
|
|
|
|
AstType.JSXEmptyExpression,
|
|
|
|
AstType.JSXOpeningFragment,
|
|
|
|
AstType.JSXText,
|
|
|
|
AstType.NullLiteral,
|
|
|
|
AstType.NumericLiteral,
|
|
|
|
AstType.PrivateIdentifier,
|
|
|
|
AstType.RegExpLiteral,
|
|
|
|
AstType.StringLiteral,
|
|
|
|
AstType.TemplateLiteral,
|
|
|
|
AstType.This,
|
|
|
|
]);
|
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {Map<number, string>} visitTypes
|
|
|
|
* @param {Record<string, (x: any) => void>} visitor
|
|
|
|
* @param {number} id
|
|
|
|
*/
|
|
|
|
function traverseInner(ctx, visitTypes, visitor, id) {
|
2024-12-12 01:35:57 +01:00
|
|
|
// console.log("traversing id", id);
|
2024-12-10 04:33:33 +01:00
|
|
|
|
|
|
|
// Empty id
|
|
|
|
if (id === 0) return;
|
2024-12-10 05:34:12 +01:00
|
|
|
const { idTable, buf } = ctx;
|
2024-12-10 04:33:33 +01:00
|
|
|
if (id >= idTable.length) {
|
2024-12-10 21:17:02 +01:00
|
|
|
throw new Error(`Invalid node id: ${id}`);
|
2024-12-10 04:33:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let offset = idTable[id];
|
|
|
|
if (offset === undefined) throw new Error(`Unknown id: ${id}`);
|
|
|
|
|
|
|
|
const type = buf[offset];
|
2024-12-13 00:33:44 +01:00
|
|
|
|
2024-12-12 01:35:57 +01:00
|
|
|
// console.log({ id, type, offset });
|
2024-12-10 04:33:33 +01:00
|
|
|
|
|
|
|
const name = visitTypes.get(type);
|
|
|
|
if (name !== undefined) {
|
2024-12-13 00:33:44 +01:00
|
|
|
// console.log("--> invoking visitor");
|
|
|
|
const node = new Node(ctx, offset);
|
2024-12-10 04:33:33 +01:00
|
|
|
visitor[name](node);
|
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
if (SKIP_CHILD_TRAVERSAL.has(type)) return;
|
|
|
|
|
2024-12-10 04:33:33 +01:00
|
|
|
// type + parentId + SpanLo + SpanHi
|
|
|
|
offset += 1 + 4 + 4 + 4;
|
2024-12-07 00:24:36 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const propCount = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
// console.log({ propCount });
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
for (let i = 0; i < propCount; i++) {
|
|
|
|
const searchProp = buf[offset];
|
|
|
|
offset += 1;
|
2024-12-11 17:41:33 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
|
2024-12-10 21:17:02 +01:00
|
|
|
offset += 1;
|
2024-12-13 00:33:44 +01:00
|
|
|
continue;
|
2024-12-10 04:33:33 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
if (isArrayProp(type, searchProp)) {
|
|
|
|
const len = readU32(buf, offset);
|
2024-12-10 04:33:33 +01:00
|
|
|
offset += 4;
|
2024-12-12 01:35:57 +01:00
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
for (let j = 0; j < len; j++) {
|
|
|
|
const childId = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
traverseInner(ctx, visitTypes, visitor, childId);
|
|
|
|
}
|
|
|
|
continue;
|
2024-12-10 04:33:33 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
const childId = readU32(buf, offset);
|
|
|
|
traverseInner(ctx, visitTypes, visitor, childId);
|
|
|
|
offset += 4;
|
2024-12-01 05:14:46 +01:00
|
|
|
}
|
2024-12-01 04:53:47 +01:00
|
|
|
}
|