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.
451 lines
20 KiB
JavaScript
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);
|