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",
|
|
|
|
];
|
2024-12-13 11:38:35 +01:00
|
|
|
// FIXME: this is slow
|
2024-12-12 22:16:49 +01:00
|
|
|
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");
|
2024-12-13 11:38:35 +01:00
|
|
|
const AST_PROP_OPERATOR = AstProp.indexOf("operator");
|
|
|
|
const AST_PROP_PREFIX = AstProp.indexOf("prefix");
|
|
|
|
const AST_PROP_OPTIONAL = AstProp.indexOf("optional");
|
|
|
|
const AST_PROP_ASYNC = AstProp.indexOf("async");
|
|
|
|
const AST_PROP_GENERATOR = AstProp.indexOf("generator");
|
|
|
|
const AST_PROP_NAME = AstProp.indexOf("name");
|
|
|
|
const AST_PROP_VALUE = AstProp.indexOf("value");
|
|
|
|
const AST_PROP_COMPUTED = AstProp.indexOf("computed");
|
|
|
|
const AST_PROP_DELEGATE = AstProp.indexOf("delegate");
|
|
|
|
const AST_PROP_SELF_CLOSING = AstProp.indexOf("selfClosing");
|
2024-12-13 00:33:44 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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:
|
2024-12-13 11:38:35 +01:00
|
|
|
switch (propId) {
|
|
|
|
case AST_PROP_CHILDREN:
|
|
|
|
case AST_PROP_ELEMENTS:
|
|
|
|
case AST_PROP_PROPERTIES:
|
|
|
|
case AST_PROP_PARAMS:
|
|
|
|
case AST_PROP_ARGUMENTS:
|
|
|
|
case AST_PROP_EXPRESSIONS:
|
|
|
|
case AST_PROP_QUASIS:
|
|
|
|
case AST_PROP_CASES:
|
|
|
|
case AST_PROP_SPECIFIERS:
|
|
|
|
case AST_PROP_DECLARATIONS:
|
|
|
|
case AST_PROP_MEMBERS:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2024-12-13 00:33:44 +01:00
|
|
|
}
|
|
|
|
}
|
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-13 11:38:35 +01:00
|
|
|
* @returns {*}
|
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) {
|
2024-12-13 11:38:35 +01:00
|
|
|
return readU32(buf, offset + 1);
|
2024-12-13 00:33:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (searchProp === propId) {
|
2024-12-13 11:38:35 +01:00
|
|
|
if (propId === AST_PROP_INTERNAL_FLAGS) {
|
|
|
|
return buf[offset];
|
|
|
|
} else if (isArrayProp(type, searchProp)) {
|
2024-12-13 00:33:44 +01:00
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
2024-12-13 11:38:35 +01:00
|
|
|
const ids = new Array(len).fill(null);
|
2024-12-13 00:33:44 +01:00
|
|
|
for (let j = 0; j < len; j++) {
|
2024-12-13 11:38:35 +01:00
|
|
|
ids[i] = readU32(buf, offset);
|
2024-12-13 00:33:44 +01:00
|
|
|
offset += 4;
|
|
|
|
}
|
2024-12-13 11:38:35 +01:00
|
|
|
return ids;
|
2024-12-13 00:33:44 +01:00
|
|
|
}
|
2024-12-12 22:16:49 +01:00
|
|
|
|
2024-12-13 11:38:35 +01:00
|
|
|
return readU32(buf, offset);
|
2024-12-12 22:16:49 +01:00
|
|
|
}
|
|
|
|
|
2024-12-13 00:33:44 +01:00
|
|
|
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
|
|
|
|
offset += 1;
|
2024-12-13 11:38:35 +01:00
|
|
|
} else if (isArrayProp(type, searchProp)) {
|
2024-12-13 00:33:44 +01:00
|
|
|
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 11:38:35 +01:00
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function getTypeId(ctx, offset) {
|
|
|
|
return ctx.buf[offset];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} offset
|
|
|
|
* @param {Map<number, any>} seen
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
function toJsValue(ctx, offset, seen) {
|
|
|
|
const cached = seen.get(offset);
|
|
|
|
if (cached !== undefined) return cached;
|
|
|
|
|
|
|
|
const type = getTypeId(ctx, offset);
|
|
|
|
const range = readValue(ctx, offset, AST_PROP_RANGE);
|
|
|
|
|
|
|
|
/** @type {Record<string, any>} */
|
|
|
|
const node = {
|
|
|
|
type: AstTypeName[type],
|
|
|
|
range,
|
|
|
|
};
|
|
|
|
|
|
|
|
seen.set(offset, node);
|
|
|
|
|
|
|
|
// console.log("toJSON", AstTypeName[type], offset);
|
|
|
|
|
|
|
|
const { buf, idTable } = ctx;
|
|
|
|
|
|
|
|
// type + parentId + SpanLo + SpanHi
|
|
|
|
offset += 1 + 4 + 4 + 4;
|
|
|
|
|
|
|
|
const propCount = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
|
|
|
|
let flags = 0;
|
|
|
|
for (let i = 0; i < propCount; i++) {
|
|
|
|
const propId = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
|
|
|
|
const name = AstProp[propId];
|
|
|
|
// console.log(`reading node ${AstTypeName[type]}, prop ${name}`);
|
|
|
|
|
|
|
|
if (propId === AST_PROP_INTERNAL_FLAGS) {
|
|
|
|
flags = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
} else if (
|
|
|
|
propId === AST_PROP_NAME && (type === AstType.Identifier ||
|
|
|
|
type === AstType.JSXIdentifier)
|
|
|
|
) {
|
|
|
|
const strId = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
node[name] = getString(ctx, strId);
|
|
|
|
} else if (
|
|
|
|
propId === AST_PROP_VALUE && (type === AstType.StringLiteral ||
|
|
|
|
type === AstType.NumericLiteral)
|
|
|
|
) {
|
|
|
|
const strId = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
node[name] = getString(ctx, strId);
|
|
|
|
} else if (isArrayProp(type, propId)) {
|
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
const elems = new Array(len).fill(null);
|
|
|
|
for (let j = 0; j < len; j++) {
|
|
|
|
const id = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
if (id === 0) {
|
|
|
|
elems[j] = undefined;
|
|
|
|
} else {
|
|
|
|
const nodeOffset = idTable[id];
|
|
|
|
elems[j] = toJsValue(ctx, nodeOffset, seen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
node[name] = elems;
|
|
|
|
} else {
|
|
|
|
const id = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
if (id === 0) {
|
|
|
|
node[name] = null;
|
|
|
|
} else {
|
|
|
|
const nodeOffset = idTable[id];
|
|
|
|
node[name] = toJsValue(ctx, nodeOffset, seen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
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-13 11:38:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {*} _
|
|
|
|
* @param {*} options
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
[Symbol.for("Deno.customInspect")](_, options) {
|
|
|
|
const seen = new Map();
|
|
|
|
const json = toJsValue(this[INTERNAL_CTX], this[INTERNAL_OFFSET], seen);
|
|
|
|
seen.clear();
|
|
|
|
return Deno.inspect(json, options);
|
|
|
|
}
|
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() {
|
2024-12-13 11:38:35 +01:00
|
|
|
const ctx = /** @type {AstContext} */ (this[INTERNAL_CTX]);
|
|
|
|
const offset = this[INTERNAL_OFFSET];
|
|
|
|
const type = getTypeId(ctx, offset);
|
|
|
|
const flags = readValue(ctx, offset, AST_PROP_INTERNAL_FLAGS);
|
|
|
|
|
|
|
|
switch (i) {
|
|
|
|
case AST_PROP_OPERATOR:
|
|
|
|
switch (type) {
|
|
|
|
case AstType.AssignmentExpression:
|
|
|
|
return getAssignOperator(flags);
|
|
|
|
case AstType.BinaryExpression:
|
|
|
|
return getBinaryOperator(flags);
|
|
|
|
case AstType.UpdateExpression:
|
|
|
|
return (flags & Flags.UpdatePlusPlus) !== 0 ? "++" : "--";
|
|
|
|
case AstType.LogicalExpression:
|
|
|
|
return getLogicalOperator(flags);
|
|
|
|
case AstType.UnaryExpression:
|
|
|
|
return getUnaryOperator(flags);
|
|
|
|
}
|
2024-12-12 22:16:49 +01:00
|
|
|
|
2024-12-13 11:38:35 +01:00
|
|
|
break;
|
|
|
|
case AST_PROP_PREFIX:
|
|
|
|
return (flags & Flags.UpdatePrefix) !== 0;
|
|
|
|
case AST_PROP_OPTIONAL:
|
|
|
|
switch (type) {
|
|
|
|
case AstType.FunctionExpression:
|
|
|
|
return (flags & Flags.FnOptional) !== 0;
|
|
|
|
case AstType.MemberExpression:
|
|
|
|
return (flags & Flags.MemberOptional) !== 0;
|
|
|
|
case AstType.ArrayPattern:
|
|
|
|
case AstType.ObjectPattern:
|
|
|
|
return (flags & Flags.ParamOptional) !== 0;
|
|
|
|
}
|
2024-12-10 04:33:33 +01:00
|
|
|
|
2024-12-13 11:38:35 +01:00
|
|
|
break;
|
|
|
|
case AST_PROP_ASYNC:
|
|
|
|
return (flags & Flags.FnAsync) !== 0;
|
|
|
|
case AST_PROP_GENERATOR:
|
|
|
|
return (flags & Flags.FnGenerator) !== 0;
|
|
|
|
case AST_PROP_COMPUTED:
|
|
|
|
return (flags & Flags.MemberComputed) !== 0;
|
|
|
|
case AST_PROP_DELEGATE:
|
|
|
|
return (flags & Flags.YieldDelegate) !== 0;
|
|
|
|
case AST_PROP_SELF_CLOSING:
|
|
|
|
return flags !== 0;
|
|
|
|
case AST_PROP_NAME: {
|
|
|
|
const value = readValue(ctx, offset, AST_PROP_NAME);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case AstType.Identifier:
|
|
|
|
case AstType.JSXIdentifier:
|
|
|
|
case AstType.PrivateIdentifier:
|
|
|
|
return getString(ctx, value);
|
|
|
|
default: {
|
|
|
|
const nodeOffset = ctx.idTable[value];
|
|
|
|
return new Node(ctx, nodeOffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-12-06 23:26:11 +01:00
|
|
|
|
2024-12-13 11:38:35 +01:00
|
|
|
const value = readValue(ctx, offset, i);
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
const nodes = new Array(value.length);
|
|
|
|
for (let i = 0; i < value.length; i++) {
|
|
|
|
const id = value[i];
|
|
|
|
if (id === 0) {
|
|
|
|
nodes[i] = undefined;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const nodeOffset = ctx.idTable[id];
|
|
|
|
nodes[i] = new Node(ctx, nodeOffset);
|
|
|
|
}
|
|
|
|
return nodes;
|
|
|
|
}
|
2024-12-05 01:18:08 +01:00
|
|
|
|
2024-12-13 11:38:35 +01:00
|
|
|
if (value === 0) return null;
|
|
|
|
const nodeOffset = ctx.idTable[value];
|
|
|
|
return new Node(ctx, nodeOffset);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
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
|
|
|
/**
|
|
|
|
* @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
|
|
|
/**
|
|
|
|
* @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
|
|
|
/**
|
|
|
|
* @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-13 11:38:35 +01:00
|
|
|
default:
|
|
|
|
throw new Error(`Unknown operator: ${n}`);
|
2024-12-11 17:41:33 +01:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
// /** @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);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
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-13 11:38:35 +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
|
|
|
}
|