shared/analyzers/generator.analyzer.ts
shared/analyzers/generator.analyzer.ts is a file in GovLab Extension Host. 81 lines of code and 21 definitions.
import { GENERATED_MARKER, MARKER_EXEMPT_BASENAMES, PRETTIER_EXTENSIONS } from "../manifests/generator.manifest.ts";
import type { InitResolver, WriteNode } from "../../types/generator.types.ts";
import { calledName, identName } from "./sink.analyzer.ts";
const EPHEMERAL_ROOT_FNS = new Set(["tmpdir", "mkdtemp", "mkdtempSync"]);
const CLIMB_LIMIT = 16;
const literalChildren = function literalChildren(node: WriteNode): (WriteNode | undefined)[] {
return [
node.callee,
node.object,
node.property,
node.left,
node.right,
node.argument,
...(node.arguments ?? []),
...(node.elements ?? []),
...(node.expressions ?? []),
];
};
const ownLiterals = function ownLiterals(node: WriteNode): string[] {
const literal = node.type === "Literal" && typeof node.value === "string" ? [node.value] : [];
const quasis = (node.quasis ?? []).flatMap((quasi) =>
typeof quasi.value?.cooked === "string" ? [quasi.value.cooked] : [],
);
return [...literal, ...quasis];
};
export const collectLiterals = function collectLiterals(node: WriteNode | undefined, depth: number): string[] {
if (!node || depth > CLIMB_LIMIT) {
return [];
}
return [...ownLiterals(node), ...literalChildren(node).flatMap((child) => collectLiterals(child, depth + 1))];
};
export const rootsAtEphemeralDir = function rootsAtEphemeralDir(
resolve: InitResolver,
node: WriteNode | undefined,
depth: number,
): boolean {
if (!node || depth > CLIMB_LIMIT) {
return false;
}
if (node.type === "CallExpression" && EPHEMERAL_ROOT_FNS.has(calledName(node) ?? "")) {
return true;
}
if (node.type === "Identifier") {
const init = resolve(identName(node) ?? "");
return init !== null && rootsAtEphemeralDir(resolve, init, depth + 1);
}
return literalChildren(node).some((child) => rootsAtEphemeralDir(resolve, child, depth + 1));
};
const extensionOf = function extensionOf(text: string): string | null {
const dot = text.lastIndexOf(".");
return dot === -1 ? null : text.slice(dot + 1).toLowerCase();
};
const prettierExtLiterals = function prettierExtLiterals(strings: string[]): string[] {
return strings.filter((text) => {
const ext = extensionOf(text);
return ext !== null && PRETTIER_EXTENSIONS.has(ext);
});
};
const targetLiterals = function targetLiterals(resolve: InitResolver, target: WriteNode): string[] {
const own = collectLiterals(target, 0);
const name = identName(target);
const init = name === null ? null : resolve(name);
return init === null ? own : [...own, ...collectLiterals(init, 0)];
};
const basenameOf = function basenameOf(text: string): string {
const slash = Math.max(text.lastIndexOf("/"), text.lastIndexOf("\\"));
return slash === -1 ? text : text.slice(slash + 1);
};
export const targetIsUnmarkedGenerated = function targetIsUnmarkedGenerated(
resolve: InitResolver,
target: WriteNode,
): boolean {
const named = prettierExtLiterals(targetLiterals(resolve, target));
if (named.length === 0) {
return false;
}
return named.every((text) => {
const base = basenameOf(text).toLowerCase();
return !base.includes(GENERATED_MARKER) && !MARKER_EXEMPT_BASENAMES.has(base);
});
};