core/analyzers/barrel.analyzer.ts
core/analyzers/barrel.analyzer.ts is a file in Project Scripts. 71 lines of code and 19 definitions.
import { GLOB_CALLEE, RELATIVE_MARK } from "#configuration/constants/closure.constants";
import type { PatternResolution, SideEffectEntry } from "#types/closure.types";
import { dirname, relative } from "node:path";
import { unmatchedBarrels, unmatchedPattern } from "#configuration/strings/closure.strings";
import { expandPattern } from "#core/resolvers/pattern.resolver";
import { normalizePath } from "@ssot/govlab/shared/resolvers/anchor.resolver.ts";
import { readFileSync } from "node:fs";
import ts from "typescript";
const SEPARATOR = "/";
const HERE = "./";
const patternOf = function patternOf(node: ts.Node, sourceFile: ts.SourceFile): string | null {
if (!ts.isCallExpression(node) || node.expression.getText(sourceFile) !== GLOB_CALLEE) {
return null;
}
const argument = node.arguments.at(0);
return argument !== undefined && ts.isStringLiteral(argument) ? argument.text : null;
};
const globArgumentsOf = function globArgumentsOf(node: ts.Node, sourceFile: ts.SourceFile): string[] {
const found: string[] = [];
const visit = function visit(child: ts.Node): void {
const own = patternOf(child, sourceFile);
if (own !== null) {
found.push(own);
}
ts.forEachChild(child, visit);
};
visit(node);
return found;
};
const resolvePattern = function resolvePattern(
pattern: string,
barrelDir: string,
barrelRel: string,
): PatternResolution {
if (!pattern.startsWith(RELATIVE_MARK)) {
return { edges: [], unmatched: [] };
}
const segments = pattern.split(SEPARATOR).filter((segment) => segment !== RELATIVE_MARK && segment.length > 0);
const matches = expandPattern(barrelDir, segments);
if (matches.length === 0) {
return { edges: [], unmatched: [unmatchedPattern(barrelRel, pattern)] };
}
return {
edges: matches.map((match) => ({
file: barrelRel,
from: `${HERE}${normalizePath(relative(barrelDir, match))}`,
})),
unmatched: [],
};
};
const resolveFile = function resolveFile(filePath: string, root: string): PatternResolution {
const text = readFileSync(filePath, "utf8");
if (!text.includes(GLOB_CALLEE)) {
return { edges: [], unmatched: [] };
}
const sourceFile = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true);
const barrelDir = dirname(filePath);
const barrelRel = normalizePath(relative(root, filePath));
const resolved = globArgumentsOf(sourceFile, sourceFile).map((pattern) =>
resolvePattern(pattern, barrelDir, barrelRel),
);
return { edges: resolved.flatMap((entry) => entry.edges), unmatched: resolved.flatMap((entry) => entry.unmatched) };
};
export const collectGlobEdges = function collectGlobEdges(root: string, files: readonly string[]): SideEffectEntry[] {
const resolved = files.map((filePath) => resolveFile(filePath, root));
const unmatched = resolved.flatMap((entry) => entry.unmatched);
if (unmatched.length > 0) {
throw new Error(unmatchedBarrels(unmatched));
}
return resolved.flatMap((entry) => entry.edges);
};