0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-08 07:16:56 -05:00
denoland-deno/cli/js/40_lint.js
2024-12-13 00:33:44 +01:00

1352 lines
30 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) {
const range = data.node ? data.node.range : data.range ? data.range : null;
if (range == null) {
throw new Error(
"Either `node` or `span` must be provided when reporting an error",
);
}
const start = range[0] - 1;
const end = range[1] - 1;
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,
FnAsync: 0b00000001,
FnGenerator: 0b00000010,
FnDeclare: 0b00000100,
FnOptional: 0b00001000,
MemberComputed: 0b00000001,
MemberOptional: 0b00000010,
PropShorthand: 0b00000001,
PropComputed: 0b00000010,
PropGetter: 0b00000100,
PropSetter: 0b00001000,
PropMethod: 0b00010000,
VarVar: 0b00000001,
VarConst: 0b00000010,
VarLet: 0b00000100,
VarDeclare: 0b00001000,
ExportType: 0b000000001,
TplTail: 0b000000001,
ForAwait: 0b000000001,
LogicalOr: 0b000000001,
LogicalAnd: 0b000000010,
LogicalNullishCoalescin: 0b000000100,
JSXSelfClosing: 0b000000001,
BinEqEq: 1,
BinNotEq: 2,
BinEqEqEq: 3,
BinNotEqEq: 4,
BinLt: 5,
BinLtEq: 6,
BinGt: 7,
BinGtEq: 8,
BinLShift: 9,
BinRShift: 10,
BinZeroFillRShift: 11,
BinAdd: 12,
BinSub: 13,
BinMul: 14,
BinDiv: 15,
BinMod: 16,
BinBitOr: 17,
BinBitXor: 18,
BinBitAnd: 19,
BinIn: 20,
BinInstanceOf: 21,
BinExp: 22,
UnaryMinus: 1,
UnaryPlus: 2,
UnaryBang: 3,
UnaryTilde: 4,
UnaryTypeOf: 5,
UnaryVoid: 6,
UnaryDelete: 7,
UpdatePrefix: 0b000000001,
UpdatePlusPlus: 0b000000010,
UpdateMinusMinus: 0b000000100,
YieldDelegate: 1,
ParamOptional: 1,
ClassDeclare: 0b000000001,
ClassAbstract: 0b000000010,
ClassConstructor: 0b000000100,
ClassMethod: 0b000001000,
ClassPublic: 0b001000000,
ClassProtected: 0b010000000,
ClassPrivate: 0b100000000,
};
// 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
ClassDeclaration: 12,
FunctionDeclaration: 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,
UnaryExpression: 44,
UpdateExpression: 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,
YieldExpression: 60,
MetaProperty: 61,
AwaitExpression: 62,
LogicalExpression: 63,
TSTypeAssertion: 64,
TSConstAssertion: 65,
TSNonNull: 66,
TSAs: 67,
TSInstantiation: 68,
TSSatisfies: 69,
PrivateIdentifier: 70,
ChainExpression: 71,
StringLiteral: 72,
BooleanLiteral: 73,
NullLiteral: 74,
NumericLiteral: 75,
BigIntLiteral: 76,
RegExpLiteral: 77,
// Custom
EmptyExpr: 78,
SpreadElement: 79,
Property: 80,
VariableDeclarator: 81,
CatchClause: 82,
RestElement: 83,
ExportSpecifier: 84,
TemplateElement: 85,
MethodDefinition: 86,
// Patterns
ArrayPattern: 87,
AssignmentPattern: 88,
ObjectPattern: 89,
// JSX
JSXAttribute: 90,
JSXClosingElement: 91,
JSXClosingFragment: 92,
JSXElement: 93,
JSXEmptyExpression: 94,
JSXExpressionContainer: 95,
JSXFragment: 96,
JSXIdentifier: 97,
JSXMemberExpression: 98,
JSXNamespacedName: 99,
JSXOpeningElement: 100,
JSXOpeningFragment: 101,
JSXSpreadAttribute: 102,
JSXSpreadChild: 103,
JSXText: 104,
};
const AstTypeName = Object.keys(AstType);
// Keep in sync with Rust
const AstProp = [
// Base
"parent",
"range",
"type",
"_InternalFlags", // Internal
// Node
"alternate",
"argument",
"arguments",
"async",
"attributes",
"await",
"block",
"body",
"callee",
"cases",
"children",
"closingElement",
"closingFragment",
"computed",
"consequent",
"cooked",
"declarations",
"declare",
"definite",
"delegate",
"discriminant",
"elements",
"elementTypes",
"expression",
"expressions",
"exported",
"finalizer",
"flags",
"generator",
"handler",
"id",
"init",
"key",
"kind",
"label",
"left",
"local",
"members",
"meta",
"method",
"name",
"namespace",
"object",
"openingElement",
"openingFragment",
"operator",
"optional",
"param",
"params",
"pattern",
"prefix",
"properties",
"property",
"quasi",
"quasis",
"raw",
"returnType",
"right",
"selfClosing",
"shorthand",
"source",
"specifiers",
"tag",
"tail",
"test",
"typeAnnotation",
"typeArguments",
"typeParameters",
"types",
"update",
"value",
];
const AST_PROP_PARENT = AstProp.indexOf("parent");
const AST_PROP_TYPE = AstProp.indexOf("type");
const AST_PROP_BODY = AstProp.indexOf("body");
const AST_PROP_CHILDREN = AstProp.indexOf("children");
const AST_PROP_ELEMENTS = AstProp.indexOf("elements");
const AST_PROP_PROPERTIES = AstProp.indexOf("properties");
const AST_PROP_ARGUMENTS = AstProp.indexOf("arguments");
const AST_PROP_PARAMS = AstProp.indexOf("params");
const AST_PROP_EXPRESSIONS = AstProp.indexOf("expressions");
const AST_PROP_QUASIS = AstProp.indexOf("quasis");
const AST_PROP_RANGE = AstProp.indexOf("range");
const AST_PROP_INTERNAL_FLAGS = AstProp.indexOf("_InternalFlags");
const AST_PROP_ATTRIBUTE = AstProp.indexOf("attribute");
const AST_PROP_CONSEQUENT = AstProp.indexOf("consequent");
const AST_PROP_CASES = AstProp.indexOf("cases");
const AST_PROP_SPECIFIERS = AstProp.indexOf("specifiers");
const AST_PROP_DECLARATIONS = AstProp.indexOf("declarations");
const AST_PROP_MEMBERS = AstProp.indexOf("members");
/**
* @param {number} type
* @param {number} propId
* @returns {boolean}
*/
function isArrayProp(type, propId) {
switch (type) {
case AstType.Program:
case AstType.BlockStatement:
// case AstType.StaticBlock:
return propId === AST_PROP_BODY;
case AstType.JSXOpeningElement:
return propId === AST_PROP_ATTRIBUTE;
case AstType.SwitchCase:
return propId === AST_PROP_CONSEQUENT;
default:
return propId === AST_PROP_CHILDREN || propId === AST_PROP_ELEMENTS ||
propId === AST_PROP_PROPERTIES || propId === AST_PROP_PARAMS ||
propId === AST_PROP_ARGUMENTS || propId === AST_PROP_EXPRESSIONS ||
propId === AST_PROP_QUASIS || propId === AST_PROP_CASES ||
propId === AST_PROP_SPECIFIERS ||
propId === AST_PROP_DECLARATIONS || propId === AST_PROP_MEMBERS;
}
}
/**
* @param {AstContext} ctx
* @param {number} offset
* @param {number} propId
*/
function readValue(ctx, offset, propId) {
const { buf } = ctx;
if (propId === AST_PROP_TYPE) {
const type = buf[offset];
return AstTypeName[type];
} else if (propId === AST_PROP_RANGE) {
const start = readU32(buf, offset + 1 + 4);
const end = readU32(buf, offset + 1 + 4 + 4);
return [start, end];
} else if (propId === AST_PROP_PARENT) {
const id = readU32(buf, offset + 1);
if (id === 0) return null;
const target = ctx.idTable[id];
return new Node(ctx, target);
} else if (propId === AST_PROP_INTERNAL_FLAGS) {
// FIXME
return 0;
}
const type = buf[offset];
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
const propCount = buf[offset];
offset += 1;
for (let i = 0; i < propCount; i++) {
const searchProp = buf[offset];
offset += 1;
const isArray = isArrayProp(type, searchProp);
if (searchProp === propId) {
// Check for arrays
if (isArray) {
const len = readU32(buf, offset);
offset += 4;
const nodes = new Array(len).fill(null);
for (let j = 0; j < len; j++) {
nodes.push(new Node(ctx, offset));
offset += 4;
}
return nodes;
}
const id = readU32(buf, offset);
if (id === 0) return null;
const target = ctx.idTable[id];
return new Node(ctx, target);
}
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
offset += 1;
} else if (isArray) {
const len = readU32(buf, offset);
offset += 4 + (len * 4);
} else {
offset += 4;
}
}
return 0;
}
const INTERNAL_CTX = Symbol("ctx");
const INTERNAL_OFFSET = Symbol("offset");
class Node {
[INTERNAL_CTX];
[INTERNAL_OFFSET];
/**
* @param {AstContext} ctx
* @param {number} offset
*/
constructor(ctx, offset) {
this[INTERNAL_CTX] = ctx;
this[INTERNAL_OFFSET] = offset;
}
}
for (let i = 0; i < AstProp.length; i++) {
const name = AstProp[i];
Object.defineProperty(Node.prototype, name, {
get() {
return readValue(this[INTERNAL_CTX], this[INTERNAL_OFFSET], i);
},
});
}
// /** @implements {Deno.AssignmentExpression} */
// class AssignmentExpression extends Node {
// operator;
// 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.UpdateExpression} */
// class UpdateExpression extends Node {
// type = /** @type {const} */ ("UpdateExpression");
// range;
// prefix;
// operator;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} flags
// * @param {number} argId
// */
// constructor(ctx, parentId, range, flags, argId) {
// super(ctx, parentId);
// this.range = range;
// this.prefix = (flags & Flags.UpdatePrefix) !== 0;
// this.operator = (flags & Flags.UpdatePlusPlus) !== 0
// ? /** @type {const} */ ("++")
// : /** @type {const} */ ("--");
// }
// }
// /** @implements {Deno.BinaryExpression} */
// class BinaryExpression extends Node {
// type = /** @type {const} */ ("BinaryExpression");
// operator;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} flags
// * @param {number} leftId
// * @param {number} rightId
// */
// constructor(ctx, parentId, range, flags, leftId, rightId) {
// super(ctx, parentId);
// this.operator = getBinaryOperator(flags);
// this.range = range;
// }
// }
/**
* @param {number} n
* @returns {Deno.BinaryExpression["operator"]}
*/
function getBinaryOperator(n) {
switch (n) {
case 1:
return "==";
case 2:
return "!=";
case 3:
return "===";
case 4:
return "!==";
case 5:
return "<";
case 6:
return "<=";
case 7:
return ">";
case 8:
return ">=";
case 9:
return "<<";
case 10:
return ">>";
case 11:
return ">>>";
case 12:
return "+";
case 13:
return "-";
case 14:
return "*";
case 15:
return "/";
case 16:
return "%";
case 17:
return "|";
case 18:
return "^";
case 19:
return "&";
case 20:
return "in";
case 21:
return "instanceof";
case 22:
return "**";
default:
throw new Error(`Unknown operator: ${n}`);
}
}
// /** @implements {Deno.CallExpression} */
// class CallExpression extends Node {
// this.optional = (flags & Flags.FnOptional) !== 0;
// this.#ctx = ctx;
// this.#calleeId = calleeId;
// this.range = range;
// this.#typeArgId = typeArgId;
// this.#argumentIds = argumentIds;
// }
// }
// /** @implements {Deno.FunctionExpression} */
// class FunctionExpression extends Node {
// constructor(
// ) {
// super(ctx, parentId);
// this.async = (flags & Flags.FnAsync) !== 0;
// this.generator = (flags & Flags.FnGenerator) !== 0;
// }
// }
// /** @implements {Deno.Identifier} */
// class Identifier extends Node {
// type = /** @type {const} */ ("Identifier");
// range;
// name = "";
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} nameId
// */
// constructor(ctx, parentId, range, nameId) {
// super(ctx, parentId);
// this.name = getString(ctx, nameId);
// this.range = range;
// }
// }
// /** @implements {Deno.LogicalExpression} */
// class LogicalExpression extends Node {
// type = /** @type {const} */ ("LogicalExpression");
// constructor(ctx, parentId, range, flags, leftId, rightId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.operator = getLogicalOperator(flags);
// this.#leftId = leftId;
// this.#rightId = rightId;
// this.range = range;
// }
// }
/**
* @param {number} n
* @returns {Deno.LogicalExpression["operator"]}
*/
function getLogicalOperator(n) {
if ((n & Flags.LogicalAnd) !== 0) {
return "&&";
} else if ((n & Flags.LogicalOr) !== 0) {
return "||";
} else if ((n & Flags.LogicalNullishCoalescin) !== 0) {
return "??";
}
throw new Error(`Unknown operator: ${n}`);
}
// /** @implements {Deno.MemberExpression} */
// class MemberExpression extends Node {
// constructor(ctx, parentId, range, flags, objId, propId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.computed = (flags & Flags.MemberComputed) !== 0;
// this.optional = (flags & Flags.MemberOptional) !== 0;
// this.#objId = objId;
// this.#propId = propId;
// this.range = range;
// }
// }
// /** @implements {Deno.PrivateIdentifier} */
// class PrivateIdentifier extends Node {
// type = /** @type {const} */ ("PrivateIdentifier");
// range;
// #ctx;
// name;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} nameId
// */
// constructor(ctx, parentId, range, nameId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.range = range;
// this.name = getString(ctx, nameId);
// }
// }
// /** @implements {Deno.Property} */
// class Property extends Node {
// type = /** @type {const} */ ("Property");
// range;
// constructor(ctx, parentId, range, keyId, valueId) {
// super(ctx, parentId);
// // FIXME flags
// this.#ctx = ctx;
// this.range = range;
// this.#keyId = keyId;
// this.#valueId = valueId;
// }
// }
// /** @implements {Deno.UnaryExpression} */
// class UnaryExpression extends Node {
// type = /** @type {const} */ ("UnaryExpression");
// range;
// constructor(ctx, parentId, range, flags, exprId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.range = range;
// this.operator = getUnaryOperator(flags);
// this.#exprId = exprId;
// }
// }
/**
* @param {number} n
* @returns {Deno.UnaryExpression["operator"]}
*/
function getUnaryOperator(n) {
switch (n) {
case 1:
return "-";
case 2:
return "+";
case 3:
return "!";
case 4:
return "~";
case 5:
return "typeof";
case 6:
return "void";
case 7:
return "delete";
}
}
// /** @implements {Deno.YieldExpression} */
// class YieldExpression extends Node {
// type = /** @type {const} */ ("YieldExpression");
// range;
// get argument() {
// return /** @type {Deno.Expression} */ (createAstNode(
// this.#ctx,
// this.#argId,
// ));
// }
// delegate = false;
// #ctx;
// #argId;
// constructor(ctx, parentId, range, flags, argId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.#argId = argId;
// this.range = range;
// this.delegate = (flags & Flags.YieldDelegate) !== 0;
// }
// }
// Literals
// /** @implements {Deno.BooleanLiteral} */
// class BooleanLiteral extends Node {
// type = /** @type {const} */ ("BooleanLiteral");
// range;
// value = false;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} flags
// */
// constructor(ctx, parentId, range, flags) {
// super(ctx, parentId);
// this.value = flags === 1;
// this.range = range;
// }
// }
// /** @implements {Deno.BigIntLiteral} */
// class BigIntLiteral extends Node {
// type = /** @type {const} */ ("BigIntLiteral");
// range;
// value;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} strId
// */
// constructor(ctx, parentId, range, strId) {
// super(ctx, parentId);
// this.range = range;
// this.value = BigInt(getString(ctx, strId));
// }
// }
// /** @implements {Deno.NullLiteral} */
// class NullLiteral extends Node {
// type = /** @type {const} */ ("NullLiteral");
// range;
// value = null;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// */
// constructor(ctx, parentId, range) {
// super(ctx, parentId);
// this.range = range;
// }
// }
// /** @implements {Deno.NumericLiteral} */
// class NumericLiteral extends Node {
// type = /** @type {const} */ ("NumericLiteral");
// range;
// value = 0;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} strId
// */
// constructor(ctx, parentId, range, strId) {
// super(ctx, parentId);
// this.range = range;
// this.value = Number(getString(ctx, strId));
// }
// }
// /** @implements {Deno.RegExpLiteral} */
// class RegExpLiteral extends Node {
// type = /** @type {const} */ ("RegExpLiteral");
// range;
// pattern = "";
// flags = "";
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} patternId
// * @param {number} flagsId
// */
// constructor(ctx, parentId, range, patternId, flagsId) {
// super(ctx, parentId);
// this.range = range;
// this.pattern = getString(ctx, patternId);
// this.flags = getString(ctx, flagsId);
// }
// }
// /** @implements {Deno.StringLiteral} */
// class StringLiteral extends Node {
// type = /** @type {const} */ ("StringLiteral");
// range;
// value = "";
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} strId
// */
// constructor(ctx, parentId, range, strId) {
// super(ctx, parentId);
// this.range = range;
// this.value = getString(ctx, strId);
// }
// }
// /** @implements {Deno.TemplateElement} */
// class TemplateElement extends Node {
// type = /** @type {const} */ ("TemplateElement");
// range;
// tail = false;
// value;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} rawId
// * @param {number} cookedId
// * @param {boolean} tail
// */
// constructor(ctx, parentId, range, rawId, cookedId, tail) {
// super(ctx, parentId);
// const raw = getString(ctx, rawId);
// this.value = {
// raw,
// cooked: cookedId === 0 ? raw : getString(ctx, cookedId),
// };
// this.tail = tail;
// this.range = range;
// }
// }
// Patterns
// /** @implements {Deno.ArrayPattern} */
// class ArrayPattern extends Node {
// constructor(ctx, parentId, range, flags, elemIds, typeId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.range = range;
// this.optional = (flags & Flags.ParamOptional) !== 0;
// this.#elemIds = elemIds;
// this.#typeId = typeId;
// }
// }
// /** @implements {Deno.ObjectPattern} */
// class ObjectPattern extends Node {
// type = /** @type {const} */ ("ObjectPattern");
// range;
// constructor(ctx, parentId, range, flags, elemIds, typeId) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.range = range;
// this.optional = (flags & Flags.ParamOptional) !== 0;
// this.#elemIds = elemIds;
// this.#typeId = typeId;
// }
// }
// /** @implements {Deno.JSXIdentifier} */
// class JSXIdentifier extends Node {
// type = /** @type {const} */ ("JSXIdentifier");
// range;
// name;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} nameId
// */
// constructor(ctx, parentId, range, nameId) {
// super(ctx, parentId);
// this.range = range;
// this.name = getString(ctx, nameId);
// }
// }
// /** @implements {Deno.JSXOpeningElement} */
// class JSXOpeningElement extends Node {
// constructor(ctx, parentId, range, isSelfClosing, nameId, attrIds) {
// super(ctx, parentId);
// this.#ctx = ctx;
// this.selfClosing = isSelfClosing;
// this.#nameId = nameId;
// this.#attrIds = attrIds;
// this.range = range;
// }
// }
const DECODER = new TextDecoder();
/**
* @typedef {{
* buf: Uint8Array,
* strTable: Map<number, string>,
* idTable: number[],
* rootId: 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 {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 {Uint8Array} buf
* @param {AstContext} buf
*/
function createAstContext(buf) {
/** @type {Map<number, string>} */
const strTable = new Map();
// console.log(JSON.stringify(buf, null, 2));
const strTableOffset = readU32(buf, buf.length - 12);
const idTableOffset = readU32(buf, buf.length - 8);
const rootId = readU32(buf, buf.length - 4);
// console.log({ strTableOffset, idTableOffset, rootId });
let offset = strTableOffset;
const stringCount = readU32(buf, offset);
offset += 4;
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, idTableOffset);
offset += 4;
const idTable = new Array(idCount);
for (let i = 0; i < idCount; i++) {
const id = readU32(buf, offset);
idTable[i] = id;
offset += 4;
}
if (idTable.length !== idCount) {
throw new Error(
`Could not deserialize id table. Expected ${idCount} items, but got ${idTable.length}`,
);
}
/** @type {AstContext} */
const ctx = { buf, idTable, strTable, rootId };
// console.log({ strTable, idTable });
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("buffer len", ctx.buf.length, ctx.buf.byteLength);
console.log("merged visitor", visitor);
console.log("visiting types", visitTypes);
traverseInner(ctx, visitTypes, visitor, ctx.rootId);
}
const SKIP_CHILD_TRAVERSAL = new Set([
AstType.BooleanLiteral,
AstType.BigIntLiteral,
AstType.DebuggerStatement,
AstType.Identifier,
AstType.JSXClosingFragment,
AstType.JSXEmptyExpression,
AstType.JSXOpeningFragment,
AstType.JSXText,
AstType.NullLiteral,
AstType.NumericLiteral,
AstType.PrivateIdentifier,
AstType.RegExpLiteral,
AstType.StringLiteral,
AstType.TemplateLiteral,
AstType.This,
]);
/**
* @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) {
// console.log("--> invoking visitor");
const node = new Node(ctx, offset);
visitor[name](node);
}
if (SKIP_CHILD_TRAVERSAL.has(type)) return;
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
const propCount = buf[offset];
offset += 1;
// console.log({ propCount });
for (let i = 0; i < propCount; i++) {
const searchProp = buf[offset];
offset += 1;
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
offset += 1;
continue;
}
if (isArrayProp(type, searchProp)) {
const len = readU32(buf, offset);
offset += 4;
for (let j = 0; j < len; j++) {
const childId = readU32(buf, offset);
offset += 4;
traverseInner(ctx, visitTypes, visitor, childId);
}
continue;
}
const childId = readU32(buf, offset);
traverseInner(ctx, visitTypes, visitor, childId);
offset += 4;
}
}