Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

scripts/lib/esast.ts

Raw
import { parseSync } from "oxc-parser";

// Shared ESTree helpers for the repository's structural checks. The native
// TypeScript (Go) toolchain ships no programmatic compiler API, so scripts parse
// with oxc-parser and walk the ESTree result directly.

/** Minimal ESTree node shape used by the structural checks. */
export type EsNode = { type: string; [key: string]: unknown };

/** Parse a source file into its ESTree program; language is inferred from the path. */
export function parseModule(file: string, text: string): EsNode {
	return parseSync(file, text).program as unknown as EsNode;
}

/**
 * The string value of a string `Literal` or a no-substitution template literal,
 * else undefined. Minified output rewrites `import("x")` to `` import(`x`) ``, so
 * import-specifier checks must accept both.
 */
export function literalString(
	node: EsNode | null | undefined,
): string | undefined {
	if (!node) return undefined;
	if (node.type === "Literal" && typeof node["value"] === "string")
		return node["value"];
	if (
		node.type === "TemplateLiteral" &&
		Array.isArray(node["expressions"]) &&
		node["expressions"].length === 0
	) {
		const quasi = (node["quasis"] as EsNode[] | undefined)?.[0];
		const cooked = (quasi?.["value"] as { cooked?: unknown } | undefined)
			?.cooked;
		return typeof cooked === "string" ? cooked : undefined;
	}
	return undefined;
}

/** Depth-first visit of every typed node in an ESTree tree. */
export function walkEs(node: unknown, visit: (node: EsNode) => void): void {
	if (!node || typeof node !== "object") return;
	if (Array.isArray(node)) {
		for (const child of node) walkEs(child, visit);
		return;
	}
	const record = node as Record<string, unknown>;
	if (typeof record["type"] === "string") visit(record as EsNode);
	for (const key of Object.keys(record))
		if (key !== "type") walkEs(record[key], visit);
}