70 lines
2.7 KiB
JavaScript
70 lines
2.7 KiB
JavaScript
#!/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);
|