lsa-planer

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

/ tests tools linterRueckstand.test.ts

5,5 KB Rohdatei
tests/tools/linterRueckstand.test.ts — 127 Zeilen
1 import { readFileSync, readdirSync } from 'node:fs';
2 import path from 'node:path';
3 import { describe, expect, it } from 'vitest';
4
5 /*
6 * Der Linter duldet keinen Rueckstand mehr.
7 *
8 * WAS VORHER WAR: 36 Warnungen standen im Baum, Fall fuer Fall durchgesehen und
9 * als beabsichtigt bestaetigt, und die Zahl stand als offener Punkt vermerkt.
10 * Das las sich wie Ordnung, hatte aber eine Luecke, die man erst spaet bemerkt:
11 * Der siebenunddreissigste Fall faellt niemandem auf. Er wird eine Warnung
12 * unter sechsunddreissig, das Gate bleibt gruen, und die Durchsicht muesste
13 * jemand von sich aus wiederholen.
14 *
15 * WAS JETZT GILT: Die beiden Regeln melden FEHLER. Die 28 Zeilen, die sie
16 * beanstandeten, tragen ihren eigenen Ausnahmevermerk samt Grund - der
17 * ausfuehrliche Kommentar stand ohnehin schon darueber. Der Rueckstand ist
18 * damit nicht verschwunden, sondern an die Stellen gewandert, an denen er
19 * hingehoert.
20 *
21 * WOGEGEN DIESER FALL GESCHRIEBEN IST: Diese Umstellung laesst sich in einer
22 * Minute rueckgaengig machen, ohne dass ein Test rot wird - `'error'` zu
23 * `'warn'`, und der Rueckstand ist zurueck, nur diesmal ohne dass jemand
24 * mitzaehlt. Drei Zeilen tragen die Umstellung, und dieser Fall haelt alle
25 * drei fest:
26 *
27 * 1. Das Skript `lint` laeuft mit `--max-warnings 0`. Ohne das waere eine
28 * Warnung wieder folgenlos, egal was in der Konfiguration steht.
29 * 2. Die beiden Regeln stehen auf `'error'`.
30 * 3. `reportUnusedDisableDirectives` steht auf `'error'`. Das ist die
31 * wichtigste der drei: Ohne sie waeren 28 Ausnahmevermerke nur eine
32 * leisere Art, denselben Rueckstand zu fuehren - sie blieben stehen, wenn
33 * die Stelle umgeschrieben wird oder TypeScript eines Tages besser
34 * verengt.
35 *
36 * Was dieser Fall NICHT tut: den Linter fahren. Das tut `gate:schnell` bei
37 * jedem Commit, und zwar mit den Einstellungen, die hier geprueft werden. Eine
38 * zweite Ausfuehrung hier waere dieselbe Messung ein zweites Mal - und
39 * langsamer.
40 */
41
42 const REGELWERK = readFileSync('eslint.config.mjs', 'utf8');
43 const PAKET = JSON.parse(readFileSync('package.json', 'utf8')) as {
44 scripts: Record<string, string>;
45 };
46
47 /** Die Regeln, die den Rueckstand ausmachten. */
48 const RUECKSTANDSREGELN = [
49 '@typescript-eslint/no-unnecessary-condition',
50 '@typescript-eslint/no-unnecessary-type-assertion',
51 ] as const;
52
53 describe('Der Linter duldet keinen Rueckstand', () => {
54 it('laesst das Skript "lint" keine Warnung durchgehen', () => {
55 const skript = PAKET.scripts['lint'] ?? '';
56 expect(skript, 'kein Skript "lint" in package.json').not.toBe('');
57 expect(skript, 'ohne --max-warnings 0 ist eine Warnung wieder folgenlos').toContain(
58 '--max-warnings 0',
59 );
60 });
61
62 it('faehrt auch das Gate ueber dieses Skript', () => {
63 // Sonst liefe der Commit-Haken an der Verschaerfung vorbei.
64 const schnell = PAKET.scripts['gate:schnell'] ?? '';
65 expect(schnell, 'kein Skript "gate:schnell"').not.toBe('');
66 expect(schnell, 'die Schnellstufe faehrt den Linter nicht').toContain('lint');
67 });
68
69 it.each(RUECKSTANDSREGELN)('meldet %s einen Fehler und keine Warnung', (regel) => {
70 const stelle = REGELWERK.indexOf(`'${regel}'`);
71 expect(stelle, `die Regel ${regel} steht nicht im Regelwerk`).toBeGreaterThanOrEqual(0);
72 // Die Einstufung steht hinter dem Namen, bis zum Zeilenende.
73 const bisEnde = REGELWERK.slice(stelle, REGELWERK.indexOf('\n', stelle));
74 expect(bisEnde, `${regel} steht wieder auf 'warn'`).not.toContain("'warn'");
75 expect(bisEnde, `${regel} steht nicht auf 'error'`).toContain("'error'");
76 });
77
78 it('ist ein ueberfluessig gewordener Ausnahmevermerk selbst ein Fehler', () => {
79 /*
80 * Die Zeile, an der alles haengt. 28 Vermerke stehen im Baum; ohne diese
81 * Einstellung ueberleben sie jede Umschreibung der Stelle, und der
82 * Rueckstand waere nur unsichtbar geworden statt abgetragen.
83 */
84 expect(
85 REGELWERK.replace(/\s+/g, ' '),
86 'reportUnusedDisableDirectives steht nicht auf error',
87 ).toContain("linterOptions: { reportUnusedDisableDirectives: 'error' }");
88 });
89
90 it('nennt jeder Ausnahmevermerk im Baum einen Grund', () => {
91 /*
92 * `eslint-disable-next-line <regel>` ohne `-- <Grund>` waere ein
93 * stillschweigendes Ausschalten. Geprueft wird an den Dateien selbst, nicht
94 * an einer Liste: Eine Liste muesste gepflegt werden und ginge irgendwann
95 * an der Wirklichkeit vorbei.
96 */
97 const ohneGrund: string[] = [];
98 let gefunden = 0;
99 for (const datei of quelldateien('src', 'electron', 'shared', 'tools', 'e2e')) {
100 const zeilen = readFileSync(datei, 'utf8').split('\n');
101 for (const [nr, zeile] of zeilen.entries()) {
102 if (!zeile.includes('eslint-disable')) continue;
103 gefunden += 1;
104 if (!zeile.includes(' -- ')) ohneGrund.push(`${datei}:${nr + 1}`);
105 }
106 }
107 expect(
108 gefunden,
109 'kein einziger Ausnahmevermerk gefunden - sucht der Fall am rechten Ort?',
110 ).toBeGreaterThan(0);
111 expect(ohneGrund, 'Ausnahmevermerke ohne Grund').toEqual([]);
112 });
113 });
114
115 /** Alle `.ts` unter den genannten Verzeichnissen. */
116 function quelldateien(...wurzeln: readonly string[]): readonly string[] {
117 const gefunden: string[] = [];
118 const suche = (ordner: string): void => {
119 for (const eintrag of readdirSync(ordner, { withFileTypes: true })) {
120 const pfad = path.join(ordner, eintrag.name);
121 if (eintrag.isDirectory()) suche(pfad);
122 else if (eintrag.name.endsWith('.ts')) gefunden.push(pfad);
123 }
124 };
125 for (const wurzel of wurzeln) suche(wurzel);
126 return gefunden;
127 }