Files
dogfather-universe/server/helfer-ohne-kommentar.mjs
T
DogFatherGitandClaude Opus 5 ea8ceaa539 Das Farbhaus von Team Dogi lag oeffentlich im Netz
Ausgeloest von pruef-modi-wortleck, die seit Tagen rot war: "24
Fundstellen -- der verborgene Zugang waere damit auffindbar." Die
erste Frage war, ob sie recht hat. Gemessen: ja, und schlimmer als
gedacht.

WER DIESE DATEIEN BEKOMMT: Nicht nur Kollegen. `curl` ohne jeden Keks
auf workspace.dogfather-universe.com liefert die Skripte und
Stilvorlagen des Arbeitsplatzes mit HTTP 200 -- fuer jeden im Netz
lesbar.

DARUNTER crew-haus.css, 27 806 Byte, das ganze Farbhaus von Team Dogi
samt seiner Kommentare. In deren Kopf stand seit dem 10.09. woertlich
"Diese Datei wird NUR auf crew.dogfather-universe.com ausgeliefert."
Das war eine Behauptung, keine Tatsache: Die Weiche biegt `haus.css`
auf diesen Namen um, wer den Namen direkt nannte, ging an ihr vorbei.
Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht.

Zwei Stellen im Haus haben sich damit widersprochen -- die Datei sagte
"nur auf crew.", pruef-crew-adresse verlangte ausdruecklich das
Gegenteil ("sie ist kein Geheimnis"). Durchgesetzt war die offene
Fassung. Filipe hat am 21.09. entschieden: zusperren. Die alte
Begruendung dagegen war die Sorge, eine Sperre koenne die Pruefung
blind machen -- die ist jetzt beantwortet statt vermieden: Jeder
Eintrag der Tafel wird in BEIDE Richtungen gemessen, und dass die
Umbiegung von `haus.css` auf crew. weiterhin greift, ebenfalls.

DIE ADRESSE WAR DAS EIGENTLICHE GEHEIMNIS, nicht das Wort. Die
Rollennamen stehen auf der Zugangswand von Team Dogi ohnehin offen --
so gewollt. WO sie liegt, stand dagegen im Klartext in gate.css,
gate.js und crew-haus.css, alle drei oeffentlich abrufbar. Jetzt steht
sie in keiner ausgelieferten Datei mehr.

DIE PRUEFUNG SELBST HATTE DREI MAENGEL:

  1. Sie kannte die Mehrzahl nicht. `\bmodi\b` fand "ein Modi", nicht
     "die Modis" -- 23 weitere Stellen, darunter der Satz "Fuer die
     rechte Hand und die Modis gibt es dort den stillen Zugang". Also
     genau die Auskunft, die sie verhindern soll, in der Form, die sie
     nicht sah.
  2. Ihre Ausnahmeliste war abgeschrieben. Sie nannte eine Datei; die
     zweite, die der Server auf crew. begrenzt, haette sie nie
     erfahren. Ausgenommen ist jetzt genau das, was der Server auch
     durchsetzt (GEHOERT_ZU_ADRESSE) -- kein Muster, sondern die
     Durchsetzung selbst.
  3. Sie mahnte 45 Kommentare an. Filipes Entscheidung: in Kommentaren
     ja, in Code und sichtbaren Texten nein. Rot, das immer da ist,
     wird ueberlesen -- und dann faengt sie auch den echten Fall nicht.

Dafuer neu: helfer-ohne-kommentar.mjs, ein Zerleger, der Kommentare
zeichengenau durch Leerzeichen ersetzt, ohne Zeichenketten,
Vorlagen oder Suchmuster zu beschaedigen. KEIN Suchausdruck -- genau
daran ist am 20.09. die Portumstellung gescheitert, mit gueltigem
JavaScript ohne das Feld `port`. Dreizehn Proben in beide Richtungen
beweisen, dass er weder zu viel noch zu wenig wegnimmt; ein Zerleger,
der zu viel nimmt, macht die ganze Pruefung STILL gruen.

Von 45 blieben damit drei echte, alle behoben:
  - daten.modis  -> daten.leute (Feldname in der Auskunft, also Code)
  - zwei Saetze, die Menschen lesen, auf "die Moderation" umgestellt --
    fuer jemanden, der neu im Treff ist, sogar klarer

Nebenbei, beim selben Durchgang gefunden und geschlossen:
  - teamlage.js fiel auf einen Rollennamen zurueck. Nachgemessen: Die
    Stilvorlage kennt zu dieser Karte gar keine solche Regel -- der
    Rueckfall hat nie etwas bewirkt und nur das Wort ausgeliefert.
  - werdegang.js fuehrte die Saetze ueber den Rollengruppen selbst,
    mit den Rollennamen als Schluessel. Der Kommentar daneben
    behauptete sogar, sie seien "abgeleitet, nicht abgeschrieben".
    Sie kommen jetzt vom Server, und pruef-werdegang misst, dass
    jede Gruppe ihren eigenen behaelt.

