Equicord/scripts/lintPatches.mjs

71 lines
2.7 KiB
JavaScript
Raw Permalink Normal View History

2026-05-15 20:19:13 +02:00
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join, sep } from "node:path";
const ROOT = new URL("..", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
const VERBOSE = process.env.LINT_PATCHES_VERBOSE === "1" || process.argv.includes("--verbose");
const tracked = execFileSync("git", ["ls-files", "src"], { cwd: ROOT, encoding: "utf8" })
.split("\n")
.filter(p => /^src\/(plugins|equicordplugins)\/.*\.(ts|tsx)$/.test(p))
.map(p => p.replace(/\//g, sep));
let errors = 0;
let warnings = 0;
function fail(at, rule, msg) {
console.error(`${at}: ERROR ${rule} ${msg}`);
errors++;
}
function warn(at, rule, msg) {
if (VERBOSE) console.warn(`${at}: WARN ${rule} ${msg}`);
warnings++;
}
for (const rel of tracked) {
const text = readFileSync(join(ROOT, rel), "utf8");
if (!text.includes("match:") && !text.includes("find:")) continue;
const lines = text.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const at = `${rel.replace(/\\/g, "/")}:${i + 1}`;
const matchM = line.match(/\bmatch\s*:\s*\/((?:\\.|[^/\\\n])+)\/[gimsuy]*/);
if (matchM) {
const src = matchM[1];
const unbounded = src.match(/\.[+*]\??/);
if (unbounded) warn(at, "P002", `unbounded ${unbounded[0]} in match`);
const minified = src.match(/\b[a-z]\.[a-z]\b/);
if (minified) fail(at, "P001", `hardcoded minified var "${minified[0]}", use \\i.\\i`);
}
const findM = line.match(/\bfind\s*:\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\/(?:\\.|[^/\\\n])+\/[gimsuy]*)/);
if (findM) {
const window = lines.slice(i, i + 12).join("\n");
if (/\b(replacement|match)\s*:/.test(window)) {
const v = findM[1];
if (v === '""' || v === "''" || v === "``") {
fail(at, "P003", "find is empty");
} else if (v.startsWith("/")) {
const src = v.slice(1, v.lastIndexOf("/"));
if (/\\i/.test(src) && !/[A-Za-z]{3,}/.test(src) && !src.includes("#{intl::")) {
warn(at, "P004", "find regex has no string anchor");
}
}
}
}
const replaceM = line.match(/\breplace\s*:\s*(["'`])((?:\\.|(?!\1).)*)\1/);
if (replaceM && /\btry\s*\{[\s\S]*?\}\s*catch\b/.test(replaceM[2])) {
warn(at, "P005", "replace uses try/catch, fix the match instead");
}
}
}
const tail = warnings && !VERBOSE ? " (run with --verbose to list)" : "";
console.log(`\npatch lint: ${errors} error(s), ${warnings} warning(s)${tail}`);
process.exit(errors > 0 ? 1 : 0);