import { readFileSync, readdirSync } from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; /* * Der Linter duldet keinen Rueckstand mehr. * * WAS VORHER WAR: 36 Warnungen standen im Baum, Fall fuer Fall durchgesehen und * als beabsichtigt bestaetigt, und die Zahl stand als offener Punkt vermerkt. * Das las sich wie Ordnung, hatte aber eine Luecke, die man erst spaet bemerkt: * Der siebenunddreissigste Fall faellt niemandem auf. Er wird eine Warnung * unter sechsunddreissig, das Gate bleibt gruen, und die Durchsicht muesste * jemand von sich aus wiederholen. * * WAS JETZT GILT: Die beiden Regeln melden FEHLER. Die 28 Zeilen, die sie * beanstandeten, tragen ihren eigenen Ausnahmevermerk samt Grund - der * ausfuehrliche Kommentar stand ohnehin schon darueber. Der Rueckstand ist * damit nicht verschwunden, sondern an die Stellen gewandert, an denen er * hingehoert. * * WOGEGEN DIESER FALL GESCHRIEBEN IST: Diese Umstellung laesst sich in einer * Minute rueckgaengig machen, ohne dass ein Test rot wird - `'error'` zu * `'warn'`, und der Rueckstand ist zurueck, nur diesmal ohne dass jemand * mitzaehlt. Drei Zeilen tragen die Umstellung, und dieser Fall haelt alle * drei fest: * * 1. Das Skript `lint` laeuft mit `--max-warnings 0`. Ohne das waere eine * Warnung wieder folgenlos, egal was in der Konfiguration steht. * 2. Die beiden Regeln stehen auf `'error'`. * 3. `reportUnusedDisableDirectives` steht auf `'error'`. Das ist die * wichtigste der drei: Ohne sie waeren 28 Ausnahmevermerke nur eine * leisere Art, denselben Rueckstand zu fuehren - sie blieben stehen, wenn * die Stelle umgeschrieben wird oder TypeScript eines Tages besser * verengt. * * Was dieser Fall NICHT tut: den Linter fahren. Das tut `gate:schnell` bei * jedem Commit, und zwar mit den Einstellungen, die hier geprueft werden. Eine * zweite Ausfuehrung hier waere dieselbe Messung ein zweites Mal - und * langsamer. */ const REGELWERK = readFileSync('eslint.config.mjs', 'utf8'); const PAKET = JSON.parse(readFileSync('package.json', 'utf8')) as { scripts: Record; }; /** Die Regeln, die den Rueckstand ausmachten. */ const RUECKSTANDSREGELN = [ '@typescript-eslint/no-unnecessary-condition', '@typescript-eslint/no-unnecessary-type-assertion', ] as const; describe('Der Linter duldet keinen Rueckstand', () => { it('laesst das Skript "lint" keine Warnung durchgehen', () => { const skript = PAKET.scripts['lint'] ?? ''; expect(skript, 'kein Skript "lint" in package.json').not.toBe(''); expect(skript, 'ohne --max-warnings 0 ist eine Warnung wieder folgenlos').toContain( '--max-warnings 0', ); }); it('faehrt auch das Gate ueber dieses Skript', () => { // Sonst liefe der Commit-Haken an der Verschaerfung vorbei. const schnell = PAKET.scripts['gate:schnell'] ?? ''; expect(schnell, 'kein Skript "gate:schnell"').not.toBe(''); expect(schnell, 'die Schnellstufe faehrt den Linter nicht').toContain('lint'); }); it.each(RUECKSTANDSREGELN)('meldet %s einen Fehler und keine Warnung', (regel) => { const stelle = REGELWERK.indexOf(`'${regel}'`); expect(stelle, `die Regel ${regel} steht nicht im Regelwerk`).toBeGreaterThanOrEqual(0); // Die Einstufung steht hinter dem Namen, bis zum Zeilenende. const bisEnde = REGELWERK.slice(stelle, REGELWERK.indexOf('\n', stelle)); expect(bisEnde, `${regel} steht wieder auf 'warn'`).not.toContain("'warn'"); expect(bisEnde, `${regel} steht nicht auf 'error'`).toContain("'error'"); }); it('ist ein ueberfluessig gewordener Ausnahmevermerk selbst ein Fehler', () => { /* * Die Zeile, an der alles haengt. 28 Vermerke stehen im Baum; ohne diese * Einstellung ueberleben sie jede Umschreibung der Stelle, und der * Rueckstand waere nur unsichtbar geworden statt abgetragen. */ expect( REGELWERK.replace(/\s+/g, ' '), 'reportUnusedDisableDirectives steht nicht auf error', ).toContain("linterOptions: { reportUnusedDisableDirectives: 'error' }"); }); it('nennt jeder Ausnahmevermerk im Baum einen Grund', () => { /* * `eslint-disable-next-line ` ohne `-- ` waere ein * stillschweigendes Ausschalten. Geprueft wird an den Dateien selbst, nicht * an einer Liste: Eine Liste muesste gepflegt werden und ginge irgendwann * an der Wirklichkeit vorbei. */ const ohneGrund: string[] = []; let gefunden = 0; for (const datei of quelldateien('src', 'electron', 'shared', 'tools', 'e2e')) { const zeilen = readFileSync(datei, 'utf8').split('\n'); for (const [nr, zeile] of zeilen.entries()) { if (!zeile.includes('eslint-disable')) continue; gefunden += 1; if (!zeile.includes(' -- ')) ohneGrund.push(`${datei}:${nr + 1}`); } } expect( gefunden, 'kein einziger Ausnahmevermerk gefunden - sucht der Fall am rechten Ort?', ).toBeGreaterThan(0); expect(ohneGrund, 'Ausnahmevermerke ohne Grund').toEqual([]); }); }); /** Alle `.ts` unter den genannten Verzeichnissen. */ function quelldateien(...wurzeln: readonly string[]): readonly string[] { const gefunden: string[] = []; const suche = (ordner: string): void => { for (const eintrag of readdirSync(ordner, { withFileTypes: true })) { const pfad = path.join(ordner, eintrag.name); if (eintrag.isDirectory()) suche(pfad); else if (eintrag.name.endsWith('.ts')) gefunden.push(pfad); } }; for (const wurzel of wurzeln) suche(wurzel); return gefunden; }