pruef-modi-wortleck  8/0  (war 5, davon 1 rot)
pruef-crew-adresse 144/0  (war 135)
pruef-werdegang    98 statt 95 Pruefungen, dieselben 3 alten Fehler
pruef-team-ampel 32/0, pruef-team-stufen 28/0, pruef-hilfe 84/0

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-21 13:00:01 +02:00

250 lines
8.9 KiB
JavaScript

/* =====================================================================
helfer-ohne-kommentar.mjs — Kommentare entfernen, ohne den Rest zu
beschaedigen (21.09.2026)
---------------------------------------------------------------------
WOFUER
Filipes Entscheidung vom 21.09.2026 zum Rollennamen der Community:
Er darf nicht in CODE und nicht in SICHTBAREN TEXTEN stehen -- in
Kommentaren schon. Die Begruendung ist messbar: Auf der Zugangswand
von Team Dogi stehen die drei Rollennamen ohnehin offen ("3 rollen.
dogfather. rechte hand und modis"), und geschuetzt wird der Zugang
durch den Code, nicht durch ein Wort. Eine Pruefung, die 45
Kommentare anmahnt, waere eine Dauer-Rotmeldung -- und Rot, das
immer da ist, wird ueberlesen.
Damit diese Regel ueberhaupt pruefbar ist, muss jemand sagen
koennen, was Kommentar ist und was nicht. Genau das tut diese Datei.
---------------------------------------------------------------------
WARUM KEIN EINFACHER SUCHEN-UND-ERSETZEN-AUSDRUCK
Ein Muster, das einfach vom naechsten Sternchen-Schraegstrich bis
zum uebernaechsten loescht, sieht richtig aus und ist es nicht.
Es zerschneidet zum Beispiel eine Adresse, in der dieselbe
Zeichenfolge mitten in einer Zeichenkette steht, oder ein
Suchmuster, das Schraegstrich und Sternchen als Zeichen
enthaelt -- und macht aus gueltigem Programmtext Unsinn.
(Beide Beispiele stehen hier bewusst NICHT ausgeschrieben: Ein
Kommentar, der die Zeichenfolge enthaelt, mit der ein Kommentar
endet, beendet sich selbst. Beim ersten Versuch ist genau das
passiert -- in der Datei, die davor warnt.)
IM HAUS IST DIESE FALLE SCHON EINMAL ZUGESCHNAPPT: Am 20.09.2026 hat ein Ausdruck beim
Umstellen der Portnummern `", port: 4231, path: "` fuer eine
Zeichenkette gehalten. Das Ergebnis war gueltiges JavaScript ohne
das Feld `port` -- `node --check` sagte bei allen 164 Dateien "in
Ordnung", und aufgefallen ist es erst, weil ein Lauf gegen einen
GEMESSENEN Vorstand verglichen wurde.
Deshalb wird hier Zeichen fuer Zeichen gelesen und mitgefuehrt, wo
man gerade steht: in Programmtext, in einer Zeichenkette, in einer
Vorlage, in einem Suchmuster oder in einem Kommentar.
---------------------------------------------------------------------
DAS SCHWIERIGE STUECK: IST `/` EIN TEILEN ODER EIN SUCHMUSTER
`a / b` teilt, `/a/.test(x)` sucht. Unterscheiden kann man das nur
am ZEICHEN DAVOR: Nach einem Wert (Zahl, Name, schliessende Klammer)
teilt es; nach einem Operator, einem Komma oder einer oeffnenden
Klammer beginnt ein Suchmuster. Das ist die Regel, die auch
Browser benutzen, und sie ist fuer diesen Zweck genau genug.
WAS SIE NICHT KANN: `return /x/` gegen `a++ /x/` unterscheiden,
wenn davor ein Schluesselwort steht, das wie ein Name aussieht.
Deshalb gelten `return`, `typeof`, `case`, `in`, `of`, `new`,
`delete`, `void`, `do` und `else` ausdruecklich als Operator. Die
Liste steht unten und ist abgezaehlt, nicht geraten.
---------------------------------------------------------------------
WAS ZURUECKKOMMT
Derselbe Text, gleich lang, mit Leerzeichen an den Stellen, an denen
Kommentar stand. GLEICH LANG ist Absicht: So stimmen Zeilennummern
und Spalten weiterhin, und ein Fund laesst sich ohne Umrechnung in
der Originaldatei zeigen. Zeilenumbrueche bleiben deshalb erhalten.
===================================================================== */
/* Nach diesen Woertern beginnt ein `/` ein Suchmuster und kein Teilen.
Abgezaehlt, nicht geraten -- siehe Kopf. */
const VOR_SUCHMUSTER = new Set([
"return", "typeof", "case", "in", "of", "new", "delete", "void",
"do", "else", "yield", "await", "instanceof",
]);
/** Ersetzt jeden Kommentar in JavaScript durch Leerzeichen.
*
* Zeichenketten, Vorlagen und Suchmuster bleiben unangetastet --
* auch wenn `//` oder darin steht. */
export function jsOhneKommentar(quelle) {
const t = String(quelle);
let aus = "";
let i = 0;
/* Das letzte Zeichen, das kein Leerraum war. Daran haengt die
Entscheidung Teilen-oder-Suchmuster. */
let letztes = "";
let letztesWort = "";
const schreibe = (s) => { aus += s; };
const leeren = (s) => { aus += s.replace(/[^\n]/g, " "); };
while (i < t.length) {
const c = t[i];
const d = t[i + 1];
/* ---- Kommentare ------------------------------------------------ */
if (c === "/" && d === "/") {
let j = i;
while (j < t.length && t[j] !== "\n") j++;
leeren(t.slice(i, j));
i = j;
continue;
}
if (c === "/" && d === "*") {
let j = t.indexOf("*/", i + 2);
j = j === -1 ? t.length : j + 2;
leeren(t.slice(i, j));
i = j;
continue;
}
/* ---- Zeichenketten --------------------------------------------- */
if (c === '"' || c === "'" || c === "`") {
let j = i + 1;
while (j < t.length) {
if (t[j] === "\\") { j += 2; continue; }
if (t[j] === c) { j++; break; }
/* In einer Vorlage kann `${…}` wieder Programmtext sein. Fuer
den Zweck hier -- Kommentare finden -- reicht es, das Stueck
als Ganzes stehen zu lassen: Ein Kommentar darin waere
ohnehin Programmtext und soll nicht verschwinden. */
j++;
}
schreibe(t.slice(i, j));
letztes = c; letztesWort = "";
i = j;
continue;
}
/* ---- Suchmuster ------------------------------------------------ */
if (c === "/") {
const wertDavor = /[\w$)\]]/.test(letztes) && !VOR_SUCHMUSTER.has(letztesWort);
if (!wertDavor) {
let j = i + 1;
let inKlasse = false;
let fertig = false;
while (j < t.length) {
const z = t[j];
if (z === "\\") { j += 2; continue; }
if (z === "\n") break; /* unfertig -- dann war es Teilen */
if (z === "[") inKlasse = true;
else if (z === "]") inKlasse = false;
else if (z === "/" && !inKlasse) { j++; fertig = true; break; }
j++;
}
if (fertig) {
while (j < t.length && /[a-z]/.test(t[j])) j++; /* Flaggen */
schreibe(t.slice(i, j));
letztes = "/"; letztesWort = "";
i = j;
continue;
}
}
}
/* ---- alles andere ---------------------------------------------- */
schreibe(c);
if (!/\s/.test(c)) {
letztes = c;
if (/[\w$]/.test(c)) letztesWort += c;
else letztesWort = "";
}
i++;
}
return aus;
}
/** CSS kennt nur die Block-Form und Zeichenketten. */
export function cssOhneKommentar(quelle) {
const t = String(quelle);
let aus = "";
let i = 0;
while (i < t.length) {
const c = t[i];
if (c === "/" && t[i + 1] === "*") {
let j = t.indexOf("*/", i + 2);
j = j === -1 ? t.length : j + 2;
aus += t.slice(i, j).replace(/[^\n]/g, " ");
i = j;
continue;
}
if (c === '"' || c === "'") {
let j = i + 1;
while (j < t.length) {
if (t[j] === "\\") { j += 2; continue; }
if (t[j] === c) { j++; break; }
j++;
}
aus += t.slice(i, j);
i = j;
continue;
}
aus += c;
i++;
}
return aus;
}
/** HTML: <!-- … --> heraus, und die eingebetteten <script>/<style>
* bekommen ihre eigene Behandlung -- sonst bliebe ein Kommentar
* mitten im Skript stehen. */
export function htmlOhneKommentar(quelle) {
const t = String(quelle);
let aus = "";
let i = 0;
while (i < t.length) {
if (t.startsWith("<!--", i)) {
let j = t.indexOf("-->", i + 4);
j = j === -1 ? t.length : j + 3;
aus += t.slice(i, j).replace(/[^\n]/g, " ");
i = j;
continue;
}
const skript = /^<script\b[^>]*>/i.exec(t.slice(i, i + 200));
if (skript) {
const start = i + skript[0].length;
const ende = t.toLowerCase().indexOf("</script", start);
const bis = ende === -1 ? t.length : ende;
aus += skript[0] + jsOhneKommentar(t.slice(start, bis));
i = bis;
continue;
}
const stil = /^<style\b[^>]*>/i.exec(t.slice(i, i + 200));
if (stil) {
const start = i + stil[0].length;
const ende = t.toLowerCase().indexOf("</style", start);
const bis = ende === -1 ? t.length : ende;
aus += stil[0] + cssOhneKommentar(t.slice(start, bis));
i = bis;
continue;
}
aus += t[i];
i++;
}
return aus;
}
/** Waehlt anhand der Endung. Unbekannte Endungen kommen UNVERAENDERT
* zurueck -- lieber eine Fundstelle zu viel melden als eine zu
* wenig. Wer hier stillschweigend alles durchliesse, haette eine
* Pruefung gebaut, die bei jeder neuen Dateiart blind wird. */
export function ohneKommentar(text, endung) {
const e = String(endung || "").toLowerCase();
if (e === ".js" || e === ".mjs") return jsOhneKommentar(text);
if (e === ".css") return cssOhneKommentar(text);
if (e === ".html" || e === ".htm") return htmlOhneKommentar(text);
return String(text);
}