101 lines
2.7 KiB
JavaScript
101 lines
2.7 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
/**
|
|
* Scan simple de archivos JS alcanzables desde un entrypoint,
|
|
* siguiendo imports estáticos: `import ... from "./x.js"` y `await import("./x.js")`.
|
|
*
|
|
* OJO: no entiende requires dinámicos ni construcciones complejas.
|
|
* Útil para detectar archivos claramente no usados.
|
|
*/
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const root = path.resolve(__dirname, "..");
|
|
|
|
const entry = path.join(root, "index.js");
|
|
const srcDir = path.join(root, "src");
|
|
|
|
function listJsFiles(dir) {
|
|
const out = [];
|
|
const stack = [dir];
|
|
while (stack.length) {
|
|
const cur = stack.pop();
|
|
const items = fs.readdirSync(cur, { withFileTypes: true });
|
|
for (const it of items) {
|
|
const p = path.join(cur, it.name);
|
|
if (it.isDirectory()) stack.push(p);
|
|
else if (it.isFile() && p.endsWith(".js")) out.push(p);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function readText(p) {
|
|
return fs.readFileSync(p, "utf8");
|
|
}
|
|
|
|
function resolveLocalImport(fromFile, spec) {
|
|
if (!spec.startsWith(".")) return null;
|
|
const base = path.resolve(path.dirname(fromFile), spec);
|
|
const candidates = [];
|
|
if (base.endsWith(".js")) candidates.push(base);
|
|
else {
|
|
candidates.push(`${base}.js`);
|
|
candidates.push(path.join(base, "index.js"));
|
|
}
|
|
for (const c of candidates) {
|
|
if (fs.existsSync(c) && fs.statSync(c).isFile()) return c;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function extractImports(code) {
|
|
const out = new Set();
|
|
// static import ... from "x"
|
|
for (const m of code.matchAll(/import\s+[^;]*?\s+from\s+["']([^"']+)["']/g)) {
|
|
out.add(m[1]);
|
|
}
|
|
// side-effect import "x"
|
|
for (const m of code.matchAll(/import\s+["']([^"']+)["']/g)) {
|
|
out.add(m[1]);
|
|
}
|
|
// dynamic import("x") or await import("x")
|
|
for (const m of code.matchAll(/import\(\s*["']([^"']+)["']\s*\)/g)) {
|
|
out.add(m[1]);
|
|
}
|
|
return [...out];
|
|
}
|
|
|
|
const allSrc = listJsFiles(srcDir);
|
|
const all = [entry, ...allSrc];
|
|
|
|
const reachable = new Set();
|
|
const queue = [entry];
|
|
|
|
while (queue.length) {
|
|
const f = queue.shift();
|
|
if (reachable.has(f)) continue;
|
|
reachable.add(f);
|
|
let code = "";
|
|
try {
|
|
code = readText(f);
|
|
} catch {
|
|
continue;
|
|
}
|
|
const specs = extractImports(code);
|
|
for (const s of specs) {
|
|
const resolved = resolveLocalImport(f, s);
|
|
if (resolved && !reachable.has(resolved)) queue.push(resolved);
|
|
}
|
|
}
|
|
|
|
const unused = allSrc
|
|
.filter((f) => !reachable.has(f))
|
|
.map((f) => path.relative(root, f).replace(/\\/g, "/"))
|
|
.sort();
|
|
|
|
console.log(JSON.stringify({ entry: path.relative(root, entry), reachable_count: reachable.size, total_src: allSrc.length, unused }, null, 2));
|
|
|