Equicord/scripts/lintIntl.mjs

55 lines
2 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 VALID_MODIFIERS = new Set(["raw", "hash"]);
const MARKER_RE = /#\{intl::([\w$+/]*)(?:::(\w+))?\}/g;
const HASH_RE = /["'`]\s*\.([A-Za-z][A-Za-z0-9+/]{5})\b/g;
const tracked = execFileSync("git", ["ls-files", "src"], { cwd: ROOT, encoding: "utf8" })
.split("\n")
.filter(p => /\.(ts|tsx|js|jsx|mjs)$/.test(p))
.map(p => p.replace(/\//g, sep));
let errors = 0;
let warnings = 0;
for (const rel of tracked) {
const text = readFileSync(join(ROOT, rel), "utf8");
if (!text.includes("#{intl::") && !text.includes("find:") && !text.includes("match:")) 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}`;
if (/#\{intl::[^}]*$/.test(line)) {
console.error(`${at}: ERROR unclosed #{intl:: marker`);
errors++;
}
for (const [full, key, modifier] of line.matchAll(MARKER_RE)) {
if (!key) {
console.error(`${at}: ERROR empty intl key: ${full}`);
errors++;
}
if (modifier && !VALID_MODIFIERS.has(modifier)) {
console.error(`${at}: ERROR unknown intl modifier "${modifier}": ${full}`);
errors++;
}
}
if (/\b(find|match)\s*:/.test(line) && !line.includes("#{intl::")) {
for (const [, hash] of line.matchAll(HASH_RE)) {
if (!/[0-9+/]/.test(hash)) continue;
console.warn(`${at}: WARN baked intl hash ".${hash}", prefer #{intl::KEY}`);
warnings++;
}
}
}
}
console.log(`\nintl lint: ${errors} error(s), ${warnings} warning(s)`);
process.exit(errors > 0 ? 1 : 0);