rea/scripts/ci/static-check-report.mjs

150 lines
4.8 KiB
JavaScript

import { spawnSync } from "node:child_process";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, join, relative, sep } from "node:path";
import { z } from "zod";
const require = createRequire(import.meta.url);
const mode = process.argv[2];
const reportRoot = join(".cache", "static-checks");
const extraArguments = process.argv.slice(3);
const lintReport = z.object({
number_of_files: z.number(),
diagnostics: z.array(
z.object({
message: z.string(),
code: z.string().optional(),
severity: z.enum(["error", "warning", "advice"]),
filename: z.string(),
labels: z.array(
z.object({
span: z.object({ line: z.number(), column: z.number() }),
}),
),
}),
),
});
const duplicateReport = z
.object({
duplicates: z.array(
z
.object({
firstFile: z.object({ name: z.string() }).passthrough(),
secondFile: z.object({ name: z.string() }).passthrough(),
})
.passthrough(),
),
statistics: z
.object({
total: z
.object({
sources: z.number(),
clones: z.number(),
percentage: z.number(),
})
.passthrough(),
})
.passthrough(),
})
.passthrough();
const run = (packageName, entrypoint, arguments_) => {
const executable = join(
dirname(require.resolve(`${packageName}/package.json`)),
entrypoint,
);
const result = spawnSync(process.execPath, [executable, ...arguments_], {
encoding: "utf8",
maxBuffer: 32 * 1024 * 1024,
});
if (result.stderr) process.stderr.write(result.stderr);
if (result.error !== undefined) throw result.error;
if (result.status === null)
throw new Error(`${packageName} terminated by ${result.signal}`);
return result;
};
try {
await mkdir(reportRoot, { recursive: true });
if (mode === "lint") {
const reportPath = join(reportRoot, "oxlint.json");
await rm(reportPath, { force: true });
const result = run("oxlint", "bin/oxlint", [
...extraArguments,
"--format",
"json",
]);
let report;
try {
report = lintReport.parse(JSON.parse(result.stdout));
} catch (cause) {
process.stderr.write(result.stdout);
throw cause;
}
await writeFile(reportPath, result.stdout);
const warnings = report.diagnostics.filter(
({ severity }) => severity === "warning",
);
const errors = report.diagnostics.filter(
({ severity }) => severity === "error",
);
console.log(
`Lint: ${report.number_of_files} files, ${errors.length} errors, ${warnings.length} warnings.`,
);
for (const diagnostic of errors) {
const location = diagnostic.labels[0]?.span;
console.error(
`${diagnostic.filename}:${location?.line ?? 1}:${location?.column ?? 1}: ${diagnostic.code ?? "lint"}: ${diagnostic.message}`,
);
}
const counts = new Map();
for (const { code } of warnings)
counts.set(code ?? "warning", (counts.get(code ?? "warning") ?? 0) + 1);
for (const [rule, count] of [...counts]
.sort(
(left, right) => right[1] - left[1] || left[0].localeCompare(right[0]),
)
.slice(0, 8))
console.log(` ${rule}: ${count} warnings`);
console.log(`Full diagnostics: ${reportPath}; npm run lint:details`);
process.exitCode = result.status;
} else if (mode === "duplicates") {
const output = join(reportRoot, "jscpd");
const reportPath = join(output, "jscpd-report.json");
await rm(reportPath, { force: true });
const result = run("jscpd", "run-jscpd.js", [
"src/",
"tests/",
...extraArguments,
"--reporters",
"json",
"--output",
output,
"--no-colors",
"--no-tips",
"--absolute",
]);
if (result.stdout) process.stdout.write(result.stdout);
const report = duplicateReport.parse(
JSON.parse(await readFile(reportPath, "utf8")),
);
// jscpd otherwise strips each scan root, making src/ and tests/ names
// ambiguous. Normalize absolute producer paths once for portable reports.
for (const clone of report.duplicates)
for (const file of [clone.firstFile, clone.secondFile])
file.name = relative(process.cwd(), file.name).split(sep).join("/");
await writeFile(reportPath, JSON.stringify(report, null, 2));
const { sources, clones, percentage } = report.statistics.total;
console.log(
`Duplication: ${sources} files, ${clones} clones, ${percentage.toFixed(2)}% duplicated lines.`,
);
console.log(`Full diagnostics: ${reportPath}; npm run jscpd:details`);
process.exitCode = result.status;
} else {
throw new Error("Expected lint or duplicates");
}
} catch (cause) {
console.error(cause instanceof Error ? cause.message : String(cause));
process.exitCode = 1;
}