0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-04 18:12:59 -05:00
deno/cli/js/40_lint.js
Marvin Hagemeister c734214c41 WIP
2024-12-06 21:31:28 +01:00

210 lines
4.7 KiB
JavaScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { core } from "ext:core/mod.js";
const {
op_lint_get_rule,
op_lint_get_source,
op_lint_report,
} = core.ops;
/** @typedef {{ plugins: Array<{ name: string, rules: Record<string, Deno.LintRule}> }} LintState */
/** @type {LintState} */
const state = {
plugins: [],
};
export class Context {
id;
fileName;
#source = null;
constructor(id, fileName) {
this.id = id;
this.fileName = fileName;
}
source() {
if (this.#source === null) {
this.#source = op_lint_get_source();
}
return this.#source;
}
report(data) {
let start, end;
if (data.node) {
start = data.node.span.start - 1;
end = data.node.span.end - 1;
} else if (data.span) {
start = data.span.start - 1;
end = data.span.end - 1;
} else {
throw new Error(
"Either `node` or `span` must be provided when reporting an error",
);
}
op_lint_report(
this.id,
this.fileName,
data.message,
start,
end,
);
}
}
export function installPlugin(plugin) {
console.log("plugin", 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 (typeof state.plugins[plugin.name] !== "undefined") {
throw new Error(`Linter plugin ${plugin.name} has already been registered`);
}
state.plugins[plugin.name] = plugin.rules;
console.log("Installed plugin", plugin.name, plugin.rules);
}
const PROGRAM = 1;
const Program = 1;
const ast = {
Program() {
return {
type: "Program",
moduleOrScript: null,
};
},
};
/**
* @param {Uint8Array} ast
*/
function buildAstFromBinary(ast) {
console.log(ast);
const stack = [];
for (let i = 0; i < ast.length; i += 5) {
const kind = ast[i];
if (kind === 0) {
throw new Error("FAIL");
}
}
}
export function runPluginsForFile(fileName, serializedAst, binary) {
const binaryAst = buildAstFromBinary(binary);
const ast = JSON.parse(serializedAst, (key, value) => {
if (key === "ctxt") {
return undefined;
}
return value;
});
/** @type {Record<string, (node: any) => void} */
const mergedVisitor = {};
const destroyFns = [];
// Instantiate and merge visitors. This allows us to only traverse
// the AST once instead of per plugin.
for (let i = 0; i < state.plugins; i++) {
const plugin = state.plugins[i];
for (const name of Object.keys(plugin)) {
const rule = plugin.rules[name];
const id = `${plugin.name}/${ruleName}`;
const ctx = new Context(id, fileName);
const visitor = rule.create(ctx);
for (const name in visitor) {
const prev = mergedVisitor[name];
mergedVisitor[name] = (node) => {
if (typeof prev === "function") {
prev(node);
}
try {
visitor[name](node);
} catch (err) {
throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
cause: err,
});
}
};
}
mergedVisitor.push({ ctx, visitor, rule });
if (typeof rule.destroy === "function") {
destroyFns.push(() => {
try {
rule.destroy(ctx);
} catch (err) {
throw new Error(`Destroy hook of "${id}" errored`, { cause: err });
}
});
}
}
}
// Traverse ast with all visitors at the same time to avoid traversing
// multiple times.
traverse(ast, mergedVisitor, null);
// Optional: Destroy rules
for (let i = 0; i < destroyFns.length; i++) {
destroyFns[i]();
}
}
/**
* @param {Record<string, any>} ast
* @param {*} visitor
* @param {any | null} parent
* @returns {void}
*/
function traverse(ast, visitor, parent) {
if (!ast || typeof ast !== "object") {
return;
}
// Get node type, accounting for SWC's type property naming
const nodeType = ast.type || (ast.nodeType ? ast.nodeType : null);
// Skip if not a valid AST node
if (!nodeType) {
return;
}
ast.parent = parent;
// Call visitor if it exists for this node type
visitor[nodeType]?.(ast);
// Traverse child nodes
for (const key in ast) {
if (key === "parent" || key === "type") {
continue;
}
const child = ast[key];
if (Array.isArray(child)) {
for (let i = 0; i < child.length; i++) {
const item = child[i];
traverse(item, visitor, ast);
}
} else if (child !== null && typeof child === "object") {
traverse(child, visitor, ast);
}
}
}