// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // @ts-check import { core } from "ext:core/mod.js"; const { op_lint_get_rule, op_lint_get_source, op_lint_report, } = core.ops; /** @typedef {{ plugins: Deno.LintPlugin[], installedPlugins: Set }} LintState */ /** @type {LintState} */ const state = { plugins: [], installedPlugins: new Set(), }; /** @implements {Deno.LintRuleContext} */ export class Context { id; fileName; #source = null; /** * @param {string} id * @param {string} fileName */ constructor(id, fileName) { this.id = id; this.fileName = fileName; } source() { if (this.#source === null) { this.#source = op_lint_get_source(); } return /** @type {*} */ (this.#source); } report(data) { let start, end; if (data.node) { start = data.node.span.start - 1; end = data.node.span.end - 1; } else if (data.span) { start = data.span.start - 1; end = data.span.end - 1; } else { throw new Error( "Either `node` or `span` must be provided when reporting an error", ); } op_lint_report( this.id, this.fileName, data.message, start, end, ); } } /** * @param {Deno.LintPlugin} plugin */ 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"); } if (state.installedPlugins.has(plugin.name)) { throw new Error(`Linter plugin ${plugin.name} has already been registered`); } state.plugins.push(plugin); state.installedPlugins.add(plugin.name); } // Keep in sync with Rust /** * @enum {number} */ const AstType = { Invalid: 0, Program: 1, Import: 2, ImportDecl: 3, ExportDecl: 4, ExportNamed: 5, ExportDefaultDecl: 6, ExportDefaultExpr: 7, ExportAll: 8, TSImportEquals: 9, TSExportAssignment: 10, TSNamespaceExport: 11, // Decls Class: 12, Fn: 13, Var: 14, Using: 15, TsInterface: 16, TsTypeAlias: 17, TsEnum: 18, TsModule: 19, // Statements BlockStatement: 20, Empty: 21, DebuggerStatement: 22, WithStatement: 23, ReturnStatement: 24, LabeledStatement: 25, BreakStatement: 26, ContinueStatement: 27, IfStatement: 28, SwitchStatement: 29, SwitchCase: 30, ThrowStatement: 31, TryStatement: 32, WhileStatement: 33, DoWhileStatement: 34, ForStatement: 35, ForInStatement: 36, ForOfStatement: 37, Decl: 38, ExpressionStatement: 39, // Expressions This: 40, ArrayExpression: 41, ObjectExpression: 42, FunctionExpression: 43, Unary: 44, Update: 45, BinaryExpression: 46, AssignmentExpression: 47, MemberExpression: 48, Super: 49, ConditionalExpression: 50, CallExpression: 51, NewExpression: 52, ParenthesisExpression: 53, SequenceExpression: 54, Identifier: 55, TemplateLiteral: 56, TaggedTemplateExpression: 57, ArrowFunctionExpression: 58, ClassExpr: 59, Yield: 60, MetaProperty: 61, AwaitExpression: 62, LogicalExpression: 63, TSTypeAssertion: 64, TSConstAssertion: 65, TSNonNull: 66, TSAs: 67, TSInstantiation: 68, TSSatisfies: 69, PrivateIdentifier: 70, OptChain: 71, StringLiteral: 72, BooleanLiteral: 73, NullLiteral: 74, NumericLiteral: 75, BigIntLiteral: 76, RegExpLiteral: 77, // Custom EmptyExpr: 84, Spread: 85, ObjProperty: 86, VarDeclarator: 87, CatchClause: 88, // JSX // FIXME JSXAttribute: Infinity, JSXClosingElement: Infinity, JSXClosingFragment: Infinity, JSXElement: Infinity, JSXExpressionContainer: Infinity, JSXFragment: Infinity, JSXIdentifier: Infinity, JSXMemberExpression: Infinity, JSXNamespacedName: Infinity, JSXOpeningElement: Infinity, JSXOpeningFragment: Infinity, JSXSpreadAttribute: Infinity, JSXSpreadChild: Infinity, JSXText: Infinity, ArrayPattern: 89, AssignmentPattern: 90, ObjectPattern: 91, }; const AstNodeById = Object.keys(AstType); /** * @param {AstContext} ctx * @param {number[]} ids * @returns {any[]} */ function createChildNodes(ctx, ids) { /** @type {any[]} */ const out = []; for (let i = 0; i < ids.length; i++) { const id = ids[i]; out.push(createAstNode(ctx, id)); } return out; } class BaseNode { #ctx; #parentId; get parent() { return /** @type {*} */ (createAstNode( this.#ctx, this.#parentId, )); } /** * @param {AstContext} ctx * @param {number} parentId */ constructor(ctx, parentId) { this.#ctx = ctx; this.#parentId = parentId; } } /** @implements {Deno.Program} */ class Program extends BaseNode { type = /** @type {const} */ ("Program"); range; get body() { return createChildNodes(this.#ctx, this.#childIds); } #ctx; #childIds; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {Deno.Program["sourceType"]} sourceType * @param {number[]} childIds */ constructor(ctx, parentId, range, sourceType, childIds) { super(ctx, parentId); this.#ctx = ctx; this.range = range; this.sourceType = sourceType; this.#childIds = childIds; } } /** @implements {Deno.BlockStatement} */ class BlockStatement extends BaseNode { type = /** @type {const} */ ("BlockStatement"); get body() { return createChildNodes(this.#ctx, this.#childIds); } range; #ctx; #childIds; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number[]} childIds */ constructor(ctx, parentId, range, childIds) { super(ctx, parentId); this.#ctx = ctx; this.range = range; this.#childIds = childIds; } } /** @implements {Deno.BreakStatement} */ class BreakStatement extends BaseNode { type = /** @type {const} */ ("BreakStatement"); get label() { if (this.#labelId === 0) return null; return /** @type {Deno.Identifier} */ (createAstNode( this.#ctx, this.#labelId, )); } range; #ctx; #labelId; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} labelId */ constructor(ctx, parentId, range, labelId) { super(ctx, parentId); this.#ctx = ctx; this.#labelId = labelId; this.range = range; } } /** @implements {Deno.ContinueStatement} */ class ContinueStatement extends BaseNode { type = /** @type {const} */ ("ContinueStatement"); range; get label() { return /** @type {Deno.Identifier} */ (createAstNode( this.#ctx, this.#labelId, )); } #ctx; #labelId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} labelId */ constructor(ctx, parentId, range, labelId) { super(ctx, parentId); this.#ctx = ctx; this.#labelId = labelId; this.range = range; } } /** @implements {Deno.DebuggerStatement} */ class DebuggerStatement extends BaseNode { type = /** @type {const} */ ("DebuggerStatement"); range; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range */ constructor(ctx, parentId, range) { super(ctx, parentId); this.range = range; } } /** @implements {Deno.DoWhileStatement} */ class DoWhileStatement extends BaseNode { type = /** @type {const} */ ("DoWhileStatement"); range; get test() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#exprId, )); } get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } #ctx; #exprId = 0; #bodyId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} exprId * @param {number} bodyId */ constructor(ctx, parentId, range, exprId, bodyId) { super(ctx, parentId); this.#ctx = ctx; this.#exprId = exprId; this.#bodyId = bodyId; this.range = range; } } /** @implements {Deno.ExpressionStatement} */ class ExpressionStatement extends BaseNode { type = /** @type {const} */ ("ExpressionStatement"); range; get expression() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#exprId, )); } #ctx; #exprId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} exprId */ constructor(ctx, parentId, range, exprId) { super(ctx, parentId); this.#ctx = ctx; this.#exprId = exprId; this.range = range; } } /** @implements {Deno.ForInStatement} */ class ForInStatement extends BaseNode { type = /** @type {const} */ ("ForInStatement"); range; get left() { return /** @type {Deno.Expression | Deno.VariableDeclaration} */ (createAstNode( this.#ctx, this.#leftId, )); } get right() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#rightId, )); } get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } #ctx; #leftId = 0; #rightId = 0; #bodyId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} leftId * @param {number} rightId * @param {number} bodyId */ constructor(ctx, parentId, range, leftId, rightId, bodyId) { super(ctx, parentId); this.#ctx = ctx; this.#leftId = leftId; this.#rightId = rightId; this.#bodyId = bodyId; this.range = range; } } /** @implements {Deno.ForOfStatement} */ class ForOfStatement extends BaseNode { type = /** @type {const} */ ("ForOfStatement"); range; get left() { return /** @type {Deno.Expression | Deno.VariableDeclaration | Deno.UsingDeclaration} */ (createAstNode( this.#ctx, this.#leftId, )); } get right() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#rightId, )); } get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } await; #ctx; #leftId = 0; #rightId = 0; #bodyId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {boolean} isAwait * @param {number} leftId * @param {number} rightId * @param {number} bodyId */ constructor(ctx, parentId, range, isAwait, leftId, rightId, bodyId) { super(ctx, parentId); this.#ctx = ctx; this.#leftId = leftId; this.#rightId = rightId; this.#bodyId = bodyId; this.range = range; this.await = isAwait; } } /** @implements {Deno.ForStatement} */ class ForStatement extends BaseNode { type = /** @type {const} */ ("ForStatement"); range; get init() { if (this.#initId === 0) return null; return /** @type {Deno.Expression | Deno.VariableDeclaration} */ (createAstNode( this.#ctx, this.#initId, )); } get test() { if (this.#initId === 0) return null; return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#testId, )); } get update() { if (this.#updateId === 0) return null; return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#updateId, )); } get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } #ctx; #initId = 0; #testId = 0; #updateId = 0; #bodyId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} initId * @param {number} testId * @param {number} updateId * @param {number} bodyId */ constructor(ctx, parentId, range, initId, testId, updateId, bodyId) { super(ctx, parentId); this.#ctx = ctx; this.#initId = initId; this.#testId = testId; this.#updateId = updateId; this.#bodyId = bodyId; this.range = range; } } /** @implements {Deno.IfStatement} */ class IfStatement extends BaseNode { type = /** @type {const} */ ("IfStatement"); range; get test() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#testId, )); } get consequent() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#consequentId, )); } get alternate() { if (this.#alternateId === 0) return null; return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#alternateId, )); } #ctx; #testId = 0; #consequentId = 0; #alternateId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} testId * @param {number} updateId * @param {number} alternateId */ constructor(ctx, parentId, range, testId, updateId, alternateId) { super(ctx, parentId); this.#ctx = ctx; this.#testId = testId; this.#consequentId = updateId; this.#alternateId = alternateId; this.range = range; } } /** @implements {Deno.LabeledStatement} */ class LabeledStatement extends BaseNode { type = /** @type {const} */ ("LabeledStatement"); range; get label() { return /** @type {Deno.Identifier} */ (createAstNode( this.#ctx, this.#labelId, )); } get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } #ctx; #labelId = 0; #bodyId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} testId * @param {number} bodyId */ constructor(ctx, parentId, range, testId, bodyId) { super(ctx, parentId); this.#ctx = ctx; this.#labelId = testId; this.#bodyId = bodyId; this.range = range; } } /** @implements {Deno.ReturnStatement} */ class ReturnStatement extends BaseNode { type = /** @type {const} */ ("ReturnStatement"); range; get argument() { if (this.#exprId === 0) return null; return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#exprId, )); } #ctx; #exprId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} argId */ constructor(ctx, parentId, range, argId) { super(ctx, parentId); this.#ctx = ctx; this.#exprId = argId; this.range = range; } } /** @implements {Deno.SwitchStatement} */ class SwitchStatement extends BaseNode { type = /** @type {const} */ ("SwitchStatement"); range; get discriminant() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#discriminantId, )); } get cases() { return []; // FIXME } #ctx; #discriminantId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} discriminantId */ constructor(ctx, parentId, range, discriminantId) { super(ctx, parentId); this.#ctx = ctx; this.#discriminantId = discriminantId; this.range = range; } } /** @implements {Deno.ThrowStatement} */ class ThrowStatement extends BaseNode { type = /** @type {const} */ ("ThrowStatement"); range; get argument() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#argId, )); } #ctx; #argId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} argId */ constructor(ctx, parentId, range, argId) { super(ctx, parentId); this.#ctx = ctx; this.#argId = argId; this.range = range; } } /** @implements {Deno.TryStatement} */ class TryStatement extends BaseNode { type = /** @type {const} */ ("TryStatement"); range; get block() { return /** @type {Deno.BlockStatement} */ (createAstNode( this.#ctx, this.#blockId, )); } get finalizer() { if (this.#finalizerId === 0) return null; return /** @type {Deno.BlockStatement} */ (createAstNode( this.#ctx, this.#finalizerId, )); } get handler() { if (this.#handlerId === 0) return null; return /** @type {Deno.CatchClause} */ (createAstNode( this.#ctx, this.#handlerId, )); } #ctx; #blockId = 0; #finalizerId = 0; #handlerId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} blockId * @param {number} finalizerId * @param {number} handlerId */ constructor(ctx, parentId, range, blockId, finalizerId, handlerId) { super(ctx, parentId); this.#ctx = ctx; this.#blockId = blockId; this.#finalizerId = finalizerId; this.#handlerId = handlerId; this.range = range; } } /** @implements {Deno.WhileStatement} */ class WhileStatement extends BaseNode { type = /** @type {const} */ ("WhileStatement"); range; get test() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#testId, )); } get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } #ctx; #testId = 0; #bodyId = 0; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} testId * @param {number} bodyId */ constructor(ctx, parentId, range, testId, bodyId) { super(ctx, parentId); this.#ctx = ctx; this.#testId = testId; this.#bodyId = bodyId; this.range = range; } } /** @implements {Deno.WithStatement} */ class WithStatement { type = /** @type {const} */ ("WithStatement"); range; get body() { return /** @type {Deno.Statement} */ (createAstNode( this.#ctx, this.#bodyId, )); } get object() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#objectId, )); } #ctx; #bodyId = 0; #objectId = 0; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} bodyId * @param {number} objectId */ constructor(ctx, range, bodyId, objectId) { this.#ctx = ctx; this.#bodyId = bodyId; this.#objectId = objectId; this.range = range; } } // Expressions /** @implements {Deno.ArrayExpression} */ class ArrayExpression extends BaseNode { type = /** @type {const} */ ("ArrayExpression"); range; get elements() { return createChildNodes(this.#ctx, this.#elemIds); } #ctx; #elemIds; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number[]} elemIds */ constructor(ctx, parentId, range, elemIds) { super(ctx, parentId); this.#ctx = ctx; this.range = range; this.#elemIds = elemIds; } } /** @implements {Deno.ArrowFunctionExpression} */ class ArrowFunctionExpression extends BaseNode { type = /** @type {const} */ ("ArrowFunctionExpression"); range; async = false; generator = false; get body() { return /** @type {*} */ (createAstNode( this.#ctx, this.#bodyId, )); } get params() { return createChildNodes(this.#ctx, this.#paramIds); } get returnType() { if (this.#returnTypeId === 0) return null; return /** @type {*} */ (createAstNode( this.#ctx, this.#returnTypeId, )); } get typeParameters() { if (this.#typeParamId === 0) return null; return /** @type {*} */ (createAstNode( this.#ctx, this.#typeParamId, )); } #ctx; #bodyId; #typeParamId; #paramIds; #returnTypeId; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {boolean} isAsync * @param {boolean} isGenerator * @param {number} typeParamId * @param {number[]} paramIds * @param {number} bodyId * @param {number} returnTypeId */ constructor( ctx, parentId, range, isAsync, isGenerator, typeParamId, paramIds, bodyId, returnTypeId, ) { super(ctx, parentId); this.#ctx = ctx; this.#bodyId = bodyId; this.#typeParamId = typeParamId; this.#paramIds = paramIds; this.#returnTypeId = returnTypeId; this.asnyc = isAsync; this.generator = isGenerator; this.range = range; } } /** @implements {Deno.AssignmentExpression} */ class AssignmentExpression { type = /** @type {const} */ ("AssignmentExpression"); range; get left() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#leftId, )); } get right() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#rightId, )); } operator; #ctx; #leftId = 0; #rightId = 0; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} flags * @param {number} leftId * @param {number} rightId */ constructor(ctx, range, flags, leftId, rightId) { this.#ctx = ctx; this.#leftId = leftId; this.#rightId = rightId; this.range = range; this.operator = getAssignOperator(flags); } } /** * @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}`); } } /** @implements {Deno.AwaitExpression} */ class AwaitExpression { type = /** @type {const} */ ("AwaitExpression"); range; get argument() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#argId, )); } #ctx; #argId = 0; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} argId */ constructor(ctx, range, argId) { this.#ctx = ctx; this.#argId = argId; this.range = range; } } /** @implements {Deno.BinaryExpression} */ class BinaryExpression { type = /** @type {const} */ ("BinaryExpression"); range; get left() { return /** @type {Deno.Expression | Deno.PrivateIdentifier} */ (createAstNode( this.#ctx, this.#leftId, )); } get right() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#rightId, )); } operator; #ctx; #leftId; #rightId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} flags * @param {number} leftId * @param {number} rightId */ constructor(ctx, range, flags, leftId, rightId) { this.#ctx = ctx; this.#leftId = leftId; this.#rightId = rightId; this.operator = getBinaryOperator(flags); this.range = range; } } /** * @param {number} n * @returns {Deno.BinaryExpression["operator"]} */ function getBinaryOperator(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 "in"; case 14: return "instanceof"; case 15: return "<="; case 16: return "<<"; case 17: return "<"; case 18: return "-"; case 19: return "%"; case 20: return "+"; case 21: return "/"; default: throw new Error(`Unknown operator: ${n}`); } } /** @implements {Deno.CallExpression} */ class CallExpression extends BaseNode { type = /** @type {const} */ ("CallExpression"); range; get callee() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#calleeId, )); } get arguments() { return createChildNodes(this.#ctx, this.#argumentIds); } get typeArguments() { if (this.#typeArgId === 0) return null; return createAstNode(this.#ctx, this.#typeArgId); } optional = false; // FIXME #ctx; #calleeId; #typeArgId; #argumentIds; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} calleeId * @param {number} typeArgId * @param {number[]} argumentIds */ constructor(ctx, parentId, range, calleeId, typeArgId, argumentIds) { super(ctx, parentId); this.#ctx = ctx; this.#calleeId = calleeId; this.range = range; this.#typeArgId = typeArgId; this.#argumentIds = argumentIds; } } /** @implements {Deno.ChainExpression} */ class ChainExpression { type = /** @type {const} */ ("ChainExpression"); range; #ctx; /** * @param {AstContext} ctx * @param {Deno.Range} range */ constructor(ctx, range) { this.#ctx = ctx; this.range = range; } } /** @implements {Deno.ConditionalExpression} */ class ConditionalExpression { type = /** @type {const} */ ("ConditionalExpression"); range; get test() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#testId, )); } get consequent() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#consequentId, )); } get alternate() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#alternateId, )); } #ctx; #testId; #consequentId; #alternateId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} testId * @param {number} consequentId * @param {number} alternateId */ constructor(ctx, range, testId, consequentId, alternateId) { this.#ctx = ctx; this.#testId = testId; this.#consequentId = consequentId; this.#alternateId = alternateId; this.range = range; } } /** @implements {Deno.FunctionExpression} */ class FunctionExpression { type = /** @type {const} */ ("FunctionExpression"); range; #ctx; /** * @param {AstContext} ctx * @param {Deno.Range} range */ constructor(ctx, range) { this.#ctx = ctx; this.range = range; } } /** @implements {Deno.Identifier} */ class Identifier extends BaseNode { type = /** @type {const} */ ("Identifier"); range; name = ""; #ctx; /** * @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.name = getString(ctx, nameId); this.range = range; } } /** @implements {Deno.LogicalExpression} */ class LogicalExpression { type = /** @type {const} */ ("LogicalExpression"); range; get left() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#leftId, )); } get right() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#rightId, )); } #ctx; #leftId; #rightId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} flags * @param {number} leftId * @param {number} rightId */ constructor(ctx, range, flags, leftId, rightId) { this.#ctx = ctx; this.operator = getLogicalOperator(flags); this.#leftId = leftId; this.#rightId = rightId; this.range = range; } } /** * @param {number} n * @returns {Deno.LogicalExpression["operator"]} */ function getLogicalOperator(n) { switch (n) { case 0: return "&&"; case 1: return "||"; case 2: return "??"; default: throw new Error(`Unknown operator: ${n}`); } } /** @implements {Deno.MemberExpression} */ class MemberExpression extends BaseNode { type = /** @type {const} */ ("MemberExpression"); range; get object() { return /** @type {*} */ (createAstNode( this.#ctx, this.#objId, )); } get property() { return /** @type {*} */ (createAstNode( this.#ctx, this.#propId, )); } optional = false; // FIXME computed = false; // FIXME #ctx; #objId; #propId; /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} flags * @param {number} objId * @param {number} propId */ constructor(ctx, parentId, range, flags, objId, propId) { super(ctx, parentId); this.#ctx = ctx; this.computed = (flags & 0b00000001) !== 0; this.#objId = objId; this.#propId = propId; this.range = range; } } /** @implements {Deno.MetaProperty} */ class MetaProperty { type = /** @type {const} */ ("MetaProperty"); range; get meta() { return /** @type {Deno.Identifier} */ (createAstNode( this.#ctx, this.#metaId, )); } get property() { return /** @type {Deno.Identifier} */ (createAstNode( this.#ctx, this.#propId, )); } #ctx; #metaId; #propId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} metaId * @param {number} propId */ constructor(ctx, range, metaId, propId) { this.#ctx = ctx; this.#metaId = metaId; this.#propId = propId; this.range = range; } } /** @implements {Deno.NewExpression} */ class NewExpression { type = /** @type {const} */ ("NewExpression"); range; get arguments() { return []; // FIXME } get callee() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#calleeId, )); } #ctx; #calleeId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} calleeId */ constructor(ctx, range, calleeId) { this.#ctx = ctx; this.#calleeId = calleeId; this.range = range; } } /** @implements {Deno.ParenthesisExpression} */ class ParenthesisExpression extends BaseNode { type = /** @type {const} */ ("ParenthesisExpression"); range; #ctx; #exprId; get expression() { return /** @type {*} */ (createAstNode(this.#ctx, this.#exprId)); } /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number} exprId */ constructor(ctx, parentId, range, exprId) { super(ctx, parentId); this.#ctx = ctx; this.range = range; this.#exprId = exprId; } } /** @implements {Deno.PrivateIdentifier} */ class PrivateIdentifier extends BaseNode { 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.SequenceExpression} */ class SequenceExpression extends BaseNode { type = /** @type {const} */ ("SequenceExpression"); range; #ctx; #childIds; get expressions() { return createChildNodes(this.#ctx, this.#childIds); } /** * @param {AstContext} ctx * @param {number} parentId * @param {Deno.Range} range * @param {number[]} childIds */ constructor(ctx, parentId, range, childIds) { super(ctx, parentId); this.#ctx = ctx; this.range = range; this.#childIds = childIds; } } /** @implements {Deno.Super} */ class Super { type = /** @type {const} */ ("Super"); range; #ctx; /** * @param {AstContext} ctx * @param {Deno.Range} range */ constructor(ctx, range) { this.#ctx = ctx; this.range = range; } } // Literals /** @implements {Deno.BooleanLiteral} */ class BooleanLiteral extends BaseNode { 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 BaseNode { 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 BaseNode { 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 BaseNode { 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 BaseNode { 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 BaseNode { 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); } } // JSX /** @implements {Deno.JSXAttribute} */ class JSXAttribute { type = /** @type {const} */ ("JSXAttribute"); range; get name() { return /** @type {Deno.JSXIdentifier | Deno.JSXNamespacedName} */ (createAstNode( this.#ctx, this.#nameId, )); } get value() { if (this.#valueId === 0) return null; return /** @type {Deno.JSXElement | Deno.JSXExpressionContainer | Deno.JSXSpreadChild | Deno.Literal} */ (createAstNode( this.#ctx, this.#valueId, )); } #ctx; #nameId; #valueId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} nameId * @param {number} valueId */ constructor(ctx, range, nameId, valueId) { this.#ctx = ctx; this.range = range; this.#nameId = nameId; this.#valueId = valueId; } } /** @implements {Deno.JSXClosingElement} */ class JSXClosingElement { type = /** @type {const} */ ("JSXClosingElement"); range; get name() { return /** @type {Deno.JSXIdentifier | Deno.JSXMemberExpression | Deno.JSXNamespacedName} */ (createAstNode( this.#ctx, this.#nameId, )); } #ctx; #nameId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} nameId */ constructor(ctx, range, nameId) { this.#ctx = ctx; this.range = range; this.#nameId = nameId; } } /** @implements {Deno.JSXClosingFragment} */ class JSXClosingFragment { type = /** @type {const} */ ("JSXClosingFragment"); range; #ctx; /** * @param {AstContext} ctx * @param {Deno.Range} range */ constructor(ctx, range) { this.#ctx = ctx; this.range = range; } } /** @implements {Deno.JSXElement} */ class JSXElement { type = /** @type {const} */ ("JSXElement"); range; get children() { return []; // FIXME } get openingElement() { return /** @type {Deno.JSXOpeningElement} */ (createAstNode( this.#ctx, this.#openId, )); } get closingElement() { return /** @type {Deno.JSXClosingElement} */ (createAstNode( this.#ctx, this.#closingId, )); } #ctx; #openId; #closingId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} openId * @param {number} closingId */ constructor(ctx, range, openId, closingId) { this.#ctx = ctx; this.range = range; this.#openId = openId; this.#closingId = closingId; } } /** @implements {Deno.JSXExpressionContainer} */ class JSXExpressionContainer { type = /** @type {const} */ ("JSXExpressionContainer"); range; get expression() { return /** @type {Deno.Expression | Deno.JSXEmptyExpression} */ (createAstNode( this.#ctx, this.#exprId, )); } #ctx; #exprId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} exprId */ constructor(ctx, range, exprId) { this.#ctx = ctx; this.range = range; this.#exprId = exprId; } } /** @implements {Deno.JSXFragment} */ class JSXFragment { type = /** @type {const} */ ("JSXFragment"); range; get children() { return []; // FIXME } get closingFragment() { return /** @type {Deno.JSXClosingFragment} */ (createAstNode( this.#ctx, this.#closingId, )); } get openingFragment() { return /** @type {Deno.JSXOpeningFragment} */ (createAstNode( this.#ctx, this.#openingId, )); } #ctx; #closingId; #openingId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} closingId * @param {number} openingId */ constructor(ctx, range, closingId, openingId) { this.#ctx = ctx; this.range = range; this.#closingId = closingId; this.#openingId = openingId; } } /** @implements {Deno.JSXIdentifier} */ class JSXIdentifier { type = /** @type {const} */ ("JSXIdentifier"); range; name; #ctx; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} nameId */ constructor(ctx, range, nameId) { this.#ctx = ctx; this.range = range; this.name = getString(ctx, nameId); } } /** @implements {Deno.JSXMemberExpression} */ class JSXMemberExpression { type = /** @type {const} */ ("JSXMemberExpression"); range; get object() { return /** @type {Deno.JSXMemberExpression["object"]} */ (createAstNode( this.#ctx, this.#objId, )); } get property() { return /** @type {Deno.JSXIdentifier} */ (createAstNode( this.#ctx, this.#propertyId, )); } #ctx; #objId; #propertyId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} objId * @param {number} propId */ constructor(ctx, range, objId, propId) { this.#ctx = ctx; this.range = range; this.#objId = objId; this.#propertyId = propId; } } /** @implements {Deno.JSXNamespacedName} */ class JSXNamespacedName { type = /** @type {const} */ ("JSXNamespacedName"); range; get name() { return /** @type {Deno.JSXIdentifier} */ (createAstNode( this.#ctx, this.#nameId, )); } get namespace() { return /** @type {Deno.JSXIdentifier} */ (createAstNode( this.#ctx, this.#namespaceId, )); } #ctx; #nameId; #namespaceId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} nameId * @param {number} nsId */ constructor(ctx, range, nameId, nsId) { this.#ctx = ctx; this.range = range; this.#nameId = nameId; this.#namespaceId = nsId; } } /** @implements {Deno.JSXOpeningElement} */ class JSXOpeningElement { type = /** @type {const} */ ("JSXOpeningElement"); range; get attributes() { return []; // FIXME } get name() { return /** @type {Deno.JSXIdentifier} */ (createAstNode( this.#ctx, this.#nameId, )); } #ctx; #nameId; selfClosing = false; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} flags * @param {number} nameId */ constructor(ctx, range, flags, nameId) { this.#ctx = ctx; this.selfClosing = flags === 1; this.#nameId = nameId; this.range = range; } } /** @implements {Deno.JSXOpeningFragment} */ class JSXOpeningFragment { type = /** @type {const} */ ("JSXOpeningFragment"); range; #ctx; /** * @param {AstContext} ctx * @param {Deno.Range} range */ constructor(ctx, range) { this.#ctx = ctx; this.range = range; } } /** @implements {Deno.JSXSpreadAttribute} */ class JSXSpreadAttribute { type = /** @type {const} */ ("JSXSpreadAttribute"); range; get argument() { return /** @type {Deno.Expression} */ (createAstNode( this.#ctx, this.#argId, )); } #ctx; #argId; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} argId */ constructor(ctx, range, argId) { this.#ctx = ctx; this.range = range; this.#argId = argId; } } /** @implements {Deno.JSXSpreadChild} */ class JSXSpreadChild { type = /** @type {const} */ ("JSXSpreadChild"); range; get expression() { return /** @type {Deno.Expression | Deno.JSXEmptyExpression} */ (createAstNode( this.#ctx, this.#exprid, )); } #ctx; #exprid; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} exprId */ constructor(ctx, range, exprId) { this.#ctx = ctx; this.range = range; this.#exprid = exprId; } } /** @implements {Deno.JSXText} */ class JSXText { type = /** @type {const} */ ("JSXText"); range; #ctx; value = ""; raw = ""; /** * @param {AstContext} ctx * @param {Deno.Range} range * @param {number} valueId * @param {number} rawId */ constructor(ctx, range, valueId, rawId) { this.#ctx = ctx; this.range = range; this.value = getString(ctx, valueId); this.raw = getString(ctx, rawId); } } const DECODER = new TextDecoder(); /** * @typedef {{ * buf: Uint8Array, * strTable: Map, * idTable: number[], * visited: Set, * }} AstContext */ /** * @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]; } /** * @param {Uint8Array} buf * @param {number} offset * @returns {number[]} */ function readChildIds(buf, offset) { const count = readU32(buf, offset); offset += 4; console.log("read children count:", count, offset); /** @type {number[]} */ const out = new Array(count); for (let i = 0; i < count; i++) { out[i] = readU32(buf, offset); offset += 4; } console.log("read children", out); return out; } /** * @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}`); } return name; } /** * @param {AstContext} ctx * @param {number} id * @returns {Deno.AstNode} */ function createAstNode(ctx, id) { const { buf, idTable } = ctx; let offset = idTable[id]; console.log({ id, offset }); /** @type {AstType} */ const kind = buf[offset]; const parentId = readU32(buf, offset + 1); const rangeStart = readU32(buf, offset + 5); const rangeEnd = readU32(buf, offset + 9); const range = /** @type {Deno.Range} */ ([rangeStart, rangeEnd]); offset += 13; switch (kind) { case AstType.Program: { const moduleType = buf[offset] === 1 ? "module" : "script"; const childIds = readChildIds(buf, offset + 1); return new Program(ctx, parentId, range, moduleType, childIds); } case AstType.Import: // case AstNodeId.ImportDecl: // case AstNodeId.ExportDecl: // case AstNodeId.ExportNamed: // case AstNodeId.ExportDefaultDecl: // case AstNodeId.ExportDefaultExpr: // case AstNodeId.ExportAll: // Statements case AstType.BlockStatement: { const childIds = readChildIds(buf, offset); return new BlockStatement(ctx, parentId, range, childIds); } case AstType.BreakStatement: { const labelId = readU32(buf, offset); return new BreakStatement(ctx, parentId, range, labelId); } case AstType.ContinueStatement: { const labelId = readU32(buf, offset); return new ContinueStatement(ctx, parentId, range, labelId); } case AstType.DebuggerStatement: return new DebuggerStatement(ctx, parentId, range); case AstType.DoWhileStatement: { const exprId = readU32(buf, offset); const bodyId = readU32(buf, offset + 4); return new DoWhileStatement(ctx, parentId, range, exprId, bodyId); } case AstType.ExpressionStatement: { const exprId = readU32(buf, offset); return new ExpressionStatement(ctx, parentId, range, exprId); } case AstType.ForInStatement: { const leftId = readU32(buf, offset); const rightId = readU32(buf, offset + 4); const bodyId = readU32(buf, offset + 8); return new ForInStatement(ctx, parentId, range, leftId, rightId, bodyId); } case AstType.ForOfStatement: throw new ForOfStatement(ctx, parentId, range, false, 0, 0, 0); // FIXME case AstType.ForStatement: { const initId = readU32(buf, offset); const testId = readU32(buf, offset + 4); const updateId = readU32(buf, offset + 8); const bodyId = readU32(buf, offset + 12); return new ForStatement( ctx, parentId, range, initId, testId, updateId, bodyId, ); } case AstType.IfStatement: { const testId = readU32(buf, offset); const consequentId = readU32(buf, offset + 4); const alternateId = readU32(buf, offset + 8); return new IfStatement( ctx, parentId, range, testId, consequentId, alternateId, ); } case AstType.LabeledStatement: { const labelId = readU32(buf, offset); const stmtId = readU32(buf, offset + 4); return new LabeledStatement(ctx, parentId, range, labelId, stmtId); } case AstType.ReturnStatement: { const argId = readU32(buf, offset); return new ReturnStatement(ctx, parentId, range, argId); } case AstType.SwitchStatement: throw new SwitchStatement(ctx, parentId, range, 0); // FIXME case AstType.ThrowStatement: { const argId = readU32(buf, offset); return new ThrowStatement(ctx, parentId, range, argId); } case AstType.TryStatement: { const blockId = readU32(buf, offset); const catchId = readU32(buf, offset + 4); const finalId = readU32(buf, offset + 8); throw new TryStatement(ctx, parentId, range, blockId, catchId, finalId); } case AstType.WhileStatement: { const testId = readU32(buf, offset); const stmtId = readU32(buf, offset + 4); return new WhileStatement(ctx, parentId, range, testId, stmtId); } case AstType.WithStatement: return new WithStatement(ctx, range, 0, 0); // Expressions case AstType.ArrayExpression: { const elemIds = readChildIds(buf, offset); return new ArrayExpression(ctx, parentId, range, elemIds); } case AstType.ArrowFunctionExpression: { const flags = buf[offset]; offset += 1; const isAsync = (flags & 0b00000001) !== 0; const isGenerator = (flags & 0b00000010) !== 0; const typeParamId = readU32(buf, offset); offset += 4; const paramIds = readChildIds(buf, offset); offset += paramIds.length * 4; const bodyId = readU32(buf, offset); offset += 4; const returnTypeId = readU32(buf, offset); offset += 4; return new ArrowFunctionExpression( ctx, parentId, range, isAsync, isGenerator, typeParamId, paramIds, bodyId, returnTypeId, ); } case AstType.AssignmentExpression: throw new AssignmentExpression(ctx, range, flags, 0, 0); // FIXME case AstType.AwaitExpression: throw new AwaitExpression(ctx, range, 0); // FIXME case AstType.BinaryExpression: throw new BinaryExpression(ctx, range, flags, 0, 0); // FIXME case AstType.CallExpression: { const calleeId = readU32(buf, offset); const typeArgId = readU32(buf, offset + 4); const childIds = readChildIds(buf, offset + 8); return new CallExpression( ctx, parentId, range, calleeId, typeArgId, childIds, ); } case AstType.ChainExpression: throw new ChainExpression(ctx, range); // FIXME case AstType.ConditionalExpression: throw new ConditionalExpression(ctx, range, 0, 0, 0); // FIXME case AstType.FunctionExpression: throw new FunctionExpression(ctx, range); // FIXME case AstType.Identifier: { const strId = readU32(buf, offset); return new Identifier(ctx, parentId, range, strId); } case AstType.LogicalExpression: throw new LogicalExpression(ctx, range, flags, 0, 0); // FIXME case AstType.MemberExpression: { const flags = buf[offset]; offset += 1; const objId = readU32(buf, offset); offset += 4; const propId = readU32(buf, offset); return new MemberExpression(ctx, parentId, range, flags, objId, propId); } case AstType.MetaProperty: throw new MetaProperty(ctx, range, 0, 0); // FIXME case AstType.NewExpression: throw new NewExpression(ctx, range, 0); // FIXME case AstType.ObjectExpression: throw new Error("TODO"); case AstType.ParenthesisExpression: { const exprId = readU32(buf, offset); return new ParenthesisExpression(ctx, parentId, range, exprId); } case AstType.PrivateIdentifier: { const strId = readU32(buf, offset); return new PrivateIdentifier(ctx, parentId, range, strId); } case AstType.StaticBlock: throw new Error("TODO"); case AstType.SequenceExpression: { const childIds = readChildIds(buf, offset); return new SequenceExpression(ctx, parentId, range, childIds); } case AstType.Super: throw new Super(ctx, range); // FIXME case AstType.TaggedTemplateExpression: throw new Error("TODO"); case AstType.TemplateLiteral: throw new Error("TODO"); // Literals case AstType.BooleanLiteral: { const flags = buf[offset]; return new BooleanLiteral(ctx, parentId, range, flags); } case AstType.BigIntLiteral: { const strId = readU32(buf, offset); return new BigIntLiteral(ctx, parentId, range, strId); } case AstType.NullLiteral: return new NullLiteral(ctx, parentId, range); case AstType.NumericLiteral: { const strId = readU32(buf, offset); return new NumericLiteral(ctx, parentId, range, strId); } case AstType.RegExpLiteral: { const patternId = readU32(buf, offset); const flagsId = readU32(buf, offset + 4); return new RegExpLiteral(ctx, parentId, range, patternId, flagsId); } case AstType.StringLiteral: { const strId = readU32(buf, offset); return new StringLiteral(ctx, parentId, range, strId); } // JSX // FIXME case AstType.JSXAttribute: throw new JSXAttribute(ctx, range, 0, 0); // FIXME case AstType.JSXClosingElement: throw new JSXClosingElement(ctx, range, 0); // FIXME case AstType.JSXClosingFragment: throw new JSXClosingFragment(ctx, range); // FIXME case AstType.JSXElement: throw new JSXElement(ctx, range, 0, 0); // FIXME case AstType.JSXExpressionContainer: throw new JSXExpressionContainer(ctx, range, 0); // FIXME case AstType.JSXFragment: throw new JSXFragment(ctx, range, 0, 0); // FIXME case AstType.JSXIdentifier: throw new JSXIdentifier(ctx, range, 0); // FIXME case AstType.JSXMemberExpression: throw new JSXMemberExpression(ctx, range, 0, 0); // FIXME case AstType.JSXNamespacedName: throw new JSXNamespacedName(ctx, range, 0, 0); // FIXME case AstType.JSXOpeningElement: throw new JSXOpeningElement(ctx, range, flags, 0); // FIXME case AstType.JSXOpeningFragment: throw new JSXOpeningFragment(ctx, range); // FIXME case AstType.JSXSpreadAttribute: throw new JSXSpreadAttribute(ctx, range, flags); // FIXME case AstType.JSXSpreadChild: throw new JSXSpreadChild(ctx, range, flags); // FIXME case AstType.JSXText: throw new JSXText(ctx, range, 0, 0); // FIXME default: throw new Error(`Unknown ast node ${kind}`); } } /** * @param {Uint8Array} buf * @param {AstContext} buf */ function createAstContext(buf) { console.log(buf); // Extract string table /** @type {Map} */ const strTable = new Map(); let offset = 0; const stringCount = readU32(buf, 0); offset += 4; let id = 0; for (let i = 0; i < stringCount; i++) { const len = readU32(buf, offset); offset += 4; const strBytes = buf.slice(offset, offset + len); offset += len; const s = DECODER.decode(strBytes); strTable.set(id, s); id++; } console.log({ stringCount, strTable }); if (strTable.size !== stringCount) { throw new Error( `Could not deserialize string table. Expected ${stringCount} items, but got ${strTable.size}`, ); } // Build id table const idCount = readU32(buf, offset); offset += 4; const idTable = new Array(idCount); for (let i = 0; i < idCount; i++) { const id = readU32(buf, offset); idTable[i] = id; offset += 4; } console.log({ idCount, idTable }); if (idTable.length !== idCount) { throw new Error( `Could not deserialize id table. Expected ${idCount} items, but got ${idTable.length}`, ); } /** @type {AstContext} */ const ctx = { buf, idTable, strTable, visited: new Set() }; return ctx; } /** * @param {string} fileName * @param {Uint8Array} serializedAst */ export function runPluginsForFile(fileName, serializedAst) { const ctx = createAstContext(serializedAst); console.log(JSON.stringify(ctx, null, 2)); /** @type {Record void>} */ const mergedVisitor = {}; const destroyFns = []; console.log(state); // Instantiate and merge visitors. This allows us to only traverse // the AST once instead of per plugin. for (let i = 0; i < state.plugins.length; i++) { const plugin = state.plugins[i]; for (const name of Object.keys(plugin.rules)) { const rule = plugin.rules[name]; const id = `${plugin.name}/${name}`; const ctx = new Context(id, fileName); const visitor = rule.create(ctx); console.log({ visitor }); for (const name in visitor) { const prev = mergedVisitor[name]; mergedVisitor[name] = (node) => { if (typeof prev === "function") { prev(node); } try { visitor[name](node); } catch (err) { throw new Error(`Visitor "${name}" of plugin "${id}" errored`, { cause: err, }); } }; } if (typeof rule.destroy === "function") { const destroyFn = rule.destroy.bind(rule); destroyFns.push(() => { try { destroyFn(ctx); } catch (err) { throw new Error(`Destroy hook of "${id}" errored`, { cause: err }); } }); } } } // Traverse ast with all visitors at the same time to avoid traversing // multiple times. try { traverse(ctx, mergedVisitor); } finally { // Optional: Destroy rules for (let i = 0; i < destroyFns.length; i++) { destroyFns[i](); } } } /** * @param {AstContext} ctx * @param {*} visitor * @returns {void} */ function traverse(ctx, visitor) { const visitTypes = new Map(); // TODO: create visiting types for (const name in visitor) { const id = AstType[name]; visitTypes.set(id, name); } console.log("merged visitor", visitor); console.log("visiting types", visitTypes); // Program is always id 1 const id = 1; traverseInner(ctx, visitTypes, visitor, id); } /** * @param {AstContext} ctx * @param {Map} visitTypes * @param {Record void>} visitor * @param {number} id */ function traverseInner(ctx, visitTypes, visitor, id) { console.log("traversing id", id); // Empty id if (id === 0) return; const { idTable, buf, visited } = ctx; if (id >= idTable.length) { throw new Error(`Invalid node id: ${id}`); } if (visited.has(id)) { throw new Error(`Already visited ${id}`); } visited.add(id); let offset = idTable[id]; if (offset === undefined) throw new Error(`Unknown id: ${id}`); const type = buf[offset]; console.log({ id, type, offset }); const name = visitTypes.get(type); if (name !== undefined) { const node = createAstNode(ctx, id); visitor[name](node); } // type + parentId + SpanLo + SpanHi offset += 1 + 4 + 4 + 4; // Children switch (type) { case AstType.Program: { // skip flag reading during traversal offset += 1; const childIds = readChildIds(buf, offset); return traverseChildren(ctx, visitTypes, visitor, childIds); } // Multiple children only case AstType.ArrayExpression: case AstType.BlockStatement: case AstType.SequenceExpression: { const stmtsIds = readChildIds(buf, offset); return traverseChildren(ctx, visitTypes, visitor, stmtsIds); } // Expressions case AstType.CallExpression: { const calleeId = readU32(buf, offset); traverseInner(ctx, visitTypes, visitor, calleeId); const typeArgId = readU32(buf, offset + 4); if (typeArgId > 0) { traverseInner(ctx, visitTypes, visitor, typeArgId); } const childIds = readChildIds(buf, offset + 8); return traverseChildren(ctx, visitTypes, visitor, childIds); } case AstType.ArrowFunctionExpression: { // Skip flags offset += 1; const typeParamId = readU32(buf, offset); offset += 4; if (typeParamId > 0) { traverseInner(ctx, visitTypes, visitor, typeParamId); } const childIds = readChildIds(buf, offset); offset += childIds.length * 4; traverseChildren(ctx, visitTypes, visitor, childIds); const bodyId = readU32(buf, offset); offset += 4; traverseInner(ctx, visitTypes, visitor, bodyId); const returnTypeId = readU32(buf, offset); traverseInner(ctx, visitTypes, visitor, returnTypeId); return; } case AstType.MemberExpression: { // Skip flags offset += 1; const objId = readU32(buf, offset); const propId = readU32(buf, offset + 4); traverseInner(ctx, visitTypes, visitor, objId); traverseInner(ctx, visitTypes, visitor, propId); return; } // Two children case AstType.LabeledStatement: case AstType.WhileStatement: { const firstId = readU32(buf, offset); traverseInner(ctx, visitTypes, visitor, firstId); const secondId = readU32(buf, offset + 4); return traverseInner(ctx, visitTypes, visitor, secondId); } // Single child case AstType.BreakStatement: case AstType.ContinueStatement: case AstType.ExpressionStatement: case AstType.ReturnStatement: case AstType.ParenthesisExpression: { const childId = readU32(buf, offset); return traverseInner(ctx, visitTypes, visitor, childId); } // These have no children case AstType.BooleanLiteral: case AstType.BigIntLiteral: case AstType.DebuggerStatement: case AstType.Identifier: case AstType.NullLiteral: case AstType.NumericLiteral: case AstType.PrivateIdentifier: case AstType.RegExpLiteral: case AstType.StringLiteral: case AstType.TemplateLiteral: case AstType.This: return; default: throw new Error(`Unknown ast type: ${type}`); } } /** * @param {AstContext} ctx * @param {Map} visitTypes * @param {Record} visitor * @param {number[]} ids */ function traverseChildren(ctx, visitTypes, visitor, ids) { for (let i = 0; i < ids.length; i++) { const id = ids[i]; traverseInner(ctx, visitTypes, visitor, id); } }