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

1353 lines
30 KiB
JavaScript
Raw Permalink Normal View History

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