mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-11-12 22:29:53 -06:00
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { parseSync } from "oxc-parser";
|
|
import { UNIT_TEST_EXCLUDE, UNIT_TEST_GLOB } from "../../vitest.unit.config.ts";
|
|
import { ROOT } from "../living-docs/ast.ts";
|
|
import { allSourceFiles, resolveImport } from "../living-docs/source-index.ts";
|
|
|
|
export interface TestCandidate {
|
|
/** Repo relative. */
|
|
file: string;
|
|
/** Imports between the test file and the target, 1 for a test importing it directly. */
|
|
distance: number;
|
|
}
|
|
|
|
type ImporterGraph = Map<string, Set<string>>;
|
|
|
|
/**
|
|
* The unit test files that import `target` directly or through other modules, nearest first.
|
|
* These are only candidates: the baseline run tells which of them execute the mutated code.
|
|
*/
|
|
export function testCandidatesOf(
|
|
target: string,
|
|
graph: ImporterGraph,
|
|
): TestCandidate[] {
|
|
const distances = new Map([[target, 0]]);
|
|
const queue = [target];
|
|
for (let file = queue.shift(); file; file = queue.shift()) {
|
|
for (const importer of graph.get(file) ?? []) {
|
|
if (distances.has(importer)) continue;
|
|
|
|
distances.set(importer, distances.get(file)! + 1);
|
|
queue.push(importer);
|
|
}
|
|
}
|
|
|
|
return [...distances]
|
|
.filter(([file]) => isUnitTestFile(file))
|
|
.map(([file, distance]) => ({ file, distance }))
|
|
.sort((a, b) => a.distance - b.distance || a.file.localeCompare(b.file));
|
|
}
|
|
|
|
/** For every source file the files importing a value from it, re-exports and `import()` included. */
|
|
export function importerGraph(): ImporterGraph {
|
|
const graph: ImporterGraph = new Map();
|
|
|
|
for (const file of allSourceFiles()) {
|
|
for (const specifier of valueImportSpecifiers(file)) {
|
|
const imported = resolveImport(file, specifier);
|
|
if (!imported) continue;
|
|
|
|
const importers = graph.get(imported) ?? new Set();
|
|
importers.add(file);
|
|
graph.set(imported, importers);
|
|
}
|
|
}
|
|
|
|
return graph;
|
|
}
|
|
|
|
/** Whether the unit projects of `vitest.unit.config.ts` run the file. */
|
|
export function isUnitTestFile(file: string) {
|
|
return (
|
|
path.matchesGlob(file, UNIT_TEST_GLOB) &&
|
|
!UNIT_TEST_EXCLUDE.some((pattern) => path.matchesGlob(file, pattern))
|
|
);
|
|
}
|
|
|
|
function valueImportSpecifiers(file: string) {
|
|
const source = fs.readFileSync(path.join(ROOT, file), "utf8");
|
|
const { module } = parseSync(file, source);
|
|
|
|
return [
|
|
...module.staticImports
|
|
.filter(
|
|
(statement) =>
|
|
statement.entries.length === 0 ||
|
|
statement.entries.some((entry) => !entry.isType),
|
|
)
|
|
.map((statement) => statement.moduleRequest.value),
|
|
...module.staticExports.flatMap((statement) =>
|
|
statement.entries
|
|
.filter((entry) => entry.moduleRequest && !entry.isType)
|
|
.map((entry) => entry.moduleRequest!.value),
|
|
),
|
|
...module.dynamicImports.map((dynamicImport) =>
|
|
source
|
|
.slice(
|
|
dynamicImport.moduleRequest.start,
|
|
dynamicImport.moduleRequest.end,
|
|
)
|
|
.replace(/^["'`]|["'`]$/g, ""),
|
|
),
|
|
];
|
|
}
|