import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; /* * Keine der drei Regeln wird stillschweigend abgeschwaecht. * * WAS DIESE DATEI FRUEHER PRUEFTE: Der Begruendungsblock in `eslint.config.mjs` * fuehrte je Regel eine Fallzahl und je Ursache eine Teilzahl, und diese Datei * rechnete beides gegeneinander nach. Das war richtig, solange es einen * geduldeten Rueckstand von 36 Warnungen gab. Den gibt es nicht mehr: Die * Regeln melden Fehler, jede beanstandete Zeile traegt ihren eigenen * `eslint-disable-next-line ... -- `, und `npm run lint` laeuft mit * `--max-warnings 0`. Zahlen, die nicht mehr dastehen, lassen sich nicht * nachrechnen - die betreffenden Faelle sind deshalb fort. * * WAS BLEIBT, UND ES IST DAS WICHTIGERE: die Frage, gegen die diese Datei * urspruenglich geschrieben wurde. Sie lautete: Wird eine der gefuehrten Regeln * stillschweigend abgeschaltet, waehrend der Text weiter so tut, als werde * gemessen? GEMESSEN war das damals moeglich - * `no-unnecessary-type-assertion` und `switch-exhaustiveness-check` liessen * sich abschalten, ohne dass ein einziger Testfall rot wurde. Diese Luecke * bleibt bestehen, wenn niemand darauf sieht; sie ist nur eine Stufe hoeher * gewandert: Aus "steht die Regel noch auf warn" wird "steht sie auf error". * * ABGRENZUNG zu `tests/tools/linterRueckstand.test.ts`: Der dortige Fall liest * die Einstufung an der Stelle, an der die Regel GESETZT wird. Diese Datei * fragt das Gegenteil - ob irgendwo sonst in der Datei eine Abschwaechung * steht. Ein spaeterer Block ueberschreibt einen frueheren, und genau eine * solche Abschwaechung gibt es begruendet: `no-unnecessary-condition` fuer * `tests/`. Jede weitere waere ein Rueckstand, den kein Text mehr nennt. */ const LINTERREGELWERK = readFileSync('eslint.config.mjs', 'utf8'); /** Die drei Regeln, die den Rueckstand ausmachten. */ const GEFUEHRTE_REGELN = [ 'no-unnecessary-condition', 'no-unnecessary-type-assertion', 'switch-exhaustiveness-check', ] as const; /** * Zu welchem Schweregrad ist eine Regel gesetzt - in der Reihenfolge der Datei? * * Erfasst beide Schreibweisen: `'regel': 'error'` und `'regel': ['error', {...}]`. * Die zweite kam mit `switch-exhaustiveness-check` hinzu; ein Muster, das nur * die erste kennt, faende die Regel gar nicht und liefe still ins Leere. */ function setzungen(regel: string): string[] { const treffer = LINTERREGELWERK.matchAll( new RegExp(`'@typescript-eslint/${regel}':\\s*\\[?\\s*'(\\w+)'`, 'g'), ); return [...treffer].map((t) => String(t[1])); } describe('eslint.config.mjs - keine der drei Regeln ist stillschweigend abgeschwaecht', () => { it.each(GEFUEHRTE_REGELN)('ist %s ueberhaupt gesetzt', (regel) => { // Ohne diesen Fall waeren die beiden darunter leer und damit gruen, ohne // etwas zu pruefen - die Falle, in die eine Wache dieses Baums schon // einmal gelaufen ist. expect(setzungen(regel).length, `Setzungen von ${regel}`).toBeGreaterThan(0); }); it.each(GEFUEHRTE_REGELN)('meldet %s einen Fehler, nirgends nur eine Warnung', (regel) => { expect(setzungen(regel), `Setzungen von ${regel}`).toContain('error'); expect(setzungen(regel), `${regel} steht irgendwo wieder auf 'warn'`).not.toContain('warn'); }); /* * 'off' ist fuer genau eine Regel und genau einen Block vorgesehen: * no-unnecessary-condition in tests/. Jede weitere Abschaltung waere ein * Rueckstand, den kein Text mehr nennt - und den seit dem Wegfall der * Fallzahlen auch keine Aufstellung mehr verraten wuerde. */ it.each(GEFUEHRTE_REGELN)('schaltet %s nicht ausserhalb des einen Blocks ab', (regel) => { const aus = setzungen(regel).filter((s) => s === 'off'); const erlaubt = regel === 'no-unnecessary-condition' ? 1 : 0; expect(aus.length, `Abschaltungen von ${regel}`).toBe(erlaubt); }); it('haelt die eine erlaubte Abschaltung im Block fuer tests/', () => { /* * Von hinten gesucht, also dort, wo der Ausnahmeblock steht: Eine * Abschaltung weiter oben wuerde bei `indexOf` den falschen Block treffen, * und der Fall pruefte dann eine Stelle, die er gar nicht meint. */ const aus = LINTERREGELWERK.lastIndexOf("'@typescript-eslint/no-unnecessary-condition': 'off'"); expect(aus, 'Ausnahmeblock nicht gefunden').toBeGreaterThan(0); const block = LINTERREGELWERK.slice(LINTERREGELWERK.lastIndexOf('files:', aus), aus); expect(block, 'Ausnahme gilt fuer tests/').toContain('tests/**/*.ts'); expect(block, 'Ausnahme nimmt nichts weiter mit').not.toContain('src/'); expect(block, 'Ausnahme nimmt nichts weiter mit').not.toContain('electron/'); expect(block, 'Ausnahme nimmt e2e/ mit - der Rauchtest gehoert zum Programm').not.toContain( 'e2e', ); }); /* * Und die Vollstaendigkeitspruefung: Ihre vier Faelle sind nicht ueber * Ausnahmevermerke erledigt, sondern ueber eine Einstellung - ein * default-Zweig macht den Schalter vollstaendig. Faellt die Einstellung weg, * kehren vier Fehler zurueck; wird sie durch `allowDefaultCaseForExhaustive` * o. ae. ersetzt, faengt die Regel weniger, ohne dass es auffiele. */ it('erklaert den default-Zweig fuer vollstaendig, statt vier Vermerke zu setzen', () => { const einzeilig = LINTERREGELWERK.replace(/\s+/g, ' '); expect(einzeilig, 'die Einstellung fehlt').toContain( 'considerDefaultExhaustiveForUnions: true', ); }); });