0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-22 13:13:32 -05:00
denoland-deno/cli/js/40_lint.js

1296 lines
29 KiB
JavaScript
Raw 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",
];
2024-12-13 11:38:35 +01:00
// FIXME: this is slow
2024-12-12 22:16:49 +01:00
const AST_PROP_PARENT = AstProp.indexOf("parent");
const AST_PROP_TYPE = AstProp.indexOf("type");
2024-12-13 00:33:44 +01:00
const AST_PROP_BODY = AstProp.indexOf("body");
const AST_PROP_CHILDREN = AstProp.indexOf("children");
const AST_PROP_ELEMENTS = AstProp.indexOf("elements");
const AST_PROP_PROPERTIES = AstProp.indexOf("properties");
const AST_PROP_ARGUMENTS = AstProp.indexOf("arguments");
const AST_PROP_PARAMS = AstProp.indexOf("params");
const AST_PROP_EXPRESSIONS = AstProp.indexOf("expressions");
const AST_PROP_QUASIS = AstProp.indexOf("quasis");
2024-12-12 22:16:49 +01:00
const AST_PROP_RANGE = AstProp.indexOf("range");
const AST_PROP_INTERNAL_FLAGS = AstProp.indexOf("_InternalFlags");
const AST_PROP_ATTRIBUTE = AstProp.indexOf("attribute");
2024-12-13 00:33:44 +01:00
const AST_PROP_CONSEQUENT = AstProp.indexOf("consequent");
const AST_PROP_CASES = AstProp.indexOf("cases");
const AST_PROP_SPECIFIERS = AstProp.indexOf("specifiers");
const AST_PROP_DECLARATIONS = AstProp.indexOf("declarations");
const AST_PROP_MEMBERS = AstProp.indexOf("members");
2024-12-13 11:38:35 +01:00
const AST_PROP_OPERATOR = AstProp.indexOf("operator");
const AST_PROP_PREFIX = AstProp.indexOf("prefix");
const AST_PROP_OPTIONAL = AstProp.indexOf("optional");
const AST_PROP_ASYNC = AstProp.indexOf("async");
const AST_PROP_GENERATOR = AstProp.indexOf("generator");
const AST_PROP_NAME = AstProp.indexOf("name");
const AST_PROP_VALUE = AstProp.indexOf("value");
const AST_PROP_COMPUTED = AstProp.indexOf("computed");
const AST_PROP_DELEGATE = AstProp.indexOf("delegate");
const AST_PROP_SELF_CLOSING = AstProp.indexOf("selfClosing");
2024-12-13 00:33:44 +01:00
/**
* @param {number} type
* @param {number} propId
* @returns {boolean}
*/
function isArrayProp(type, propId) {
switch (type) {
case AstType.Program:
case AstType.BlockStatement:
// case AstType.StaticBlock:
return propId === AST_PROP_BODY;
case AstType.JSXOpeningElement:
return propId === AST_PROP_ATTRIBUTE;
case AstType.SwitchCase:
return propId === AST_PROP_CONSEQUENT;
default:
2024-12-13 11:38:35 +01:00
switch (propId) {
case AST_PROP_CHILDREN:
case AST_PROP_ELEMENTS:
case AST_PROP_PROPERTIES:
case AST_PROP_PARAMS:
case AST_PROP_ARGUMENTS:
case AST_PROP_EXPRESSIONS:
case AST_PROP_QUASIS:
case AST_PROP_CASES:
case AST_PROP_SPECIFIERS:
case AST_PROP_DECLARATIONS:
case AST_PROP_MEMBERS:
return true;
default:
return false;
}
2024-12-13 00:33:44 +01:00
}
}
2024-12-10 04:33:33 +01:00
/**
* @param {AstContext} ctx
2024-12-12 22:16:49 +01:00
* @param {number} offset
* @param {number} propId
2024-12-13 11:38:35 +01:00
* @returns {*}
2024-12-10 04:33:33 +01:00
*/
2024-12-12 22:16:49 +01:00
function readValue(ctx, offset, propId) {
const { buf } = ctx;
2024-12-10 04:33:33 +01:00
2024-12-12 22:16:49 +01:00
if (propId === AST_PROP_TYPE) {
const type = buf[offset];
return AstTypeName[type];
} else if (propId === AST_PROP_RANGE) {
const start = readU32(buf, offset + 1 + 4);
const end = readU32(buf, offset + 1 + 4 + 4);
return [start, end];
} else if (propId === AST_PROP_PARENT) {
2024-12-13 11:38:35 +01:00
return readU32(buf, offset + 1);
2024-12-13 00:33:44 +01:00
}
const type = buf[offset];
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
const propCount = buf[offset];
offset += 1;
for (let i = 0; i < propCount; i++) {
const searchProp = buf[offset];
offset += 1;
if (searchProp === propId) {
2024-12-13 11:38:35 +01:00
if (propId === AST_PROP_INTERNAL_FLAGS) {
return buf[offset];
} else if (isArrayProp(type, searchProp)) {
2024-12-13 00:33:44 +01:00
const len = readU32(buf, offset);
offset += 4;
2024-12-13 11:38:35 +01:00
const ids = new Array(len).fill(null);
2024-12-13 00:33:44 +01:00
for (let j = 0; j < len; j++) {
2024-12-13 11:38:35 +01:00
ids[i] = readU32(buf, offset);
2024-12-13 00:33:44 +01:00
offset += 4;
}
2024-12-13 11:38:35 +01:00
return ids;
2024-12-13 00:33:44 +01:00
}
2024-12-12 22:16:49 +01:00
2024-12-13 11:38:35 +01:00
return readU32(buf, offset);
2024-12-12 22:16:49 +01:00
}
2024-12-13 00:33:44 +01:00
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
offset += 1;
2024-12-13 11:38:35 +01:00
} else if (isArrayProp(type, searchProp)) {
2024-12-13 00:33:44 +01:00
const len = readU32(buf, offset);
offset += 4 + (len * 4);
} else {
offset += 4;
}
2024-12-12 22:16:49 +01:00
}
2024-12-13 00:33:44 +01:00
return 0;
2024-12-10 04:33:33 +01:00
}
2024-12-13 11:38:35 +01:00
/**
* @param {AstContext} ctx
* @param {number} offset
* @returns {number}
*/
function getTypeId(ctx, offset) {
return ctx.buf[offset];
}
/**
* @param {AstContext} ctx
* @param {number} offset
* @param {Map<number, any>} seen
* @returns {*}
*/
function toJsValue(ctx, offset, seen) {
const cached = seen.get(offset);
if (cached !== undefined) return cached;
const type = getTypeId(ctx, offset);
const range = readValue(ctx, offset, AST_PROP_RANGE);
/** @type {Record<string, any>} */
const node = {
type: AstTypeName[type],
range,
};
seen.set(offset, node);
// console.log("toJSON", AstTypeName[type], offset);
const { buf, idTable } = ctx;
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
const propCount = buf[offset];
offset += 1;
let flags = 0;
for (let i = 0; i < propCount; i++) {
const propId = buf[offset];
offset += 1;
const name = AstProp[propId];
// console.log(`reading node ${AstTypeName[type]}, prop ${name}`);
if (propId === AST_PROP_INTERNAL_FLAGS) {
flags = buf[offset];
offset += 1;
} else if (
propId === AST_PROP_NAME && (type === AstType.Identifier ||
type === AstType.JSXIdentifier)
) {
const strId = readU32(buf, offset);
offset += 4;
node[name] = getString(ctx, strId);
} else if (
propId === AST_PROP_VALUE && (type === AstType.StringLiteral ||
type === AstType.NumericLiteral)
) {
const strId = readU32(buf, offset);
offset += 4;
node[name] = getString(ctx, strId);
} else if (isArrayProp(type, propId)) {
const len = readU32(buf, offset);
offset += 4;
const elems = new Array(len).fill(null);
for (let j = 0; j < len; j++) {
const id = readU32(buf, offset);
offset += 4;
if (id === 0) {
elems[j] = undefined;
} else {
const nodeOffset = idTable[id];
elems[j] = toJsValue(ctx, nodeOffset, seen);
}
}
node[name] = elems;
} else {
const id = readU32(buf, offset);
offset += 4;
if (id === 0) {
node[name] = null;
} else {
const nodeOffset = idTable[id];
node[name] = toJsValue(ctx, nodeOffset, seen);
}
}
}
return node;
}
2024-12-13 00:33:44 +01:00
const INTERNAL_CTX = Symbol("ctx");
const INTERNAL_OFFSET = Symbol("offset");
2024-12-12 22:16:49 +01:00
class Node {
2024-12-13 00:33:44 +01:00
[INTERNAL_CTX];
[INTERNAL_OFFSET];
2024-12-10 04:33:33 +01:00
/**
* @param {AstContext} ctx
2024-12-12 22:16:49 +01:00
* @param {number} offset
2024-12-10 04:33:33 +01:00
*/
2024-12-12 22:16:49 +01:00
constructor(ctx, offset) {
2024-12-13 00:33:44 +01:00
this[INTERNAL_CTX] = ctx;
this[INTERNAL_OFFSET] = offset;
2024-12-10 04:33:33 +01:00
}
2024-12-13 11:38:35 +01:00
/**
* @param {*} _
* @param {*} options
* @returns {string}
*/
[Symbol.for("Deno.customInspect")](_, options) {
const seen = new Map();
const json = toJsValue(this[INTERNAL_CTX], this[INTERNAL_OFFSET], seen);
seen.clear();
return Deno.inspect(json, options);
}
2024-12-10 04:33:33 +01:00
}
2024-12-05 01:18:08 +01:00
2024-12-13 00:33:44 +01:00
for (let i = 0; i < AstProp.length; i++) {
const name = AstProp[i];
Object.defineProperty(Node.prototype, name, {
get() {
2024-12-13 11:38:35 +01:00
const ctx = /** @type {AstContext} */ (this[INTERNAL_CTX]);
const offset = this[INTERNAL_OFFSET];
const type = getTypeId(ctx, offset);
const flags = readValue(ctx, offset, AST_PROP_INTERNAL_FLAGS);
switch (i) {
case AST_PROP_OPERATOR:
switch (type) {
case AstType.AssignmentExpression:
return getAssignOperator(flags);
case AstType.BinaryExpression:
return getBinaryOperator(flags);
case AstType.UpdateExpression:
return (flags & Flags.UpdatePlusPlus) !== 0 ? "++" : "--";
case AstType.LogicalExpression:
return getLogicalOperator(flags);
case AstType.UnaryExpression:
return getUnaryOperator(flags);
}
2024-12-12 22:16:49 +01:00
2024-12-13 11:38:35 +01:00
break;
case AST_PROP_PREFIX:
return (flags & Flags.UpdatePrefix) !== 0;
case AST_PROP_OPTIONAL:
switch (type) {
case AstType.FunctionExpression:
return (flags & Flags.FnOptional) !== 0;
case AstType.MemberExpression:
return (flags & Flags.MemberOptional) !== 0;
case AstType.ArrayPattern:
case AstType.ObjectPattern:
return (flags & Flags.ParamOptional) !== 0;
}
2024-12-10 04:33:33 +01:00
2024-12-13 11:38:35 +01:00
break;
case AST_PROP_ASYNC:
return (flags & Flags.FnAsync) !== 0;
case AST_PROP_GENERATOR:
return (flags & Flags.FnGenerator) !== 0;
case AST_PROP_COMPUTED:
return (flags & Flags.MemberComputed) !== 0;
case AST_PROP_DELEGATE:
return (flags & Flags.YieldDelegate) !== 0;
case AST_PROP_SELF_CLOSING:
return flags !== 0;
case AST_PROP_NAME: {
const value = readValue(ctx, offset, AST_PROP_NAME);
switch (type) {
case AstType.Identifier:
case AstType.JSXIdentifier:
case AstType.PrivateIdentifier:
return getString(ctx, value);
default: {
const nodeOffset = ctx.idTable[value];
return new Node(ctx, nodeOffset);
}
}
}
}
2024-12-06 23:26:11 +01:00
2024-12-13 11:38:35 +01:00
const value = readValue(ctx, offset, i);
if (Array.isArray(value)) {
const nodes = new Array(value.length);
for (let i = 0; i < value.length; i++) {
const id = value[i];
if (id === 0) {
nodes[i] = undefined;
continue;
}
const nodeOffset = ctx.idTable[id];
nodes[i] = new Node(ctx, nodeOffset);
}
return nodes;
}
2024-12-05 01:18:08 +01:00
2024-12-13 11:38:35 +01:00
if (value === 0) return null;
const nodeOffset = ctx.idTable[value];
return new Node(ctx, nodeOffset);
},
});
}
2024-12-11 17:41:33 +01:00
2024-12-13 00:33:44 +01:00
/**
* @param {number} n
* @returns {Deno.AssignmentExpression["operator"]}
*/
function getAssignOperator(n) {
switch (n) {
case 0:
return "=";
case 1:
return "+=";
case 2:
return "-=";
case 3:
return "*=";
case 4:
return "/=";
case 5:
return "%=";
case 6:
return "<<=";
case 7:
return ">>=";
case 8:
return ">>>=";
case 9:
return "|=";
case 10:
return "^=";
case 11:
return "&=";
case 12:
return "**=";
case 13:
return "&&=";
case 14:
return "||=";
case 15:
return "??=";
default:
throw new Error(`Unknown operator: ${n}`);
2024-12-11 17:41:33 +01:00
}
2024-12-13 00:33:44 +01:00
}
2024-12-11 17:41:33 +01:00
2024-12-13 00:33:44 +01:00
/**
* @param {number} n
* @returns {Deno.BinaryExpression["operator"]}
*/
function getBinaryOperator(n) {
switch (n) {
case 1:
return "==";
case 2:
return "!=";
case 3:
return "===";
case 4:
return "!==";
case 5:
return "<";
case 6:
return "<=";
case 7:
return ">";
case 8:
return ">=";
case 9:
return "<<";
case 10:
return ">>";
case 11:
return ">>>";
case 12:
return "+";
case 13:
return "-";
case 14:
return "*";
case 15:
return "/";
case 16:
return "%";
case 17:
return "|";
case 18:
return "^";
case 19:
return "&";
case 20:
return "in";
case 21:
return "instanceof";
case 22:
return "**";
default:
throw new Error(`Unknown operator: ${n}`);
2024-12-11 17:41:33 +01:00
}
2024-12-13 00:33:44 +01:00
}
2024-12-11 17:41:33 +01:00
2024-12-13 00:33:44 +01:00
/**
* @param {number} n
* @returns {Deno.LogicalExpression["operator"]}
*/
function getLogicalOperator(n) {
if ((n & Flags.LogicalAnd) !== 0) {
return "&&";
} else if ((n & Flags.LogicalOr) !== 0) {
return "||";
} else if ((n & Flags.LogicalNullishCoalescin) !== 0) {
return "??";
2024-12-11 17:41:33 +01:00
}
2024-12-13 00:33:44 +01:00
throw new Error(`Unknown operator: ${n}`);
}
2024-12-11 17:41:33 +01:00
2024-12-13 00:33:44 +01:00
/**
* @param {number} n
* @returns {Deno.UnaryExpression["operator"]}
*/
function getUnaryOperator(n) {
switch (n) {
case 1:
return "-";
case 2:
return "+";
case 3:
return "!";
case 4:
return "~";
case 5:
return "typeof";
case 6:
return "void";
case 7:
return "delete";
2024-12-13 11:38:35 +01:00
default:
throw new Error(`Unknown operator: ${n}`);
2024-12-11 17:41:33 +01:00
}
}
2024-12-10 21:17:02 +01:00
2024-12-13 00:33:44 +01:00
// Literals
2024-12-10 21:17:02 +01:00
2024-12-13 00:33:44 +01:00
// /** @implements {Deno.BooleanLiteral} */
// class BooleanLiteral extends Node {
// type = /** @type {const} */ ("BooleanLiteral");
// range;
// value = false;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} flags
// */
// constructor(ctx, parentId, range, flags) {
// super(ctx, parentId);
// this.value = flags === 1;
// this.range = range;
// }
// }
// /** @implements {Deno.BigIntLiteral} */
// class BigIntLiteral extends Node {
// type = /** @type {const} */ ("BigIntLiteral");
// range;
// value;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} strId
// */
// constructor(ctx, parentId, range, strId) {
// super(ctx, parentId);
// this.range = range;
// this.value = BigInt(getString(ctx, strId));
// }
// }
// /** @implements {Deno.NullLiteral} */
// class NullLiteral extends Node {
// type = /** @type {const} */ ("NullLiteral");
// range;
// value = null;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// */
// constructor(ctx, parentId, range) {
// super(ctx, parentId);
// this.range = range;
// }
// }
// /** @implements {Deno.NumericLiteral} */
// class NumericLiteral extends Node {
// type = /** @type {const} */ ("NumericLiteral");
// range;
// value = 0;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} strId
// */
// constructor(ctx, parentId, range, strId) {
// super(ctx, parentId);
// this.range = range;
// this.value = Number(getString(ctx, strId));
// }
// }
// /** @implements {Deno.RegExpLiteral} */
// class RegExpLiteral extends Node {
// type = /** @type {const} */ ("RegExpLiteral");
// range;
// pattern = "";
// flags = "";
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} patternId
// * @param {number} flagsId
// */
// constructor(ctx, parentId, range, patternId, flagsId) {
// super(ctx, parentId);
// this.range = range;
// this.pattern = getString(ctx, patternId);
// this.flags = getString(ctx, flagsId);
// }
// }
// /** @implements {Deno.StringLiteral} */
// class StringLiteral extends Node {
// type = /** @type {const} */ ("StringLiteral");
// range;
// value = "";
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} strId
// */
// constructor(ctx, parentId, range, strId) {
// super(ctx, parentId);
// this.range = range;
// this.value = getString(ctx, strId);
// }
// }
// /** @implements {Deno.TemplateElement} */
// class TemplateElement extends Node {
// type = /** @type {const} */ ("TemplateElement");
// range;
// tail = false;
// value;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} rawId
// * @param {number} cookedId
// * @param {boolean} tail
// */
// constructor(ctx, parentId, range, rawId, cookedId, tail) {
// super(ctx, parentId);
// const raw = getString(ctx, rawId);
// this.value = {
// raw,
// cooked: cookedId === 0 ? raw : getString(ctx, cookedId),
// };
// this.tail = tail;
// this.range = range;
// }
// }
2024-12-10 21:17:02 +01:00
2024-12-13 00:33:44 +01:00
// /** @implements {Deno.JSXIdentifier} */
// class JSXIdentifier extends Node {
// type = /** @type {const} */ ("JSXIdentifier");
// range;
// name;
// /**
// * @param {AstContext} ctx
// * @param {number} parentId
// * @param {Deno.Range} range
// * @param {number} nameId
// */
// constructor(ctx, parentId, range, nameId) {
// super(ctx, parentId);
// this.range = range;
// this.name = getString(ctx, nameId);
// }
// }
const DECODER = new TextDecoder();
2024-12-05 01:18:08 +01:00
2024-12-13 00:33:44 +01:00
/**
* @typedef {{
* buf: Uint8Array,
* strTable: Map<number, string>,
* idTable: number[],
* rootId: number
* }} AstContext
*/
2024-12-05 01:18:08 +01:00
2024-12-13 00:33:44 +01:00
/**
* @param {Uint8Array} buf
* @param {number} i
* @returns {number}
*/
function readU32(buf, i) {
return (buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) +
buf[i + 3];
2024-12-05 01:18:08 +01:00
}
2024-12-13 00:33:44 +01:00
/**
* @param {AstContext} ctx
* @param {number} id
* @returns {string}
*/
function getString(ctx, id) {
const name = ctx.strTable.get(id);
if (name === undefined) {
throw new Error(`Missing string id: ${id}`);
2024-12-06 23:26:11 +01:00
}
2024-12-13 00:33:44 +01:00
return name;
}
2024-12-06 23:26:11 +01:00
2024-12-13 00:33:44 +01:00
/**
* @param {Uint8Array} buf
* @param {AstContext} buf
*/
function createAstContext(buf) {
/** @type {Map<number, string>} */
const strTable = new Map();
2024-12-10 04:33:33 +01:00
2024-12-13 00:33:44 +01:00
// console.log(JSON.stringify(buf, null, 2));
2024-12-05 01:18:08 +01:00
2024-12-13 00:33:44 +01:00
const strTableOffset = readU32(buf, buf.length - 12);
const idTableOffset = readU32(buf, buf.length - 8);
const rootId = readU32(buf, buf.length - 4);
// console.log({ strTableOffset, idTableOffset, rootId });
let offset = strTableOffset;
const stringCount = readU32(buf, offset);
offset += 4;
2024-12-05 01:18:08 +01:00
2024-12-13 00:33:44 +01:00
let id = 0;
for (let i = 0; i < stringCount; i++) {
const len = readU32(buf, offset);
offset += 4;
2024-12-06 23:26:11 +01:00
2024-12-13 00:33:44 +01:00
const strBytes = buf.slice(offset, offset + len);
offset += len;
const s = DECODER.decode(strBytes);
strTable.set(id, s);
id++;
2024-12-05 01:18:08 +01:00
}
2024-12-13 00:33:44 +01:00
// console.log({ stringCount, strTable });
2024-12-05 11:21:34 +01:00
if (strTable.size !== stringCount) {
throw new Error(
`Could not deserialize string table. Expected ${stringCount} items, but got ${strTable.size}`,
);
}
2024-12-10 04:33:33 +01:00
// Build id table
2024-12-12 20:53:08 +01:00
const idCount = readU32(buf, idTableOffset);
2024-12-10 04:33:33 +01:00
offset += 4;
const idTable = new Array(idCount);
for (let i = 0; i < idCount; i++) {
const id = readU32(buf, offset);
idTable[i] = id;
offset += 4;
}
if (idTable.length !== idCount) {
throw new Error(
`Could not deserialize id table. Expected ${idCount} items, but got ${idTable.length}`,
);
}
2024-12-07 00:24:36 +01:00
/** @type {AstContext} */
2024-12-12 20:53:08 +01:00
const ctx = { buf, idTable, strTable, rootId };
// console.log({ strTable, idTable });
2024-12-05 01:18:08 +01:00
2024-12-07 00:24:36 +01:00
return ctx;
2024-12-04 16:21:17 +01:00
}
2024-12-07 00:24:36 +01:00
/**
* @param {string} fileName
* @param {Uint8Array} serializedAst
*/
2024-12-05 11:21:34 +01:00
export function runPluginsForFile(fileName, serializedAst) {
2024-12-07 00:24:36 +01:00
const ctx = createAstContext(serializedAst);
2024-12-10 21:17:02 +01:00
// console.log(JSON.stringify(ctx, null, 2));
2024-12-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-13 11:38:35 +01:00
// console.log("buffer len", ctx.buf.length, ctx.buf.byteLength);
2024-12-10 22:48:54 +01:00
console.log("merged visitor", visitor);
console.log("visiting types", visitTypes);
2024-12-10 04:33:33 +01:00
2024-12-12 20:53:08 +01:00
traverseInner(ctx, visitTypes, visitor, ctx.rootId);
2024-12-10 04:33:33 +01:00
}
2024-12-13 00:33:44 +01:00
const SKIP_CHILD_TRAVERSAL = new Set([
AstType.BooleanLiteral,
AstType.BigIntLiteral,
AstType.DebuggerStatement,
AstType.Identifier,
AstType.JSXClosingFragment,
AstType.JSXEmptyExpression,
AstType.JSXOpeningFragment,
AstType.JSXText,
AstType.NullLiteral,
AstType.NumericLiteral,
AstType.PrivateIdentifier,
AstType.RegExpLiteral,
AstType.StringLiteral,
AstType.TemplateLiteral,
AstType.This,
]);
2024-12-10 04:33:33 +01:00
/**
* @param {AstContext} ctx
* @param {Map<number, string>} visitTypes
* @param {Record<string, (x: any) => void>} visitor
* @param {number} id
*/
function traverseInner(ctx, visitTypes, visitor, id) {
2024-12-12 01:35:57 +01:00
// console.log("traversing id", id);
2024-12-10 04:33:33 +01:00
// Empty id
if (id === 0) return;
2024-12-10 05:34:12 +01:00
const { idTable, buf } = ctx;
2024-12-10 04:33:33 +01:00
if (id >= idTable.length) {
2024-12-10 21:17:02 +01:00
throw new Error(`Invalid node id: ${id}`);
2024-12-10 04:33:33 +01:00
}
let offset = idTable[id];
if (offset === undefined) throw new Error(`Unknown id: ${id}`);
const type = buf[offset];
2024-12-13 00:33:44 +01:00
2024-12-12 01:35:57 +01:00
// console.log({ id, type, offset });
2024-12-10 04:33:33 +01:00
const name = visitTypes.get(type);
if (name !== undefined) {
2024-12-13 00:33:44 +01:00
// console.log("--> invoking visitor");
const node = new Node(ctx, offset);
2024-12-10 04:33:33 +01:00
visitor[name](node);
}
2024-12-13 00:33:44 +01:00
if (SKIP_CHILD_TRAVERSAL.has(type)) return;
2024-12-10 04:33:33 +01:00
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
2024-12-07 00:24:36 +01:00
2024-12-13 00:33:44 +01:00
const propCount = buf[offset];
offset += 1;
// console.log({ propCount });
2024-12-11 17:41:33 +01:00
2024-12-13 00:33:44 +01:00
for (let i = 0; i < propCount; i++) {
const searchProp = buf[offset];
offset += 1;
2024-12-11 17:41:33 +01:00
2024-12-13 00:33:44 +01:00
if (searchProp === AST_PROP_INTERNAL_FLAGS) {
2024-12-10 21:17:02 +01:00
offset += 1;
2024-12-13 00:33:44 +01:00
continue;
2024-12-10 04:33:33 +01:00
}
2024-12-13 00:33:44 +01:00
if (isArrayProp(type, searchProp)) {
const len = readU32(buf, offset);
2024-12-10 04:33:33 +01:00
offset += 4;
2024-12-12 01:35:57 +01:00
2024-12-13 00:33:44 +01:00
for (let j = 0; j < len; j++) {
const childId = readU32(buf, offset);
offset += 4;
traverseInner(ctx, visitTypes, visitor, childId);
}
continue;
2024-12-10 04:33:33 +01:00
}
2024-12-13 00:33:44 +01:00
const childId = readU32(buf, offset);
traverseInner(ctx, visitTypes, visitor, childId);
offset += 4;
2024-12-01 05:14:46 +01:00
}
2024-12-01 04:53:47 +01:00
}