lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests tools linterregelwerk.test.ts

5,4 KB Rohdatei
tests/tools/linterregelwerk.test.ts — 111 Zeilen
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 });