com.wallstop-studios.unity-helpers
Version:
Treasure chest of Unity developer tools
198 lines (177 loc) • 5.83 kB
JavaScript
#!/usr/bin/env node
// MIT License — Copyright (c) wallstop studios
//
// Lints cspell.json for common configuration issues:
// 1. Case-redundant entries (when caseSensitive is false)
// 2. Cross-dictionary duplicates (same word in multiple dictionaries)
//
// Usage:
// node scripts/lint-cspell-config.js # Check only
// node scripts/lint-cspell-config.js --fix # Auto-deduplicate
"use strict";
const fs = require("fs");
const path = require("path");
const CSPELL_PATH = path.resolve(__dirname, "..", "cspell.json");
function loadConfig() {
try {
const raw = fs.readFileSync(CSPELL_PATH, "utf8");
return JSON.parse(raw);
} catch (err) {
console.error(`Error loading cspell.json: ${err.message}`);
process.exit(1);
}
}
/**
* Find case-redundant entries within a single word list.
* Returns an array of { canonical, duplicates } objects.
*/
function findCaseRedundant(words) {
const seen = new Map(); // lowercase → first occurrence
const redundant = [];
for (const word of words) {
const lower = word.toLowerCase();
if (seen.has(lower)) {
const existing = seen.get(lower);
existing.variants.push(word);
} else {
seen.set(lower, { canonical: word, variants: [] });
}
}
for (const [, entry] of seen) {
if (entry.variants.length > 0) {
redundant.push({
canonical: entry.canonical,
duplicates: entry.variants
});
}
}
return redundant;
}
/**
* Deduplicate a word list, keeping the first occurrence of each case-insensitive word.
*/
function deduplicateWords(words) {
const seen = new Set();
const result = [];
for (const word of words) {
const lower = word.toLowerCase();
if (!seen.has(lower)) {
seen.add(lower);
result.push(word);
}
}
return result;
}
/**
* Find words that appear in multiple dictionaries or in both a dictionary and root words.
*/
function findCrossDuplicates(config) {
const locations = new Map(); // lowercase → [location names]
// Check categorized dictionaries
if (config.dictionaryDefinitions) {
for (const dict of config.dictionaryDefinitions) {
if (dict.words) {
for (const word of dict.words) {
const lower = word.toLowerCase();
if (!locations.has(lower)) {
locations.set(lower, []);
}
locations.get(lower).push(`dictionary:${dict.name}`);
}
}
}
}
// Check root words
if (config.words) {
for (const word of config.words) {
const lower = word.toLowerCase();
if (!locations.has(lower)) {
locations.set(lower, []);
}
locations.get(lower).push("root:words");
}
}
const duplicates = [];
for (const [word, locs] of locations) {
if (locs.length > 1) {
duplicates.push({ word, locations: locs });
}
}
return duplicates;
}
function main() {
const fixMode = process.argv.includes("--fix");
const config = loadConfig();
const isCaseInsensitive = config.caseSensitive === false;
let issueCount = 0;
// ── Check 1: Case-redundant entries ──
if (isCaseInsensitive) {
// Check categorized dictionaries
if (config.dictionaryDefinitions) {
for (const dict of config.dictionaryDefinitions) {
if (dict.words) {
const redundant = findCaseRedundant(dict.words);
for (const r of redundant) {
issueCount++;
const all = [r.canonical, ...r.duplicates].join(", ");
console.log(
` [dictionary:${dict.name}] Case-redundant: ${all} (caseSensitive is false)`
);
}
if (fixMode) {
dict.words = deduplicateWords(dict.words);
}
}
}
}
// Check root words
if (config.words) {
const redundant = findCaseRedundant(config.words);
for (const r of redundant) {
issueCount++;
const all = [r.canonical, ...r.duplicates].join(", ");
console.log(` [root:words] Case-redundant: ${all} (caseSensitive is false)`);
}
if (fixMode) {
config.words = deduplicateWords(config.words);
}
}
}
// ── Check 2: Cross-dictionary duplicates (warnings only — not blocking) ──
const crossDups = findCrossDuplicates(config);
let warningCount = 0;
for (const d of crossDups) {
warningCount++;
console.error(
` [cross-duplicate] (warning) "${d.word}" appears in: ${d.locations.join(", ")}`
);
}
// ── Summary ──
if (issueCount > 0 && !fixMode) {
console.log("\nChecks enforced:");
console.log(" 1. Case-redundant entries (blocking)");
console.log(" 2. Cross-dictionary duplicates (warning only)");
console.log(`\ncspell.json: ${issueCount} error(s), ${warningCount} warning(s).`);
console.log("Run with --fix to auto-deduplicate case-redundant entries.");
process.exit(1);
} else if (issueCount > 0 && fixMode) {
// Write back
const output = JSON.stringify(config, null, 2) + "\n";
fs.writeFileSync(CSPELL_PATH, output, "utf8");
console.log(
`cspell.json: Fixed ${issueCount} case-redundant entries. Run prettier to re-format.`
);
process.exit(0);
} else {
console.log("Checks enforced:");
console.log(" 1. Case-redundant entries (blocking)");
console.log(" 2. Cross-dictionary duplicates (warning only)");
if (warningCount > 0) {
console.log(`cspell.json: No errors. ${warningCount} warning(s) (cross-duplicates).`);
} else {
console.log("cspell.json: No issues found.");
}
process.exit(0);
}
}
main();