import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative } from 'node:path'; const root = 'd:/work/ylrz_saas/java'; function walk(dir, out = []) { for (const name of readdirSync(dir)) { const p = join(dir, name); const st = statSync(p); if (st.isDirectory()) { if (name === 'target' || name === 'node_modules' || name === '.git') continue; walk(p, out); } else if (name.endsWith('.java')) { out.push(p); } } return out; } function hasMojibakeLatin(line) { // UTF-8 CJK misread as Latin-1 often produces 3+ chars in U+00C0..U+00FF range const m = line.match(/[\u00c0-\u00ff]{3,}/g); if (!m) return false; return /\/\/|\/\*|\*|"|'|error\(|success\(|return /.test(line); } function scanFile(absPath) { const raw = readFileSync(absPath); const rel = relative('d:/work/ylrz_saas', absPath).replace(/\\/g, '/'); const bom = raw.length >= 3 && raw[0] === 0xef && raw[1] === 0xbb && raw[2] === 0xbf; const t = raw.toString('utf8'); const badLines = []; t.split('\n').forEach((line, i) => { const n = i + 1; if (line.includes('\uFFFD')) { badLines.push({ line: n, kind: 'FFFD', text: line.trim().slice(0, 100) }); return; } if (line.includes('\u951f\u65a4\u70b7')) { badLines.push({ line: n, kind: 'replacement-seq', text: line.trim().slice(0, 100) }); return; } if (hasMojibakeLatin(line)) { badLines.push({ line: n, kind: 'latin-mojibake', text: line.trim().slice(0, 100) }); return; } if (/[^\s"'/\\]\?{2,}[^\s?]/.test(line) && /\/\/|\/\*|"|'|error\(|success\(/.test(line)) { badLines.push({ line: n, kind: 'qmarks', text: line.trim().slice(0, 100) }); } }); const unicodeEscapes = (t.match(/\\u[0-9a-fA-F]{4}/g) || []).length; if (!bom && !badLines.length) return null; return { rel, bom, badLineCount: badLines.length, badLines: badLines.slice(0, 5), unicodeEscapes }; } const files = walk(root); const issues = files.map(scanFile).filter(Boolean); issues.sort((a, b) => a.rel.localeCompare(b.rel)); const summary = { scanned: files.length, filesWithIssues: issues.length, bomFiles: issues.filter((x) => x.bom).map((x) => x.rel), fffdFiles: issues.filter((x) => x.badLines.some((l) => l.kind === 'FFFD')).map((x) => x.rel), issues: issues.map((x) => ({ rel: x.rel, bom: x.bom, badLineCount: x.badLineCount, samples: x.badLines, })), }; import { writeFileSync } from 'node:fs'; const outPath = 'd:/work/ylrz_saas/java/fs-service/scripts/scan_java_encoding_result.json'; writeFileSync(outPath, JSON.stringify(summary, null, 2), 'utf8'); console.log('scanned', summary.scanned, 'java files'); console.log('files with issues', summary.filesWithIssues); console.log('BOM files', summary.bomFiles.length); console.log('FFFD files', summary.fffdFiles.length); console.log('result', outPath); if (summary.fffdFiles.length) { console.log('FFFD list:'); summary.fffdFiles.forEach((f) => console.log(' ', f)); }