0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 20:25:12 -05:00
denoland-deno/cli/js/40_lint.js
Marvin Hagemeister 22681b428a WIP
2024-12-10 22:48:54 +01:00

3107 lines
68 KiB
JavaScript

// 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<string> }} 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 Flags = {
ProgramModule: 0b00000001,
BoolFalse: 0b00000000,
BoolTrue: 0b00000001,
FnAsync: 0b00000001,
FnGenerator: 0b00000010,
FnDeclare: 0b00000100,
MemberComputed: 0b00000001,
PropShorthand: 0b00000001,
PropComputed: 0b00000010,
PropGetter: 0b00000100,
PropSetter: 0b00001000,
PropMethod: 0b00010000,
VarVar: 0b00000001,
VarConst: 0b00000010,
VarLet: 0b00000100,
VarDeclare: 0b00001000,
ExportType: 0b000000001,
TplTail: 0b000000001,
};
// 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,
VariableDeclaration: 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,
SpreadElement: 85,
Property: 86,
VariableDeclarator: 87,
CatchClause: 88,
RestElement: 89,
ExportSpecifier: 90,
TemplateElement: 91,
// Patterns
ArrayPattern: 92,
AssignmentPattern: 93,
ObjectPattern: 94,
// 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,
};
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;
}
}
// Declarations
/** @implements {Deno.VariableDeclaration} */
class VariableDeclaration extends BaseNode {
type = /** @type {const} */ ("VariableDeclaration");
range;
get declarations() {
return createChildNodes(this.#ctx, this.#childIds);
}
#ctx;
#childIds;
kind;
/**
* @param {AstContext} ctx
* @param {number} parentId
* @param {Deno.Range} range
* @param {number} flags
* @param {number[]} childIds
*/
constructor(ctx, parentId, range, flags, childIds) {
super(ctx, parentId);
this.#ctx = ctx;
this.range = range;
this.kind = (Flags.VarConst & flags) != 0
? /** @type {const} */ ("const")
: (Flags.VarLet & flags) != 0
? /** @type {const} */ ("let")
: /** @type {const} */ ("var");
// FIXME: Declare
this.#childIds = childIds;
}
}
/** @implements {Deno.VariableDeclarator} */
class VariableDeclarator extends BaseNode {
type = /** @type {const} */ ("VariableDeclarator");
range;
get id() {
return /** @type {*} */ (createAstNode(this.#ctx, this.#nameId));
}
get init() {
if (this.#initId === 0) return null;
return /** @type {*} */ (createAstNode(this.#ctx, this.#initId));
}
#ctx;
#nameId;
#initId;
definite = false;
/**
* @param {AstContext} ctx
* @param {number} parentId
* @param {Deno.Range} range
* @param {number} nameId
* @param {number} initId
*/
constructor(ctx, parentId, range, nameId, initId) {
// FIXME: Definite
super(ctx, parentId);
this.#ctx = ctx;
this.range = range;
this.#nameId = nameId;
this.#initId = initId;
}
}
// Statements
/** @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 extends BaseNode {
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 {number} parentId
* @param {Deno.Range} range
* @param {number} flags
* @param {number} leftId
* @param {number} rightId
*/
constructor(ctx, parentId, range, flags, leftId, rightId) {
super(ctx, parentId);
this.#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 & Flags.MemberComputed) !== 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.ObjectExpression} */
class ObjectExpression extends BaseNode {
type = /** @type {const} */ ("ObjectExpression");
range;
get properties() {
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.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.Property} */
class Property extends BaseNode {
type = /** @type {const} */ ("Property");
range;
#ctx;
get key() {
return /** @type {*} */ (createAstNode(this.#ctx, this.#keyId));
}
get value() {
return /** @type {*} */ (createAstNode(this.#ctx, this.#valueId));
}
#keyId;
#valueId;
// FIXME
computed = false;
method = false;
shorthand = false;
kind = /** @type {const} */ ("get");
/**
* @param {AstContext} ctx
* @param {number} parentId
* @param {Deno.Range} range
* @param {number} keyId
* @param {number} valueId
*/
constructor(ctx, parentId, range, keyId, valueId) {
super(ctx, parentId);
// FIXME flags
this.#ctx = ctx;
this.range = range;
this.#keyId = keyId;
this.#valueId = valueId;
}
}
/** @implements {Deno.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.SpreadElement} */
class SpreadElement extends BaseNode {
type = /** @type {const} */ ("SpreadElement");
range;
#ctx;
#exprId;
get argument() {
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.Super} */
class Super extends BaseNode {
type = /** @type {const} */ ("Super");
range;
/**
* @param {AstContext} ctx
* @param {number} parentId
* @param {Deno.Range} range
*/
constructor(ctx, parentId, range) {
super(ctx, parentId);
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);
}
}
/** @implements {Deno.TemplateLiteral} */
class TemplateLiteral extends BaseNode {
type = /** @type {const} */ ("TemplateLiteral");
range;
#ctx;
#exprIds;
#quasiIds;
get expressions() {
return createChildNodes(this.#ctx, this.#exprIds);
}
get quasis() {
return createChildNodes(this.#ctx, this.#quasiIds);
}
/**
* @param {AstContext} ctx
* @param {number} parentId
* @param {Deno.Range} range
* @param {number[]} quasiIds
* @param {number[]} exprIds
*/
constructor(ctx, parentId, range, quasiIds, exprIds) {
super(ctx, parentId);
this.#ctx = ctx;
this.#quasiIds = quasiIds;
this.#exprIds = exprIds;
this.range = range;
}
}
/** @implements {Deno.TemplateElement} */
class TemplateElement extends BaseNode {
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;
}
}
// 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<number, string>,
* idTable: number[],
* }} 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];
if (offset >= buf.length) {
throw new Error(
`Could not find id: ${id}. Offset ${offset} bigger than buffer length: ${buf.length}`,
);
}
// 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:
// Declarations
case AstType.VariableDeclaration: {
const flags = buf[offset];
const childIds = readChildIds(buf, offset + 1);
return new VariableDeclaration(ctx, parentId, range, flags, childIds);
}
case AstType.VariableDeclarator: {
const nameId = readU32(buf, offset);
const initId = readU32(buf, offset + 4);
return new VariableDeclarator(ctx, parentId, range, nameId, initId);
}
// 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 & Flags.FnAsync) !== 0;
const isGenerator = (flags & Flags.FnGenerator) !== 0;
const typeParamId = readU32(buf, offset);
offset += 4;
const paramIds = readChildIds(buf, offset);
offset += 4;
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: {
const flags = buf[offset];
const leftId = readU32(buf, offset + 1);
const rightId = readU32(buf, offset + 5);
return new AssignmentExpression(
ctx,
parentId,
range,
flags,
leftId,
rightId,
);
}
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.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: {
const elemIds = readChildIds(buf, offset);
return new ObjectExpression(ctx, parentId, range, elemIds);
}
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.Property: {
const flags = buf[offset]; // FIXME
const keyId = readU32(buf, offset + 1);
const valueId = readU32(buf, offset + 5);
return new Property(ctx, parentId, range, keyId, valueId);
}
case AstType.SequenceExpression: {
const childIds = readChildIds(buf, offset);
return new SequenceExpression(ctx, parentId, range, childIds);
}
case AstType.SpreadElement: {
const exprId = readU32(buf, offset);
return new SpreadElement(ctx, parentId, range, exprId);
}
case AstType.Super:
throw new Super(ctx, parentId, range);
case AstType.TaggedTemplateExpression:
throw new Error("FIXME");
case AstType.TemplateElement: {
const flags = buf[offset];
const tail = (flags & Flags.TplTail) !== 0;
const rawId = readU32(buf, offset + 1);
const cookedId = readU32(buf, offset + 5);
return new TemplateElement(ctx, parentId, range, rawId, cookedId, tail);
}
case AstType.TemplateLiteral: {
const quasiIds = readChildIds(buf, offset);
offset += 4;
offset += quasiIds.length * 4;
const exprIds = readChildIds(buf, offset);
return new TemplateLiteral(ctx, parentId, range, quasiIds, exprIds);
}
// 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<number, string>} */
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 };
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<string, (node: any) => 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) {
// FIXME: console here doesn't support error cause
console.log(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<number, string>} visitTypes
* @param {Record<string, (x: any) => 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 } = ctx;
if (id >= idTable.length) {
throw new Error(`Invalid node id: ${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);
}
case AstType.VariableDeclaration: {
// Skip flags
offset += 1;
const childIds = readChildIds(buf, offset);
traverseChildren(ctx, visitTypes, visitor, childIds);
return;
}
// Multiple children only
case AstType.ArrayExpression:
case AstType.BlockStatement:
case AstType.ObjectExpression:
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 += 4;
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;
}
case AstType.AssignmentExpression: {
// Skip flags
offset += 1;
const leftId = readU32(buf, offset);
const rightId = readU32(buf, offset + 4);
traverseInner(ctx, visitTypes, visitor, leftId);
traverseInner(ctx, visitTypes, visitor, rightId);
return;
}
case AstType.Property: {
// Skip flags
offset += 1;
const keyId = readU32(buf, offset);
const valueId = readU32(buf, offset + 4);
traverseInner(ctx, visitTypes, visitor, keyId);
traverseInner(ctx, visitTypes, visitor, valueId);
return;
}
// Two children
case AstType.AssignmentPattern:
case AstType.LabeledStatement:
case AstType.VariableDeclarator:
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.SpreadElement:
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<number, string>} visitTypes
* @param {Record<string, *>} 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);
}
}