Files
dogfather-universe/server/pruef-meldungen.mjs
T
DogFatherGit 04cfb06d33 Meldungen: eine Tabelle, 27 fehlende Saetze, und die Pruefung sagt jetzt wo
pruef-meldungen war rot, und zwar schon vor der Arbeit dieser Woche
(mit einer zweiten Arbeitskopie auf HEAD belegt). Gemeldet waren "27
Kennungen ohne deutschen Satz" und drei Seiten, die eine Kennung roh
anzeigen. Nachgemessen war die Lage anders -- und das ist der Kern:

  18 Kennungen hatten NIRGENDS einen Satz. Wer sie traf, las den
     Ersatzsatz "Das hat nicht geklappt" und erfuhr damit nicht, dass
     die Karte entfernt wurde oder dass es zu einer Kachel nur eine
     geben kann.

   9 hatten einen Satz -- aber nur in einer privaten Tabelle in
     manager-ziele.js. Ueber ihr stand als Begruendung woertlich: "An
     EINER Stelle: Stuende jeder dort, wo er gebraucht wird, hiesse
     dieselbe Absage an zwei Stellen verschieden." Der Satz war
     richtig; nur war diese Tabelle selbst inzwischen die zweite
     Stelle. Drei Kennungen standen in BEIDEN mit verschiedenem
     Wortlaut.

ZWEI DER DREI ROHANZEIGEN WAREN FEHLALARME: manager-ziele.js:888 und
:1322 riefen satzZu(d.fehler) -- eine echte Uebersetzung, die die
Pruefung nur nicht kannte. Echt war chat.js:3542 mit
`Ging nicht: ${d.fehler}`.

Gemacht:
  - 27 Saetze nach meldung.js (157 -> 184). Der Wortlaut der neun ist
    aus manager-ziele.js uebernommen, nicht neu erfunden.
  - Zwei Haussaetze seitenneutral gemacht: name_fehlt sprach von einer
    "Karte", nicht_deins von einem "Stueck" -- beide Kennungen kommen
    gemessen von Seiten, die weder Karte noch Stueck haben. Ohne das
    waere das Aufloesen der Zweittabelle eine VERSCHLECHTERUNG gewesen.
  - FEHLERTEXT und satzZu in manager-ziele.js weg, 8 Aufrufe auf
    sagWas umgestellt (gleiche Form: sagWas(fehler, ersatz)).
    manager-ziele.html laedt meldung.js jetzt vor der Seite.
  - chat.js zeigt einen Satz statt der Kennung.

An der Pruefung:
  - PRUEFUNG 4 (neu): Nur die Haustabelle uebersetzt Serverkennungen.
    Ausnahmen werden benannt, mit Gegenwache, dass jede noch existiert.
    Der Sucher ist eng gefasst, weil ein weicherer vier Treffer meldete
    und zwei davon Fehlalarme waren (`leer:` in den Spaltenlisten von
    aufgaben.js und chat.js) sowie einer kein Fehler war (reaktion.js
    uebersetzt schreib_grund in einen Platzhaltertext, nicht fehler:).
  - PRUEFUNG 1 nennt jetzt die Zweitstelle. Ohne das schickt der Befund
    in die falsche Richtung -- wer "27 ohne Satz" liest, schreibt 27
    neue und hat danach drei Tabellen.
  - Die Kommentar-Entfernung steht auf Modulebene, mit Selbsttest und
    drittem Ausgang: raeumt sie alles weg, ist jede Suche darueber
    gruen.

10 Pruefungen, 0 Fehler (vorher 8 mit 2 Fehlschlaegen) -- die Zahl ist
gestiegen, nichts ist stillschweigend weggefallen.

SECHS GEGENPROBEN, alle sechs schlagen an. Drei waren im ersten Lauf
falsch gebaut (CRLF-Anker, Variable statt Schluessel umbenannt, eine
Sabotage die nichts tat) -- das ist jeweils als Begruendung vermerkt,
weil eine blinde Sabotage genauso aussieht wie eine blinde Pruefung.
Gegenprobe 1 hat dabei einen ECHTEN Fehler in meinem neuen Pruefcode
gefunden: woSonst() griff auf `skripte` zu, das erst spaeter angelegt
wird. Im gruenen Zustand laeuft diese Zeile nie -- der Absturz waere
genau dann gekommen, wenn ein Satz fehlt, also wenn man die Meldung am
noetigsten braucht.

