54 lines
2 KiB
JavaScript
54 lines
2 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 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);
|