0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-10 14:17:49 -04:00
deno/cli/js/40_lint.js

957 lines
19 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-05 01:18:08 +01:00
// Keep in sync with Rust
2024-12-06 23:26:11 +01:00
/**
* @enum {number}
*/
2024-12-15 00:40:55 +01:00
const AstTypeName = [
"Invalid",
"Program",
"Import",
"ImportDecl",
"ExportDecl",
"ExportNamed",
"ExportDefaultDecl",
"ExportDefaultExpr",
"ExportAll",
"TSImportEquals",
"TSExportAssignment",
"TSNamespaceExport",
2024-12-05 01:18:08 +01:00
// Decls
2024-12-15 00:40:55 +01:00
"ClassDeclaration",
"FunctionDeclaration",
"VariableDeclaration",
"Using",
"TsInterface",
"TsTypeAlias",
"TsEnum",
"TsModule",
2024-12-05 01:18:08 +01:00
// Statements
2024-12-15 00:40:55 +01:00
"BlockStatement",
"Empty",
"DebuggerStatement",
"WithStatement",
"ReturnStatement",
"LabeledStatement",
"BreakStatement",
"ContinueStatement",
"IfStatement",
"SwitchStatement",
"SwitchCase",
"ThrowStatement",
"TryStatement",
"WhileStatement",
"DoWhileStatement",
"ForStatement",
"ForInStatement",
"ForOfStatement",
"Decl",
"ExpressionStatement",
2024-12-05 01:18:08 +01:00
// Expressions
2024-12-15 00:40:55 +01:00
"This",
"ArrayExpression",
"ObjectExpression",
"FunctionExpression",
"UnaryExpression",
"UpdateExpression",
"BinaryExpression",
"AssignmentExpression",
"MemberExpression",
"Super",
"ConditionalExpression",
"CallExpression",
"NewExpression",
"ParenthesisExpression",
"SequenceExpression",
"Identifier",
"TemplateLiteral",
"TaggedTemplateExpression",
"ArrowFunctionExpression",
"ClassExpr",
"YieldExpression",
"MetaProperty",
"AwaitExpression",
"LogicalExpression",
"TSTypeAssertion",
"TSConstAssertion",
"TSNonNull",
"TSAs",
"TSInstantiation",
"TSSatisfies",
"PrivateIdentifier",
"ChainExpression",
"StringLiteral",
"BooleanLiteral",
"NullLiteral",
"NumericLiteral",
"BigIntLiteral",
"RegExpLiteral",
2024-12-05 01:18:08 +01:00
// Custom
2024-12-15 00:40:55 +01:00
"EmptyExpr",
"SpreadElement",
"Property",
"VariableDeclarator",
"CatchClause",
"RestElement",
"ExportSpecifier",
"TemplateElement",
"MethodDefinition",
2024-12-10 21:17:02 +01:00
// Patterns
2024-12-15 00:40:55 +01:00
"ArrayPattern",
"AssignmentPattern",
"ObjectPattern",
2024-12-07 00:24:36 +01:00
// JSX
2024-12-15 00:40:55 +01:00
"JSXAttribute",
"JSXClosingElement",
"JSXClosingFragment",
"JSXElement",
"JSXEmptyExpression",
"JSXExpressionContainer",
"JSXFragment",
"JSXIdentifier",
"JSXMemberExpression",
"JSXNamespacedName",
"JSXOpeningElement",
"JSXOpeningFragment",
"JSXSpreadAttribute",
"JSXSpreadChild",
"JSXText",
];
2024-12-04 16:21:17 +01:00
2024-12-15 00:40:55 +01:00
/** @type {Map<string, number>} */
const AstType = new Map();
for (let i = 0; i < AstTypeName.length; i++) {
AstType.set(AstTypeName[i], i);
}
2024-12-12 22:16:49 +01:00
// Keep in sync with Rust
2024-12-15 00:40:55 +01:00
const AstPropArr = [
2024-12-12 22:16:49 +01:00
// Base
2024-12-15 00:53:37 +01:00
"parent",
2024-12-12 22:16:49 +01:00
"range",
"type",
2024-12-15 00:40:55 +01:00
"_InternalFlags",
2024-12-12 22:16:49 +01:00
// Node
2024-12-15 00:40:55 +01:00
"abstract",
"accessibility",
2024-12-12 22:16:49 +01:00
"alternate",
"argument",
"arguments",
"async",
"attributes",
"await",
"block",
"body",
"callee",
"cases",
"children",
2024-12-15 00:40:55 +01:00
"checkType",
2024-12-12 22:16:49 +01:00
"closingElement",
"closingFragment",
"computed",
"consequent",
2024-12-15 00:40:55 +01:00
"const",
"constraint",
2024-12-13 00:33:44 +01:00
"cooked",
2024-12-12 22:16:49 +01:00
"declarations",
"declare",
2024-12-15 00:40:55 +01:00
"default",
2024-12-12 22:16:49 +01:00
"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-15 00:40:55 +01:00
"exprName",
2024-12-12 22:16:49 +01:00
"expression",
"expressions",
2024-12-13 00:33:44 +01:00
"exported",
2024-12-15 00:40:55 +01:00
"extendsType",
"falseType",
2024-12-12 22:16:49 +01:00
"finalizer",
"flags",
"generator",
"handler",
"id",
2024-12-15 00:40:55 +01:00
"in",
2024-12-12 22:16:49 +01:00
"init",
2024-12-15 00:40:55 +01:00
"initializer",
"implements",
2024-12-12 22:16:49 +01:00
"key",
"kind",
"label",
"left",
2024-12-15 00:40:55 +01:00
"literal",
2024-12-13 00:33:44 +01:00
"local",
2024-12-12 22:16:49 +01:00
"members",
"meta",
"method",
"name",
"namespace",
2024-12-15 00:40:55 +01:00
"nameType",
2024-12-12 22:16:49 +01:00
"object",
"openingElement",
"openingFragment",
"operator",
"optional",
2024-12-15 00:40:55 +01:00
"out",
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",
2024-12-15 00:40:55 +01:00
"readonly",
2024-12-12 22:16:49 +01:00
"returnType",
"right",
"selfClosing",
"shorthand",
"source",
2024-12-15 00:40:55 +01:00
"sourceType",
2024-12-12 22:16:49 +01:00
"specifiers",
2024-12-15 00:40:55 +01:00
"superClass",
"superTypeArguments",
2024-12-12 22:16:49 +01:00
"tag",
"tail",
"test",
2024-12-15 00:40:55 +01:00
"trueType",
2024-12-12 22:16:49 +01:00
"typeAnnotation",
"typeArguments",
2024-12-15 00:40:55 +01:00
"typeName",
"typeParameter",
2024-12-12 22:16:49 +01:00
"typeParameters",
2024-12-13 00:33:44 +01:00
"types",
2024-12-12 22:16:49 +01:00
"update",
"value",
];
2024-12-15 00:40:55 +01:00
/** @type {Map<string, number>} */
const AstProp = new Map();
for (let i = 0; i < AstPropArr.length; i++) {
AstProp.set(AstPropArr[i], i);
}
const AST_PROP_TYPE = /** @type {number} */ (AstProp.get("type"));
const AST_PROP_PARENT = /** @type {number} */ (AstProp.get("parent"));
const AST_PROP_RANGE = /** @type {number} */ (AstProp.get("range"));
const AstPropName = Array.from(AstProp.keys());
// Keep in sync with Rust
/** @enum {number} */
const PropFlags = {
Ref: 0,
RefArr: 1,
String: 2,
Bool: 3,
AssignOp: 4,
BinOp: 5,
LogicalOp: 6,
UnaryOp: 7,
VarKind: 8,
TruePlusMinus: 9,
Accessibility: 10,
UpdateOp: 11,
};
// Keep in sync with Rust
const ASSIGN_OP = [
"=",
"+=",
"-=",
"*=",
"/=",
"%=",
"<<=",
">>=",
">>>=",
"|=",
"^=",
"&=",
"**=",
"&&=",
"||=",
"??=",
];
const BIN_OP = [
"==",
"!=",
"===",
"!==",
"<",
"<=",
">",
">=",
"<<",
">>",
">>>",
"+",
"-",
"*",
"/",
"%",
"|",
"^",
"&",
"in",
"instanceof",
"**",
];
// Keep in sync with rust
const LOGICAL_OP = [
"&&",
"||",
"??",
];
// Keep in sync with rust
const UNARY_OP = [
"-",
"+",
"!",
"~",
"typeof",
"void",
"delete",
];
// Keep in sync with rust
const VAR_KIND = [
"var",
"let",
"const",
];
// Keep in sync with rust
const ACCESSIBILITY = [
undefined,
"public",
"protected",
"private",
];
// Keep in sync with rust
const UPDATE_OP = [
"++",
"--",
];
2024-12-13 00:33:44 +01:00
/**
2024-12-15 00:40:55 +01:00
* @typedef {{
* buf: Uint8Array,
* strTable: Map<number, string>,
* strTableOffset: number,
* rootId: number,
* nodes: Map<number, Node>
* }} AstContext
2024-12-13 00:33:44 +01:00
*/
2024-12-15 00:40:55 +01:00
/**
* @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,
);
2024-12-13 00:33:44 +01:00
}
}
2024-12-10 04:33:33 +01:00
2024-12-15 00:40:55 +01:00
/**
* @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);
}
2024-12-10 04:33:33 +01:00
/**
* @param {AstContext} ctx
2024-12-12 22:16:49 +01:00
* @param {number} offset
2024-12-15 00:40:55 +01:00
* @returns
2024-12-10 04:33:33 +01:00
*/
2024-12-15 00:40:55 +01:00
function getNode(ctx, offset) {
if (offset === 0) return null;
2024-12-10 04:33:33 +01:00
2024-12-15 00:40:55 +01:00
const cached = ctx.nodes.get(offset);
if (cached !== undefined) return cached;
2024-12-13 00:33:44 +01:00
2024-12-15 00:40:55 +01:00
const node = new Node(ctx, offset);
ctx.nodes.set(offset, /** @type {*} */ (cached));
return node;
}
2024-12-13 00:33:44 +01:00
2024-12-15 00:40:55 +01:00
/**
* @param {Uint8Array} buf
* @param {number} search
* @param {number} offset
* @returns {number}
*/
function findPropOffset(buf, offset, search) {
2024-12-13 00:33:44 +01:00
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
const propCount = buf[offset];
offset += 1;
for (let i = 0; i < propCount; i++) {
2024-12-15 00:40:55 +01:00
const maybe = offset;
const prop = buf[offset++];
const kind = buf[offset++];
if (prop === search) return maybe;
2024-12-12 22:16:49 +01:00
2024-12-15 00:40:55 +01:00
if (kind === PropFlags.Ref) {
offset += 4;
} else if (kind === PropFlags.RefArr) {
2024-12-13 00:33:44 +01:00
const len = readU32(buf, offset);
offset += 4 + (len * 4);
2024-12-15 00:40:55 +01:00
} else if (kind === PropFlags.String) {
2024-12-13 00:33:44 +01:00
offset += 4;
2024-12-15 00:40:55 +01:00
} else if (kind === PropFlags.Bool) {
offset++;
} else {
offset++;
2024-12-13 00:33:44 +01:00
}
2024-12-12 22:16:49 +01:00
}
2024-12-13 00:33:44 +01:00
2024-12-15 00:40:55 +01:00
return -1;
2024-12-10 04:33:33 +01:00
}
2024-12-13 11:38:35 +01:00
/**
* @param {AstContext} ctx
* @param {number} offset
2024-12-15 00:40:55 +01:00
* @param {number} search
* @returns {*}
*/
function readValue(ctx, offset, search) {
const { buf } = ctx;
const type = buf[offset];
if (search === AST_PROP_TYPE) {
return AstTypeName[type];
} else if (search === AST_PROP_RANGE) {
const start = readU32(buf, offset + 1 + 4);
const end = readU32(buf, offset + 1 + 4 + 4);
return [start, end];
} else if (search === AST_PROP_PARENT) {
const pos = readU32(buf, offset + 1);
return getNode(ctx, pos);
}
offset = findPropOffset(ctx.buf, offset, search);
if (offset === -1) return undefined;
const kind = buf[offset + 1];
if (kind === PropFlags.Ref) {
const value = readU32(buf, offset + 2);
return getNode(ctx, value);
} else if (kind === PropFlags.RefArr) {
const len = readU32(buf, offset);
offset += 4;
const nodes = new Array(len);
for (let i = 0; i < len; i++) {
nodes[i] = getNode(ctx, readU32(buf, offset));
offset += 4;
}
return nodes;
} else if (kind === PropFlags.Bool) {
return buf[offset] === 1;
} else if (kind === PropFlags.String) {
const v = readU32(buf, offset);
return getString(ctx, v);
}
return getFlagValue(kind, buf[offset]);
}
/**
* @param {number} kind
* @param {number} value
* @returns
2024-12-13 11:38:35 +01:00
*/
2024-12-15 00:40:55 +01:00
function getFlagValue(kind, value) {
switch (kind) {
case PropFlags.AssignOp:
return ASSIGN_OP[value];
case PropFlags.BinOp:
return BIN_OP[value];
case PropFlags.LogicalOp:
return LOGICAL_OP[value];
case PropFlags.TruePlusMinus:
throw new Error("TODO: TruePlusMinus");
case PropFlags.UnaryOp:
return UNARY_OP[value];
case PropFlags.VarKind:
return VAR_KIND[value];
case PropFlags.Accessibility:
return ACCESSIBILITY[value];
case PropFlags.UpdateOp:
return UPDATE_OP[value];
default:
throw new Error(`Unknown prop kind: ${kind}`);
}
2024-12-13 11:38:35 +01:00
}
/**
* @param {AstContext} ctx
* @param {number} offset
* @returns {*}
*/
2024-12-15 00:40:55 +01:00
function toJsValue(ctx, offset) {
const { buf } = ctx;
console.log("to js", offset);
2024-12-13 11:38:35 +01:00
/** @type {Record<string, any>} */
const node = {
2024-12-15 00:40:55 +01:00
type: readValue(ctx, offset, AST_PROP_TYPE),
range: readValue(ctx, offset, AST_PROP_RANGE),
2024-12-13 11:38:35 +01:00
};
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
2024-12-15 00:53:37 +01:00
const count = buf[offset++];
2024-12-15 00:40:55 +01:00
for (let i = 0; i < count; i++) {
const prop = buf[offset++];
const kind = buf[offset++];
const name = AstPropName[prop];
console.log({ prop, kind, name });
2024-12-13 11:38:35 +01:00
2024-12-15 00:40:55 +01:00
if (kind === PropFlags.Ref) {
const v = readU32(buf, offset);
2024-12-13 11:38:35 +01:00
offset += 4;
2024-12-15 00:40:55 +01:00
node[name] = toJsValue(ctx, v);
} else if (kind === PropFlags.RefArr) {
2024-12-13 11:38:35 +01:00
const len = readU32(buf, offset);
offset += 4;
2024-12-15 00:40:55 +01:00
const nodes = new Array(len);
for (let i = 0; i < len; i++) {
const v = readU32(buf, offset);
if (v === 0) continue;
nodes[i] = toJsValue(ctx, v);
2024-12-13 11:38:35 +01:00
offset += 4;
}
2024-12-15 00:40:55 +01:00
node[name] = nodes;
} else if (kind === PropFlags.Bool) {
const v = buf[offset++];
node[name] = v === 1;
} else if (kind === PropFlags.String) {
const v = readU32(buf, offset);
2024-12-13 11:38:35 +01:00
offset += 4;
2024-12-15 00:40:55 +01:00
node[name] = getString(ctx, v);
} else {
node[name] = getFlagValue(kind, buf[offset++]);
2024-12-13 11:38:35 +01:00
}
}
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) {
2024-12-15 00:40:55 +01:00
const json = toJsValue(this[INTERNAL_CTX], this[INTERNAL_OFFSET]);
2024-12-13 11:38:35 +01:00
return Deno.inspect(json, options);
}
2024-12-10 04:33:33 +01:00
}
2024-12-05 01:18:08 +01:00
2024-12-15 00:40:55 +01:00
for (let i = 0; i < AstPropName.length; i++) {
const name = AstPropName[i];
2024-12-13 00:33:44 +01:00
Object.defineProperty(Node.prototype, name, {
get() {
2024-12-15 00:40:55 +01:00
return readValue(this[INTERNAL_CTX], this[INTERNAL_OFFSET], i);
2024-12-13 11:38:35 +01:00
},
});
}
2024-12-11 17:41:33 +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
/**
* @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-15 00:40:55 +01:00
console.log(JSON.stringify(buf, null, 2));
2024-12-05 01:18:08 +01:00
2024-12-15 00:40:55 +01:00
const strTableOffset = readU32(buf, buf.length - 8);
2024-12-13 00:33:44 +01:00
const rootId = readU32(buf, buf.length - 4);
2024-12-15 00:40:55 +01:00
// console.log({ strTableOffset, rootId });
2024-12-13 00:33:44 +01:00
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-15 00:40:55 +01:00
console.log({ stringCount, strTable, rootId });
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-07 00:24:36 +01:00
/** @type {AstContext} */
2024-12-15 00:40:55 +01:00
const ctx = { buf, strTable, rootId, nodes: new Map(), strTableOffset };
2024-12-12 20:53:08 +01:00
2024-12-15 00:40:55 +01:00
dump(ctx);
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 {
2024-12-15 00:40:55 +01:00
ctx.nodes.clear();
2024-12-07 00:24:36 +01:00
// 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) {
2024-12-15 00:40:55 +01:00
const id = AstType.get(name);
2024-12-10 04:33:33 +01:00
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
}
/**
* @param {AstContext} ctx
* @param {Map<number, string>} visitTypes
* @param {Record<string, (x: any) => void>} visitor
2024-12-15 00:40:55 +01:00
* @param {number} offset
2024-12-10 04:33:33 +01:00
*/
2024-12-15 00:40:55 +01:00
function traverseInner(ctx, visitTypes, visitor, offset) {
// console.log("traversing offset", offset);
2024-12-10 04:33:33 +01:00
// Empty id
2024-12-15 00:40:55 +01:00
if (offset === 0) return;
const { buf } = ctx;
2024-12-10 04:33:33 +01:00
const type = buf[offset];
2024-12-13 00:33:44 +01:00
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);
}
// 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++) {
2024-12-15 00:40:55 +01:00
const kind = buf[offset + 1];
offset += 2; // propId + propFlags
2024-12-10 04:33:33 +01:00
2024-12-15 00:40:55 +01:00
if (kind === PropFlags.Ref) {
const next = readU32(buf, offset);
offset += 4;
traverseInner(ctx, visitTypes, visitor, next);
} else if (kind === PropFlags.RefArr) {
2024-12-13 00:33:44 +01:00
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++) {
2024-12-15 00:40:55 +01:00
const chiild = readU32(buf, offset);
2024-12-13 00:33:44 +01:00
offset += 4;
2024-12-15 00:40:55 +01:00
traverseInner(ctx, visitTypes, visitor, chiild);
2024-12-13 00:33:44 +01:00
}
2024-12-15 00:40:55 +01:00
} else if (kind === PropFlags.String) {
offset += 4;
} else {
// Everything else is a flag or a bool
offset += 1;
2024-12-10 04:33:33 +01:00
}
2024-12-15 00:40:55 +01:00
}
}
/**
* @param {AstContext} ctx
*/
function dump(ctx) {
const { buf, strTableOffset } = ctx;
let offset = 0;
while (offset < strTableOffset) {
const type = buf[offset];
const name = AstTypeName[type];
2024-12-15 00:53:37 +01:00
console.log(`${name}, offset: ${offset}, type: ${type}`);
2024-12-15 00:40:55 +01:00
offset += 1;
const parent = readU32(buf, offset);
offset += 4;
console.log(` parent: ${parent}`);
2024-12-10 04:33:33 +01:00
2024-12-15 00:40:55 +01:00
const start = readU32(buf, offset);
offset += 4;
const end = readU32(buf, offset);
2024-12-13 00:33:44 +01:00
offset += 4;
2024-12-15 00:53:37 +01:00
console.log(` range: ${start} -> ${end}}`);
2024-12-15 00:40:55 +01:00
2024-12-15 00:53:37 +01:00
const count = buf[offset++];
2024-12-15 00:40:55 +01:00
console.log(` prop count: ${count}`);
for (let i = 0; i < count; i++) {
const prop = buf[offset++];
const kind = buf[offset++];
const name = AstPropName[prop];
let kindName = "unknown";
for (const k in PropFlags) {
if (kind === PropFlags[k]) {
kindName = k;
}
}
if (kind === PropFlags.Ref) {
const v = readU32(buf, offset);
offset += 4;
console.log(` ${name}: ${v} (${kindName})`);
} else if (kind === PropFlags.RefArr) {
const len = readU32(buf, offset);
offset += 4;
console.log(` ${name}: Array(${len}) (${kindName})`);
for (let j = 0; j < len; j++) {
const v = readU32(buf, offset);
offset += 4;
console.log(` - ${v}`);
}
} else if (kind === PropFlags.Bool) {
const v = buf[offset++];
console.log(` ${name}: ${v} (${kindName})`);
} else if (kind === PropFlags.String) {
const v = readU32(buf, offset);
offset += 4;
console.log(` ${name}: ${getString(ctx, v)} (${kindName})`);
} else {
const v = buf[offset++];
console.log(` ${name}: ${getFlagValue(kind, v)} (${kindName})`);
}
}
2024-12-01 05:14:46 +01:00
}
2024-12-01 04:53:47 +01:00
}