Files
pulseboard/kontrat-testi.mjs

200 lines
6.0 KiB
JavaScript
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.
/* =========================================================
PulseBoard — Sözleşme (contract) testi
Tüm ESM import'larını gerçekten çözümler:
1) Hedef dosya diskte var mı?
2) İstenen isimli export o dosyada gerçekten var mı?
3) Döngüsel bağımlılık var mı?
Çalıştırma: node kontrat-testi.mjs
========================================================= */
import { readFileSync, existsSync } from "node:fs";
import { dirname, resolve, relative } from "node:path";
const KOK = process.cwd();
const hatalar = [];
const uyarilar = [];
const grafik = new Map();
/** Bir dosyadan import/export ifadelerini kabaca çıkarır */
function importlariCikar(kod) {
const liste = [];
const re = /(?:^|\n)\s*(?:import|export)\s+([\s\S]*?)\s*from\s*["']([^"']+)["']/g;
let m;
while ((m = re.exec(kod))) {
const govde = m[1].trim();
const kaynak = m[2];
const adlar = [];
const susl = govde.match(/^\{([\s\S]*)\}$/);
if (susl) {
for (const parca of susl[1].split(",")) {
const p = parca.trim();
if (!p) continue;
const asli = p.split(/\s+as\s+/)[0].trim();
if (asli && asli !== "default") adlar.push(asli);
}
}
const yanEtki = !govde && kaynak;
liste.push({ govde, kaynak, adlar, yanEtki });
}
// yan etkili import "import './x.js'"
const reYan = /(?:^|\n)\s*import\s*["']([^"']+)["']/g;
while ((m = reYan.exec(kod))) {
liste.push({ govde: "", kaynak: m[1], adlar: [], yanEtki: true });
}
return liste;
}
/** Dosyada export edilen isimleri toplar */
function exportlariCikar(kod) {
const isimler = new Set();
const reFonksiyon = /export\s+(?:async\s+)?(?:function|class|const|let|var)\s+([A-Za-z0-9_$]+)/g;
let m;
while ((m = reFonksiyon.exec(kod))) isimler.add(m[1]);
const reListe = /export\s*\{([^}]*)\}/g;
while ((m = reListe.exec(kod))) {
for (const parca of m[1].split(",")) {
const p = parca.trim();
if (!p) continue;
const asli = p.split(/\s+as\s+/)[0].trim();
if (asli) isimler.add(asli);
}
}
const reVarsayilan =
/export\s+default\s+(?:function|class)?\s*([A-Za-z0-9_$]*)/g;
while ((m = reVarsayilan.exec(kod))) isimler.add("default");
if (/export\s+default\b/.test(kod)) isimler.add("default");
return isimler;
}
/* --- Taranacak dosyalar (giriş noktasından derinlemesine) --- */
const baslangic = "js/app.js";
const gorulen = new Set();
const kuyruk = [baslangic];
const tumDosyalar = new Set();
function gitme(yol) {
const mutlak = resolve(KOK, yol);
if (gorulen.has(mutlak)) return;
gorulen.add(mutlak);
if (!existsSync(mutlak)) {
hatalar.push(`MISSING FILE: ${yol}`);
return;
}
tumDosyalar.add(yol);
const kod = readFileSync(mutlak, "utf8");
const bagimliliklar = importlariCikar(kod);
const yerelBagimliliklar = [];
for (const b of bagimliliklar) {
if (!b.kaynak.startsWith(".")) {
uyarilar.push(
`${yol}: paket import'u "${b.kaynak}" (node_modules gerekir)`,
);
continue;
}
const hedefYol = resolve(dirname(mutlak), b.kaynak);
const hedefGoreli = relative(KOK, hedefYol).replace(/\\/g, "/");
if (!existsSync(hedefYol)) {
hatalar.push(
`UNRESOLVED IMPORT: ${yol} -> "${b.kaynak}" (beklenen: ${hedefGoreli})`,
);
continue;
}
yerelBagimliliklar.push(hedefGoreli);
// isimli export kontrolü
if (b.adlar.length) {
const hedefKod = readFileSync(hedefYol, "utf8");
const mevcut = exportlariCikar(hedefKod);
for (const ad of b.adlar) {
if (!mevcut.has(ad)) {
hatalar.push(
`MISSING EXPORT: ${yol} -> "${b.kaynak}" içinden { ${ad} } import ediyor ama ${hedefGoreli} bunu export ETMİYOR`,
);
}
}
}
}
grafik.set(yol, yerelBagimliliklar);
}
while (kuyruk.length) {
const dosya = kuyruk.shift();
gitme(dosya);
const mutlak = resolve(KOK, dosya);
if (!existsSync(mutlak)) continue;
const kod = readFileSync(mutlak, "utf8");
for (const b of importlariCikar(kod)) {
if (!b.kaynak.startsWith(".")) continue;
const hedefYol = resolve(dirname(mutlak), b.kaynak);
const hedefGoreli = relative(KOK, hedefYol).replace(/\\/g, "/");
if (!gorulen.has(hedefYol)) kuyruk.push(hedefGoreli);
}
}
/* --- Döngüsel bağımlılık tespiti (DFS) --- */
const BEYAZ = 0, GRI = 1, SIYAH = 2;
const renk = new Map();
const yigin = [];
const donguler = new Set();
function dfs(dugum) {
renk.set(dugum, GRI);
yigin.push(dugum);
for (const komsu of grafik.get(dugum) || []) {
const c = renk.get(komsu) ?? BEYAZ;
if (c === GRI) {
const bas = yigin.indexOf(komsu);
const dongu = [...yigin.slice(bas), komsu].join(" -> ");
donguler.add(dongu);
} else if (c === BEYAZ) {
dfs(komsu);
}
}
yigin.pop();
renk.set(dugum, SIYAH);
}
for (const d of grafik.keys()) {
if ((renk.get(d) ?? BEYAZ) === BEYAZ) dfs(d);
}
/* --- Rapor --- */
console.log("=".repeat(62));
console.log("PulseBoard — Sözleşme Testi");
console.log("=".repeat(62));
console.log(`Taranan modül sayısı : ${tumDosyalar.size}`);
console.log(`Kenar (import) sayısı: ${[...grafik.values()].reduce((t, l) => t + l.length, 0)}`);
console.log("");
for (const d of [...tumDosyalar].sort()) {
const b = grafik.get(d) || [];
console.log(` ${d} -> ${b.length ? b.join(", ") : "(yaprak)"}`);
}
if (uyarilar.length) {
console.log("\nUYARILAR:");
uyarilar.forEach((u) => console.log(" ⚠ " + u));
}
if (donguler.size) {
console.log("\nDÖNGÜSEL BAĞIMLILIK:");
[...donguler].forEach((d) => console.log(" ↻ " + d));
hatalar.push(`Döngüsel bağımlılık: ${[...donguler].join(" | ")}`);
}
console.log("\n" + "=".repeat(62));
if (hatalar.length === 0) {
console.log("SONUÇ: GEÇTİ — tüm import yolları ve isimli export'lar çözüldü.");
console.log("=".repeat(62));
process.exit(0);
} else {
console.log(`SONUÇ: KALDI — ${hatalar.length} hata`);
hatalar.forEach((h) => console.log(" ✗ " + h));
console.log("=".repeat(62));
process.exit(1);
}