Geprueft: meldungen 10, manager-ziele 229, struktur 102, chat 80,
chat-optik 84, chat-anhaenge 168, workspace-seiten 37, formulare 23,
entwicklung 79, xlsx 75, lesbarkeit 14, zeichen 8, deutsche-texte 12 --
alle gruen, keine Zahl gesunken.
2026-10-08 21:15:36 +02:00

451 lines
20 KiB
JavaScript

/* =====================================================================
pruef-meldungen.mjs — Kein Benutzer liest je eine Maschinenkennung.
Filipe, 19.09.2026: „keine kryptischen oder technischen
Fehlermeldungen, sondern klare Aussagen darüber, was passiert ist
und wie man das Problem lösen kann."
---------------------------------------------------------------------
WARUM DIESE PRÜFUNG DIE EIGENTLICHE ARBEIT IST
Die Übersetzungstabelle in `workspace/assets/js/meldung.js` zu
schreiben war eine Stunde Arbeit. Sie in einem halben Jahr noch
vollständig zu haben, ist die schwierigere Aufgabe — und zwar aus
einem Grund, den dieses Haus teuer gelernt hat:
„Eine abgeschriebene Liste ist dieselbe Falle wie eine feste Zahl."
(11.09.2026, als die Umstellung drei Spalten verlor, obwohl ein
Kommentar direkt daneben davor warnte.)
Wer morgen `fehler: "zu_frueh"` erfindet, wird nicht daran denken,
hier einen Satz nachzutragen. Er merkt es auch nicht: Die Oberfläche
zeigt dann klaglos den Ersatzsatz. Es sieht aus, als funktioniere es.
Deshalb rechnet diese Prüfung die Liste der Kennungen AUS DEM SERVER
aus, statt sie zu kennen. Eine neue Kennung ohne Satz macht sie rot.
Man KANN es nicht mehr vergessen.
---------------------------------------------------------------------
DREI DINGE WERDEN GEPRÜFT
1. VOLLSTÄNDIG — jede Kennung, die der Server schicken kann, hat
einen Satz.
2. ANGESCHLOSSEN — keine Seite zeigt `.fehler` noch roh an, und
jede Seite, deren Skript `sagWas` benutzt, lädt auch
`meldung.js`. (Der zweite Teil ist der wichtigere: Ohne die
Datei wäre `sagWas` nicht definiert, und aus einer Fehlermeldung
würde ein Absturz — schlimmer als vorher.)
3. LESBAR — kein Satz enthält eine Kennung, eine nackte Zahl oder
ein englisches Wort.
Dazu drei GEGENPROBEN. Eine Prüfung, die immer bestätigt, bestätigt
nichts (01.09.2026): Jede der drei muss beweisen, dass sie auch
„nicht in Ordnung" sagen kann.
AUFRUF: node server/pruef-meldungen.mjs
Läuft in unter einer Sekunde, ohne Server, ohne Browser.
===================================================================== */
import { readFileSync, readdirSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const HIER = dirname(fileURLToPath(import.meta.url));
const WURZEL = join(HIER, "..");
const JS = join(WURZEL, "workspace", "assets", "js");
const SEITEN = join(WURZEL, "workspace");
/* ---------------------------------------------------------------------
Quelltext ohne Kommentare — an EINER Stelle.
WARUM DAS SEIN MUSS: Eine Suche über eine Datei findet Wörter auch
dort, wo sie nur ERWÄHNT werden. In diesem Haus stehen in vielen
Dateien mehr Kommentarzeilen als Anweisungen — am 24.09.2026
meldete diese Prüfung einen längst behobenen Fehler weiter, weil
der Kommentar daneben die alte Zeile zitierte.
Zeichenketten mit // darin (Adressen) bleiben unberührt:
geschnitten wird nur, wo kein Doppelpunkt und kein
Anführungszeichen davorsteht.
------------------------------------------------------------------- */
const ohneKommentareJs = (roh) => roh
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:"'\\])\/\/[^\n]*/gm, "$1");
/* GEGENPROBE AN DER ENTFERNUNG SELBST. Räumt sie zu viel weg, ist
jede Suche darüber grün — über einem leeren Text findet niemand
etwas. Zwei Proben: ein Kommentar muss verschwinden, Quelltext
und eine Adresse müssen stehen bleiben. */
{
const probe = ohneKommentareJs(
'const a = 1; /* fehler: "weg_damit" */\n'
+ 'const b = "https://x.test/y"; // fehler: "auch_weg"\n'
+ 'res.json({ fehler: "bleibt_da" });');
const gut = !probe.includes("weg_damit") && !probe.includes("auch_weg")
&& probe.includes("bleibt_da") && probe.includes("https://x.test/y");
if (!gut) {
console.log(" KONNTE NICHT NACHSEHEN — die Kommentar-Entfernung"
+ " arbeitet nicht wie erwartet. Ohne sie wäre jede Suche"
+ " darüber wertlos. Abbruch.");
console.log(" Herausgekommen ist:\n" + probe + "\n");
process.exit(2);
}
}
let ok = 0, fehl = 0;
const pruef = (name, bedingung, zusatz = "") => {
if (bedingung) { ok++; console.log(` OK ${name}`); }
else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); }
};
/* ---------------------------------------------------------------------
Die Tabelle aus meldung.js lesen — ohne einen Browser zu bauen.
`meldung.js` hängt sich an `window`. Statt eine halbe Browserwelt
nachzustellen, wird der Block zwischen `var SAETZE = {` und der
schließenden Klammer ausgeschnitten und als JavaScript ausgewertet.
Das ist ehrlicher als ein zweiter Auszug der Namen: Es ist dieselbe
Quelle, kein abgeschriebener Zwilling.
------------------------------------------------------------------- */
function saetzeLesen() {
const quelle = readFileSync(join(JS, "meldung.js"), "utf8");
const start = quelle.indexOf("var SAETZE = {");
if (start < 0) return null;
const auf = quelle.indexOf("{", start);
let tiefe = 0, ende = -1;
for (let i = auf; i < quelle.length; i++) {
if (quelle[i] === "{") tiefe++;
else if (quelle[i] === "}") { tiefe--; if (tiefe === 0) { ende = i; break; } }
}
if (ende < 0) return null;
// eslint-disable-next-line no-new-func
return Function(`"use strict"; return (${quelle.slice(auf, ende + 1)});`)();
}
/* ---------------------------------------------------------------------
Alle Kennungen, die der Server schicken kann.
ABGELEITET, NICHT ABGESCHRIEBEN. Gesucht wird `fehler: "…"` in allen
Server-Dateien; als Kennung gilt, was nur aus Kleinbuchstaben,
Ziffern und Unterstrichen besteht. Alles andere ist ein fertiger
Satz und braucht keine Übersetzung.
------------------------------------------------------------------- */
function kennungenAusServer() {
const gefunden = new Map();
for (const datei of readdirSync(HIER)) {
if (!datei.endsWith(".js")) continue;
if (datei.startsWith("pruef-") || datei.startsWith("test-")) continue;
/* KOMMENTARE ZAEHLEN NICHT ALS KENNUNG (24.09.2026).
Gefunden an einer Stelle, die genau das dokumentierte, was
diese Pruefung verlangt: In workspace-support.js stand
fehler: "ungueltiger_stand" -- behoben, und der Kommentar
daneben zitierte die alte Zeile als Begruendung. Die Pruefung
las das Zitat und meldete den Fehler weiter, obwohl er behoben
war.
Dieselbe Falle wie ein Grep ueber eine Datei, die ihre eigene
Geschichte enthaelt -- mir ist sie heute schon einmal
passiert. Eine Pruefung, die verbietet, ueber einen behobenen
Fehler zu SCHREIBEN, erzieht dazu, die Begruendung
wegzulassen.
Zeichenketten mit // darin (Adressen) bleiben unberuehrt:
geschnitten wird nur, wo kein Doppelpunkt und kein
Anfuehrungszeichen davorsteht. */
const text = ohneKommentareJs(readFileSync(join(HIER, datei), "utf8"));
for (const m of text.matchAll(/fehler:\s*"([a-z0-9_]+)"/g)) {
if (!gefunden.has(m[1])) gefunden.set(m[1], datei);
}
}
return gefunden;
}
console.log("\n=== Meldungen: sagt das Haus ganze Sätze? ===\n");
const SAETZE = saetzeLesen();
/* DER DRITTE AUSGANG. Lässt sich die Tabelle nicht lesen, ist das
weder „in Ordnung" noch „ein Satz fehlt" — es heißt, die Prüfung
kann ihre Frage nicht beantworten. Dann bricht sie ab, statt grün
zu melden. Genau dieser Fall hat am 03.09. wochenlang eine
Pflichtprüfung wertlos gemacht. */
if (!SAETZE) {
console.log(" KONNTE NICHT NACHSEHEN — in meldung.js ist kein Block `var SAETZE = {` zu finden.");
console.log(" Grund: entweder wurde die Datei umgebaut oder sie fehlt.");
console.log(" Ohne sie lässt sich nicht sagen, ob Sätze fehlen. Abbruch.\n");
process.exit(2);
}
const kennungen = kennungenAusServer();
console.log(` (${kennungen.size} Kennungen im Server, ${Object.keys(SAETZE).length} Sätze in der Tabelle)\n`);
/* Die Browserdateien. SIE MUSS HIER STEHEN, nicht erst in Abschnitt 2:
Prüfung 1 braucht sie über `woSonst`, und eine `const` weiter unten
ist zu diesem Zeitpunkt noch nicht zugänglich. Gefunden hat das die
Gegenprobe, nicht der grüne Lauf — im grünen Zustand wird `woSonst`
nie gerufen, und der Absturz wäre genau dann gekommen, wenn ein
Satz fehlt, also wenn man die Meldung am nötigsten braucht. */
const skripte = readdirSync(JS).filter((f) => f.endsWith(".js"));
/* ---- 1. Vollständig ------------------------------------------------- */
const ohneSatz = [...kennungen.keys()].filter((k) => !SAETZE[k]);
/* ---------------------------------------------------------------------
WO STEHT DER SATZ SONST? (08.10.2026)
Ohne diese Zeilen meldete die Prüfung „27 Kennungen ohne Satz" —
und schickte in die falsche Richtung: Neun hatten sehr wohl einen
Satz, nur in einer privaten Tabelle in `manager-ziele.js`. Wer das
liest, schreibt 27 neue Sätze und hat danach DREI Tabellen.
Ein Befund, der die halbe Wahrheit sagt, ist teurer als einer, der
nichts sagt.
------------------------------------------------------------------- */
const NAMENSZEICHEN = "abcdefghijklmnopqrstuvwxyz0123456789_$";
const woSonst = (k) => {
for (const f of skripte) {
if (f === "meldung.js") continue;
const text = ohneKommentareJs(readFileSync(join(JS, f), "utf8"));
const flach = text.split(" ").join("").split("\t").join("");
for (const auf of ["'", '"', "`"]) {
const nadel = k + ":" + auf;
let i = -1;
while ((i = flach.indexOf(nadel, i + 1)) !== -1) {
/* ECHTE WORTGRENZE. Ohne sie trifft `unbekannt` mitten in
`buddy_unbekannt` — beim Messen am 08.10.2026 genau so
passiert, und der Befund sah richtig aus. */
const davor = i === 0 ? "" : flach[i - 1].toLowerCase();
if (!NAMENSZEICHEN.includes(davor)) return f;
}
}
}
return null;
};
pruef("Jede Server-Kennung hat einen Satz",
kennungen.size > 0 && ohneSatz.length === 0,
ohneSatz.length
? `ohne Satz: ${ohneSatz.map((k) => {
const sonst = woSonst(k);
return `${k} (${kennungen.get(k)}`
+ (sonst ? ` — ein Satz steht in ${sonst}, er gehört nach meldung.js` : "")
+ ")";
}).join(", ")}`
: "");
/* Die Zahl gehört in die BEDINGUNG, nicht nur in den Meldetext.
`every()` auf einem leeren Feld liefert `true` — eine Prüfung, die
nichts gefunden hat, wäre sonst grün (01.09.2026, Shop). */
pruef("Es wurden überhaupt Kennungen gefunden", kennungen.size >= 30,
`nur ${kennungen.size} — findet der Suchausdruck noch, was er soll?`);
/* ---- 2. Angeschlossen ----------------------------------------------- */
/* 2a: Keine Rohanzeige mehr. Gesucht wird `.fehler` in derselben Zeile
wie eine Ausgabe — aber nur dort, wo KEIN `sagWas` und kein
Vergleich (`===`) steht. Ein Vergleich übersetzt selbst. */
const roh = [];
for (const f of skripte) {
if (f === "meldung.js") continue;
const zeilen = readFileSync(join(JS, f), "utf8").split("\n");
zeilen.forEach((z, i) => {
if (!/\.fehler/.test(z)) return;
if (!/(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)) return;
if (/sagWas/.test(z) || /\.fehler\s*===/.test(z) || /\[\s*\w+\.fehler\s*\]/.test(z)) return;
roh.push(`${f}:${i + 1}`);
});
}
pruef("Keine Seite zeigt eine Kennung roh an", roh.length === 0,
roh.length ? roh.join(", ") : "");
/* 2b: Wer sagWas benutzt, muss meldung.js laden. DAS IST DIE
WICHTIGERE HÄLFTE: Fehlt die Datei, ist `sagWas` nicht definiert,
und aus einer Fehlermeldung wird ein Absturz. */
const benutzt = new Set(skripte.filter(
(f) => f !== "meldung.js" && /\bsagWas\(/.test(readFileSync(join(JS, f), "utf8"))));
const luecken = [];
for (const seite of readdirSync(SEITEN).filter((f) => f.endsWith(".html"))) {
const html = readFileSync(join(SEITEN, seite), "utf8");
const geladen = [...html.matchAll(/src="assets\/js\/([a-z0-9-]+)\.js/g)].map((m) => m[1] + ".js");
const braucht = geladen.filter((g) => benutzt.has(g));
if (braucht.length && !geladen.includes("meldung.js")) {
luecken.push(`${seite} (braucht es wegen ${braucht.join(", ")})`);
}
}
pruef("Jede Seite, die sagWas braucht, lädt meldung.js",
benutzt.size > 0 && luecken.length === 0,
luecken.length ? luecken.join("; ") : (benutzt.size ? "" : "keine Datei benutzt sagWas — stimmt das?"));
/* ---------------------------------------------------------------------
Jedes Objektliteral einer Datei — über Klammerzählung.
NICHT ÜBERSPRINGEN, wenn eines gefunden ist: Der erste Treffer in
einer Browserdatei ist der ganze Rumpf der Funktion, in der alles
steckt. Wer danach weiterspringt, sieht nie eine Tabelle IM Rumpf.
Gemessen am 08.10.2026: mit Übersprung 0 Treffer, obwohl zwei
Tabellen dastanden. Der Rumpf selbst fällt unten durch, weil er
Klammern enthält.
------------------------------------------------------------------- */
function literale(text) {
const raus = [];
for (let i = 0; i < text.length; i++) {
if (text[i] !== "{") continue;
let tiefe = 0, ende = -1;
for (let j = i; j < text.length; j++) {
if (text[j] === "{") tiefe++;
else if (text[j] === "}") { tiefe--; if (tiefe === 0) { ende = j; break; } }
}
if (ende >= 0) raus.push(text.slice(i, ende + 1));
}
return raus;
}
/* ---------------------------------------------------------------------
Ist dieses Literal eine reine Zuordnung Kennung → Satz?
WARUM DIE REGEL SO ENG IST. Ein weicherer Sucher („irgendwo steht
ein Name mit einem Satz dahinter") meldete am 08.10.2026 vier
Treffer, von denen zwei Fehlalarme waren: `leer:` in den
Spaltenlisten von `aufgaben.js` und `chat.js` — Beschriftungen
einer leeren Spalte, zufällig mit einem Namen, den es auch als
Serverkennung gibt. Ein dritter Treffer, `reaktion.js`, war
ebenfalls keiner: Dort steht kein Satz zu einer Absage, sondern
ein Platzhaltertext für das Eingabefeld, und sein Schlüssel kommt
nicht aus `fehler:`, sondern aus `schreib_grund`.
Verlangt wird deshalb: jeder Wert ist eine Zeichenkette, im
Literal steht nichts anderes, und JEDER Schlüssel ist eine
Serverkennung. Damit fällt eine Satzliste auf und ein Datensatz
nicht. Nachgemessen über alle Browserdateien: genau ein Treffer.
------------------------------------------------------------------- */
function nurSaetze(lit) {
const inhalt = lit.slice(1, -1);
if (/[{}()]/.test(inhalt)) return null;
/* Alle drei Anführungsarten — sonst ist die Prüfung blind, sobald
jemand doppelte statt einfacher benutzt. */
const found = [...inhalt.matchAll(
/([a-z][a-z0-9_]*)\s*:\s*(?:'([^']*)'|"([^"]*)"|`([^`]*)`)/g)]
.map((m) => [m[1], m[2] ?? m[3] ?? m[4]]);
if (!found.length) return null;
/* Die Zahl der Schlüsselzeilen muss zur Zahl der Paare passen —
sonst steht im Literal noch etwas, das kein Satz ist. */
const zeilen = (inhalt.match(/^\s*[a-z][a-z0-9_]*\s*:/gm) || []).length;
if (zeilen !== found.length) return null;
return new Map(found);
}
/** Wo außerhalb von meldung.js steht eine Satztabelle für Kennungen? */
function zweittabellen() {
const raus = [];
for (const f of skripte) {
if (f === "meldung.js") continue;
const text = ohneKommentareJs(readFileSync(join(JS, f), "utf8"));
for (const lit of literale(text)) {
const m = nurSaetze(lit);
if (!m || m.size < 2) continue;
if (![...m.keys()].every((k) => kennungen.has(k))) continue;
raus.push({ datei: f, keys: [...m.keys()] });
}
}
return raus;
}
/* ---- 4. EINE Tabelle, nicht zwei ------------------------------------
DER FALL, DER DAZU GEFÜHRT HAT (08.10.2026). `manager-ziele.js`
führte eine eigene Tabelle mit zwölf Sätzen. Darüber stand als
Begründung wörtlich: „An EINER Stelle: Stünde jeder dort, wo er
gebraucht wird, hieße dieselbe Absage an zwei Stellen
verschieden." Der Satz war richtig — nur war diese Tabelle selbst
inzwischen die zweite Stelle.
GEMESSEN, was das gekostet hat: Neun Kennungen hatten ihren Satz
NUR dort; auf jeder anderen Seite, die dieselbe Absage bekam,
erschien der Ersatzsatz. Drei standen in BEIDEN Tabellen mit
verschiedenem Wortlaut. Und zwei Sätze der Haustabelle waren für
eine fremde Seite geschrieben („Karte", „Stück") — sie wären
beim Auflösen auf einer Seite erschienen, auf der es weder Karte
noch Stück gibt. Die Pflicht ist deshalb doppelt: EINE Tabelle,
und jeder Satz darin muss auf jeder Seite wahr sein, die ihn
auslösen kann.
AUSNAHMEN WERDEN BENANNT, nicht stillschweigend zugelassen — und
die Gegenwache darunter prüft, dass jede Ausnahme noch existiert.
Eine Ausnahmeliste, die niemand pflegt, ist dieselbe Falle wie
eine abgeschriebene Liste (11.09.2026).
------------------------------------------------------------------- */
const ERLAUBT = new Map([
["entwicklung.js", {
keys: ["kein_hakt", "schon_da"],
grund: "zwei Absagen am Schritt-Knopf, absichtlich knapper und auf "
+ "die Handlung bezogen; die Haustabelle sagt dasselbe allgemein",
}],
]);
const zweite = zweittabellen();
const unerlaubt = zweite.filter((z) => {
const e = ERLAUBT.get(z.datei);
return !e || z.keys.some((k) => !e.keys.includes(k));
});
pruef("Nur die Haustabelle übersetzt Server-Kennungen", unerlaubt.length === 0,
unerlaubt.length
? unerlaubt.map((z) => `${z.datei} führt eigene Sätze für `
+ `${z.keys.join(", ")} — sie gehören nach meldung.js`).join("; ")
: "");
/* Gegenwache zur Ausnahmeliste: Jede benannte Ausnahme muss es noch
geben. Sonst bleibt ein Freibrief stehen, nachdem die Stelle längst
aufgeräumt ist — und deckt beim nächsten Mal etwas ab, das niemand
entschieden hat. */
const toteAusnahmen = [...ERLAUBT.keys()].filter(
(f) => !zweite.some((z) => z.datei === f));
pruef("Jede benannte Ausnahme gibt es wirklich noch",
ERLAUBT.size > 0 && toteAusnahmen.length === 0,
toteAusnahmen.length ? `nicht mehr vorhanden: ${toteAusnahmen.join(", ")}` : "");
for (const [f, e] of ERLAUBT) {
/* Eine Ausnahme, die niemand liest, wird zur Gewohnheit. */
console.log(` (erlaubt: ${f} für ${e.keys.join(", ")} — ${e.grund})`);
}
/* ---- 3. Lesbar ------------------------------------------------------ */
const schlecht = [];
for (const [k, satz] of Object.entries(SAETZE)) {
if (/[a-z]+_[a-z]+/.test(satz)) schlecht.push(`${k}: enthält eine Kennung`);
else if (/\b(?:400|401|403|404|409|429|500|503)\b/.test(satz)) schlecht.push(`${k}: enthält eine Fehlernummer`);
else if (/\b(error|failed|invalid|denied|request|server error)\b/i.test(satz)) schlecht.push(`${k}: englisches Wort`);
else if (satz.length < 15) schlecht.push(`${k}: zu kurz ("${satz}")`);
}
pruef("Jeder Satz ist deutsch, ohne Kennung und ohne Zahl",
Object.keys(SAETZE).length > 0 && schlecht.length === 0,
schlecht.length ? schlecht.join("; ") : "");
/* =====================================================================
DIE GEGENPROBEN
Zu jeder Prüfung, die „alles in Ordnung" sagen kann, gehört der
Beweis, dass sie auch „nicht in Ordnung" sagen kann. Sonst weiß
niemand, ob sie überhaupt hinsieht.
===================================================================== */
console.log("\n --- Gegenproben (jede MUSS anschlagen) ---");
const g1 = (() => { const t = { ...SAETZE }; delete t.nicht_verfuegbar;
return [...kennungen.keys()].filter((k) => !t[k]).length > 0; })();
pruef("Gegenprobe: fehlt ein Satz, schlägt Prüfung 1 an", g1);
const g2 = (() => {
const z = " $('x').textContent = d.fehler || 'Ging nicht.';";
return /\.fehler/.test(z)
&& /(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)
&& !/sagWas/.test(z) && !/\.fehler\s*===/.test(z);
})();
pruef("Gegenprobe: eine Rohanzeige würde Prüfung 2a finden", g2);
const g3 = (() => {
const s = "Fehler 503: request denied";
return /[a-z]+_[a-z]+/.test(s)
|| /\b(?:400|401|403|404|409|429|500|503)\b/.test(s)
|| /\b(error|failed|invalid|denied|request)\b/i.test(s);
})();
pruef("Gegenprobe: ein unlesbarer Satz würde Prüfung 3 finden", g3);
console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`);
process.exit(fehl ? 1 : 0);