morphit/apps/indexer/scripts/duplicate-import-smoke.ts

153 lines
4.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Static-analysis smoke: duplicate-import detector.
*
* Catches the pattern where two or more identical `import { X }
* from 'foo'` lines appear in the same file. These are usually
* artifacts of merge conflicts or copy-paste edits that survived
* review because:
* - bundlers (Vite/esbuild/tsc) accept duplicate imports
* without error and dedupe them at build time.
* - linters that catch this aren't always wired into smokes.
*
* Why it matters: duplicate imports add bundle bytes (tiny) but
* are a strong signal that someone hand-edited an import block
* and left a mess. The audit found three instances of this in
* the codebase (BATCH19B caught two; BATCH19D-pass-d caught
* three more in +layout.svelte, ConversationView.svelte, and
* release-validator-smoke.ts) — and one of those was in the
* highest-traffic file in the entire app (the root layout).
*
* Output: exit-code-0 if no dups; exit-code-1 + concrete
* findings on any hit. Each scanned file counts as one
* scenario in the runner protocol.
*/
import { readFile, readdir } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Script is at apps/indexer/scripts/, repo root is 3 up.
const REPO = join(__dirname, '..', '..', '..');
const SCAN_ROOTS = [
'apps/web/src',
'apps/indexer/src',
'apps/indexer/scripts',
'apps/relay/src',
'apps/ops-cli/src',
'apps/matrix-bot/src',
'packages'
];
const IMPORT_RE = /^(\s*import\s+[^\n]+from\s+['"][^'"]+['"];?)/gm;
interface Finding {
file: string;
count: number;
importLine: string;
}
async function* walk(dir: string): AsyncGenerator<string> {
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return;
}
for (const e of entries) {
const full = join(dir, e.name);
if (e.isDirectory()) {
if (
e.name === 'node_modules' ||
e.name === '.svelte-kit' ||
e.name === '.git' ||
e.name === 'dist' ||
e.name === 'build'
)
continue;
yield* walk(full);
} else if (e.isFile()) {
if (
full.endsWith('.ts') ||
full.endsWith('.svelte') ||
full.endsWith('.tsx') ||
full.endsWith('.js') ||
full.endsWith('.mjs')
)
yield full;
}
}
}
async function main(): Promise<void> {
const findings: Finding[] = [];
let scanned = 0;
// cp131 — fail loudly on stale SCAN_ROOTS. Pre-cp131 the walk()
// helper silently caught ENOENT and returned, so paths like
// `apps/avatar/src` (long-since-removed) sat in the list
// unnoticed. Stale roots are silent drift: a real bug in a
// missing directory wouldn't fire. Verify each root exists
// before walking.
const { access } = await import('node:fs/promises');
for (const root of SCAN_ROOTS) {
try {
await access(join(REPO, root));
} catch {
console.error(
` ✗ stale SCAN_ROOT: '${root}' does not exist on disk.\n` +
` If a workspace was removed, drop the entry from SCAN_ROOTS.\n` +
` If a new workspace was added, this smoke needs an additive update.`
);
process.exit(1);
}
}
for (const root of SCAN_ROOTS) {
for await (const file of walk(join(REPO, root))) {
scanned++;
let src;
try {
src = await readFile(file, 'utf8');
} catch {
continue;
}
const counts = new Map<string, number>();
for (const m of src.matchAll(IMPORT_RE)) {
const line = (m[1] ?? '').trim();
counts.set(line, (counts.get(line) ?? 0) + 1);
}
for (const [line, c] of counts) {
if (c > 1) {
findings.push({
file: file.replace(REPO, ''),
count: c,
importLine: line.slice(0, 100)
});
}
}
}
}
console.log(`Scanned ${scanned} source files.`);
if (findings.length > 0) {
console.error(`\nFAIL: ${findings.length} duplicate-import finding(s):\n`);
for (const f of findings) {
console.error(` ${f.file}: ${f.count}× ${f.importLine}`);
}
console.error(
"\nDelete the extra copies. Bundlers tolerate dups but they're " +
'almost always merge / copy-paste artifacts.\n'
);
process.exit(1);
}
console.log(`OK: 0 duplicate-import findings across ${scanned} files.`);
console.log(`✓ all ${scanned} scenarios passed`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});