lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests tools linterregelwerk.test.ts
| 1 | import { readFileSync } from 'node:fs'; |
| 2 | import { describe, expect, it } from 'vitest'; |
| 3 | |
| 4 | /* |
| 5 | * Keine der drei Regeln wird stillschweigend abgeschwaecht. |
| 6 | * |
| 7 | * WAS DIESE DATEI FRUEHER PRUEFTE: Der Begruendungsblock in `eslint.config.mjs` |
| 8 | * fuehrte je Regel eine Fallzahl und je Ursache eine Teilzahl, und diese Datei |
| 9 | * rechnete beides gegeneinander nach. Das war richtig, solange es einen |
| 10 | * geduldeten Rueckstand von 36 Warnungen gab. Den gibt es nicht mehr: Die |
| 11 | * Regeln melden Fehler, jede beanstandete Zeile traegt ihren eigenen |
| 12 | * `eslint-disable-next-line ... -- <Grund>`, und `npm run lint` laeuft mit |
| 13 | * `--max-warnings 0`. Zahlen, die nicht mehr dastehen, lassen sich nicht |
| 14 | * nachrechnen - die betreffenden Faelle sind deshalb fort. |
| 15 | * |
| 16 | * WAS BLEIBT, UND ES IST DAS WICHTIGERE: die Frage, gegen die diese Datei |
| 17 | * urspruenglich geschrieben wurde. Sie lautete: Wird eine der gefuehrten Regeln |
| 18 | * stillschweigend abgeschaltet, waehrend der Text weiter so tut, als werde |
| 19 | * gemessen? GEMESSEN war das damals moeglich - |
| 20 | * `no-unnecessary-type-assertion` und `switch-exhaustiveness-check` liessen |
| 21 | * sich abschalten, ohne dass ein einziger Testfall rot wurde. Diese Luecke |
| 22 | * bleibt bestehen, wenn niemand darauf sieht; sie ist nur eine Stufe hoeher |
| 23 | * gewandert: Aus "steht die Regel noch auf warn" wird "steht sie auf error". |
| 24 | * |
| 25 | * ABGRENZUNG zu `tests/tools/linterRueckstand.test.ts`: Der dortige Fall liest |
| 26 | * die Einstufung an der Stelle, an der die Regel GESETZT wird. Diese Datei |
| 27 | * fragt das Gegenteil - ob irgendwo sonst in der Datei eine Abschwaechung |
| 28 | * steht. Ein spaeterer Block ueberschreibt einen frueheren, und genau eine |
| 29 | * solche Abschwaechung gibt es begruendet: `no-unnecessary-condition` fuer |
| 30 | * `tests/`. Jede weitere waere ein Rueckstand, den kein Text mehr nennt. |
| 31 | */ |
| 32 | |
| 33 | const LINTERREGELWERK = readFileSync('eslint.config.mjs', 'utf8'); |
| 34 | |
| 35 | /** Die drei Regeln, die den Rueckstand ausmachten. */ |
| 36 | const GEFUEHRTE_REGELN = [ |
| 37 | 'no-unnecessary-condition', |
| 38 | 'no-unnecessary-type-assertion', |
| 39 | 'switch-exhaustiveness-check', |
| 40 | ] as const; |
| 41 | |
| 42 | /** |
| 43 | * Zu welchem Schweregrad ist eine Regel gesetzt - in der Reihenfolge der Datei? |
| 44 | * |
| 45 | * Erfasst beide Schreibweisen: `'regel': 'error'` und `'regel': ['error', {...}]`. |
| 46 | * Die zweite kam mit `switch-exhaustiveness-check` hinzu; ein Muster, das nur |
| 47 | * die erste kennt, faende die Regel gar nicht und liefe still ins Leere. |
| 48 | */ |
| 49 | function setzungen(regel: string): string[] { |
| 50 | const treffer = LINTERREGELWERK.matchAll( |
| 51 | new RegExp(`'@typescript-eslint/${regel}':\\s*\\[?\\s*'(\\w+)'`, 'g'), |
| 52 | ); |
| 53 | return [...treffer].map((t) => String(t[1])); |
| 54 | } |
| 55 | |
| 56 | describe('eslint.config.mjs - keine der drei Regeln ist stillschweigend abgeschwaecht', () => { |
| 57 | it.each(GEFUEHRTE_REGELN)('ist %s ueberhaupt gesetzt', (regel) => { |
| 58 | // Ohne diesen Fall waeren die beiden darunter leer und damit gruen, ohne |
| 59 | // etwas zu pruefen - die Falle, in die eine Wache dieses Baums schon |
| 60 | // einmal gelaufen ist. |
| 61 | expect(setzungen(regel).length, `Setzungen von ${regel}`).toBeGreaterThan(0); |
| 62 | }); |
| 63 | |
| 64 | it.each(GEFUEHRTE_REGELN)('meldet %s einen Fehler, nirgends nur eine Warnung', (regel) => { |
| 65 | expect(setzungen(regel), `Setzungen von ${regel}`).toContain('error'); |
| 66 | expect(setzungen(regel), `${regel} steht irgendwo wieder auf 'warn'`).not.toContain('warn'); |
| 67 | }); |
| 68 | |
| 69 | /* |
| 70 | * 'off' ist fuer genau eine Regel und genau einen Block vorgesehen: |
| 71 | * no-unnecessary-condition in tests/. Jede weitere Abschaltung waere ein |
| 72 | * Rueckstand, den kein Text mehr nennt - und den seit dem Wegfall der |
| 73 | * Fallzahlen auch keine Aufstellung mehr verraten wuerde. |
| 74 | */ |
| 75 | it.each(GEFUEHRTE_REGELN)('schaltet %s nicht ausserhalb des einen Blocks ab', (regel) => { |
| 76 | const aus = setzungen(regel).filter((s) => s === 'off'); |
| 77 | const erlaubt = regel === 'no-unnecessary-condition' ? 1 : 0; |
| 78 | expect(aus.length, `Abschaltungen von ${regel}`).toBe(erlaubt); |
| 79 | }); |
| 80 | |
| 81 | it('haelt die eine erlaubte Abschaltung im Block fuer tests/', () => { |
| 82 | /* |
| 83 | * Von hinten gesucht, also dort, wo der Ausnahmeblock steht: Eine |
| 84 | * Abschaltung weiter oben wuerde bei `indexOf` den falschen Block treffen, |
| 85 | * und der Fall pruefte dann eine Stelle, die er gar nicht meint. |
| 86 | */ |
| 87 | const aus = LINTERREGELWERK.lastIndexOf("'@typescript-eslint/no-unnecessary-condition': 'off'"); |
| 88 | expect(aus, 'Ausnahmeblock nicht gefunden').toBeGreaterThan(0); |
| 89 | const block = LINTERREGELWERK.slice(LINTERREGELWERK.lastIndexOf('files:', aus), aus); |
| 90 | expect(block, 'Ausnahme gilt fuer tests/').toContain('tests/**/*.ts'); |
| 91 | expect(block, 'Ausnahme nimmt nichts weiter mit').not.toContain('src/'); |
| 92 | expect(block, 'Ausnahme nimmt nichts weiter mit').not.toContain('electron/'); |
| 93 | expect(block, 'Ausnahme nimmt e2e/ mit - der Rauchtest gehoert zum Programm').not.toContain( |
| 94 | 'e2e', |
| 95 | ); |
| 96 | }); |
| 97 | |
| 98 | /* |
| 99 | * Und die Vollstaendigkeitspruefung: Ihre vier Faelle sind nicht ueber |
| 100 | * Ausnahmevermerke erledigt, sondern ueber eine Einstellung - ein |
| 101 | * default-Zweig macht den Schalter vollstaendig. Faellt die Einstellung weg, |
| 102 | * kehren vier Fehler zurueck; wird sie durch `allowDefaultCaseForExhaustive` |
| 103 | * o. ae. ersetzt, faengt die Regel weniger, ohne dass es auffiele. |
| 104 | */ |
| 105 | it('erklaert den default-Zweig fuer vollstaendig, statt vier Vermerke zu setzen', () => { |
| 106 | const einzeilig = LINTERREGELWERK.replace(/\s+/g, ' '); |
| 107 | expect(einzeilig, 'die Einstellung fehlt').toContain( |
| 108 | 'considerDefaultExhaustiveForUnions: true', |
| 109 | ); |
| 110 | }); |
| 111 | }); |