Files
Personal-Portfolio-Website/scripts/check-source-quality.cjs
2026-04-17 23:40:29 +02:00

97 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const {
PROJECT_ROOT,
DEFAULT_ALLOWED_EXTENSIONS,
collectSourceFiles,
toProjectRelativePath,
} = require('./source-files.cjs');
const SOURCE_DIRECTORIES = [
path.join(PROJECT_ROOT, 'backend', 'src'),
path.join(PROJECT_ROOT, 'src'),
path.join(PROJECT_ROOT, 'scripts'),
];
const IGNORED_RELATIVE_PATHS = new Set([
'scripts/check-source-quality.cjs',
'scripts/fix-source-text.cjs',
]);
const ALLOWED_EXTENSIONS = DEFAULT_ALLOWED_EXTENSIONS;
const MOJIBAKE_PATTERNS = [
{ label: 'UTF8_AS_LATIN1', regex: /\u00C3[\u0080-\u00BF]/u },
{ label: 'REPLACEMENT_CHAR', regex: /\uFFFD/u },
{ label: 'MISDECODED_QUESTION_MARK', regex: /\u00EF\u00BF\u00BD/u },
{ label: 'CP1252_ARTIFACT', regex: /\u00C2[\u0080-\u00BF]?/u },
{
label: 'DOUBLE_DECODED_APOSTROPHE',
regex: /\u00E2\u20AC\u2122|\u00E2\u20AC\u0153|\u00E2\u20AC\u009D/u,
},
];
const ASCII_TRANSLITERATION_PATTERNS = [
{
label: 'ASCII_TRANSLITERATION',
regex:
/\b(?:fuer|Fuer|gueltig|Gueltig|ungueltig|Ungueltig|zurueck|Zurueck|ueberein|Ueberein|waehlen|Waehlen|zaehler|Zaehler|ausfuellen|Ausfuellen|geschuetzt|Geschuetzt|verschluesselt|Verschluesselt)\b/u,
},
];
const VAR_PATTERN = /\bvar\s+[A-Za-z_$][A-Za-z0-9_$]*/u;
/** @type {Array<{file: string, rule: string, detail: string}>} */
const issues = [];
for (const directoryPath of SOURCE_DIRECTORIES) {
const files = collectSourceFiles(directoryPath, ALLOWED_EXTENSIONS);
for (const filePath of files) {
const source = fs.readFileSync(filePath, 'utf8');
const relativePath = toProjectRelativePath(filePath);
const extension = path.extname(filePath).toLowerCase();
if (IGNORED_RELATIVE_PATHS.has(relativePath)) {
continue;
}
for (const pattern of MOJIBAKE_PATTERNS) {
if (pattern.regex.test(source)) {
issues.push({
file: relativePath,
rule: pattern.label,
detail: 'possible mojibake detected',
});
}
}
for (const pattern of ASCII_TRANSLITERATION_PATTERNS) {
if (pattern.regex.test(source)) {
issues.push({
file: relativePath,
rule: pattern.label,
detail: 'possible ASCII transliteration detected',
});
}
}
if (extension !== '.html' && VAR_PATTERN.test(source)) {
issues.push({
file: relativePath,
rule: 'NO_VAR',
detail: 'use const/let instead of var',
});
}
}
}
if (issues.length === 0) {
console.log('Source quality check passed: no mojibake artifacts and no var declarations found.');
process.exit(0);
}
console.error('Source quality check failed:');
for (const issue of issues) {
console.error(`- ${issue.file} [${issue.rule}] ${issue.detail}`);
}
process.exit(1);