// TODO: Check of dit bestand weg kan — uitsluitend gebruikt door eigen test import { access, readdir, readFile } from "node:fs/promises"; import { dirname, extname, join, relative, resolve, sep } from "node:path"; export interface MissingLocalImport { importer: string; specifier: string; } const sourceExtensions = new Set([ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ]); const resolutionSuffixes = [ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".css", "/index.ts", "/index.tsx", "/index.js", "/index.jsx", ]; const importPattern = /(?:import|export)\s+(?:[^"']*?\s+from\s+)?["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)|require\(\s*["']([^"']+)["']\s*\)/g; async function collectSourceFiles(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }); const files = await Promise.all( entries.map(async (entry) => { const path = join(directory, entry.name); if (entry.isDirectory()) return collectSourceFiles(path); if (/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) return []; return sourceExtensions.has(extname(entry.name)) ? [path] : []; }), ); return files.flat(); } async function exists(path: string): Promise { try { await access(path); return true; } catch { return false; } } async function resolvesToLocalFile(base: string): Promise { if (extname(base) && (await exists(base))) return true; for (const suffix of resolutionSuffixes) { if (await exists(`${base}${suffix}`)) return true; } return false; } export async function findMissingLocalImports( root: string, ): Promise { const src = resolve(root, "src"); const files = await collectSourceFiles(src); const missing: MissingLocalImport[] = []; for (const importer of files) { const source = await readFile(importer, "utf8"); for (const match of source.matchAll(importPattern)) { const specifier = match[1] ?? match[2] ?? match[3]; if ( !specifier?.startsWith("@/") && !specifier?.startsWith("./") && !specifier?.startsWith("../") ) continue; const base = specifier.startsWith("@/") ? resolve(src, specifier.slice(2)) : resolve(dirname(importer), specifier); if (!(await resolvesToLocalFile(base))) { missing.push({ importer: relative(root, importer).split(sep).join("/"), specifier, }); } } } return missing.sort( (a, b) => a.importer.localeCompare(b.importer) || a.specifier.localeCompare(b.specifier), ); }