lsa-planer

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

/ eslint.config.mjs

10,6 KB Rohdatei
eslint.config.mjs — 240 Zeilen
1 // @ts-check
2 import { readFileSync } from 'node:fs';
3 import js from '@eslint/js';
4 import globals from 'globals';
5 import tseslint from 'typescript-eslint';
6
7 /**
8 * Was Git nicht erfasst, prueft auch der Linter nicht.
9 *
10 * Prettier liest die .gitignore von sich aus, ESLint nicht; ohne diese
11 * Uebersetzung stuende jede Zeile ein zweites Mal hier, und die beiden Listen
12 * liefen auseinander. Umgesetzt sind die Formen, die dort stehen: `name/` und
13 * `*.endung` gelten auf jeder Ebene, eine Zeile mit Schraegstrich am Anfang
14 * oder in der Mitte nur ab der Wurzel. Eine Negation (`!`) oder ein Backslash
15 * bricht ab, statt still anders gelesen zu werden als von Git.
16 *
17 * @returns {string[]}
18 */
19 function ausGitignore() {
20 const muster = [];
21 const text = readFileSync(new URL('.gitignore', import.meta.url), 'utf8');
22 for (const roh of text.split(/\r?\n/)) {
23 const zeile = roh.trim();
24 if (zeile === '' || zeile.startsWith('#')) continue;
25 if (zeile.startsWith('!') || zeile.includes('\\')) {
26 throw new Error(`.gitignore: diese Zeile setzt eslint.config.mjs nicht um: ${zeile}`);
27 }
28 const verzeichnis = zeile.endsWith('/');
29 const kern = verzeichnis ? zeile.slice(0, -1) : zeile;
30 const pfad = kern.includes('/') ? kern.replace(/^\//, '') : `**/${kern}`;
31 muster.push(verzeichnis ? `${pfad}/` : pfad);
32 }
33 return muster;
34 }
35
36 /**
37 * Regelwerk fuer den LSA-Planer.
38 *
39 * Was der Linter hier NICHT leisten muss: Typfehler finden. Das erledigt
40 * TypeScript selbst, und zwar streng (strict, noUncheckedIndexedAccess,
41 * exactOptionalPropertyTypes). Der Linter ist fuer die Fehlerklasse da, die
42 * der Uebersetzer durchlaesst - allen voran die schwebenden Zusagen:
43 *
44 * ipcMain.handle('kanal', async () => { ... }); // fehlt ein await,
45 * speichern(); // faellt der Fehler ins Nichts
46 *
47 * In einer Electron-Anwendung mit asynchronem IPC ist das der haeufigste Weg,
48 * auf dem eine Ausnahme lautlos verschwindet. Genau dagegen sind
49 * no-floating-promises und no-misused-promises eingeschaltet, und dafuer
50 * braucht der Linter Typinformationen (projectService).
51 *
52 * Formatierung steht hier bewusst nicht drin - dafuer gibt es Prettier.
53 */
54 export default tseslint.config(
55 {
56 /*
57 * Ein Ausnahmevermerk, den es nicht mehr braucht, ist ein Fehler.
58 *
59 * 28 Zeilen tragen einen `eslint-disable-next-line` samt Grund, statt dass
60 * 36 Warnungen als Rueckstand gefuehrt werden (bis 5.16.0 war es so). Ohne diese
61 * Zeile waere das nur eine leisere Art, denselben Rueckstand zu haben:
62 * Vermerke bleiben stehen, wenn die Stelle umgeschrieben wird oder
63 * TypeScript eines Tages besser verengt, und niemand merkt es. So faellt
64 * jeder ueberfluessig gewordene Vermerk beim naechsten Lauf auf.
65 */
66 linterOptions: { reportUnusedDisableDirectives: 'error' },
67 },
68
69 {
70 // Erzeugtes, Fremdes und was nicht Teil der Fassung ist, wird nicht
71 // geprueft; ein Regelwerk darueber erzeugte nur Laerm. Dazu alles, was
72 // die .gitignore nennt (siehe ausGitignore).
73 ignores: [
74 'build/**',
75 'coverage/**',
76 'release/**',
77 'node_modules/**',
78 'public/**',
79 // Vom Anwender abgelegte Arbeitsunterlagen (gespeicherte Netzseiten)
80 // bringen fremdes JavaScript mit. Es gehoert nicht zu diesem Programm,
81 // steht in keiner tsconfig, und der typbewusste Linter bricht daran ab.
82 '*_files/**',
83 '*.htm',
84 ...ausGitignore(),
85 ],
86 },
87
88 js.configs.recommended,
89 ...tseslint.configs.recommendedTypeChecked,
90
91 {
92 // Die beiden Projekte ausdruecklich benennen statt sie raten zu lassen.
93 // Mit `projectService: true` fielen electron/main.ts, preload.ts und
94 // protokoll.ts durch das Raster ("was not found by the project service"),
95 // waehrend electron/karte.ts geprueft wurde - eine Zuordnung, die man
96 // beim Lesen des Regelwerks nicht nachvollziehen kann.
97 files: [
98 'src/**/*.ts',
99 'shared/**/*.ts',
100 'electron/**/*.ts',
101 'tests/**/*.ts',
102 'e2e/**/*.ts',
103 'tools/**/*.ts',
104 ],
105 languageOptions: {
106 parserOptions: {
107 project: ['./tsconfig.json', './tsconfig.electron.json'],
108 tsconfigRootDir: import.meta.dirname,
109 },
110 },
111 rules: {
112 // --- Die eigentlichen Waechter -----------------------------------
113 '@typescript-eslint/no-floating-promises': 'error',
114 '@typescript-eslint/no-misused-promises': 'error',
115 '@typescript-eslint/await-thenable': 'error',
116 // FEHLER, NICHT WARNUNG.
117 //
118 // Bis zur Fassung 5.16.0 standen beide Regeln auf 'warn', weil 36 Faelle im Baum
119 // standen, die Fall fuer Fall durchgesehen und als beabsichtigt
120 // bestaetigt waren. Ein geduldeter Rueckstand hat aber eine Eigenschaft,
121 // die man erst spaet merkt: Der 37. Fall faellt niemandem auf. Er wird
122 // eine Warnung unter sechsunddreissig, das Gate bleibt gruen, und die
123 // Durchsicht muesste jemand von sich aus wiederholen.
124 //
125 // Der Rueckstand steht jetzt an den Stellen selbst. Jede der 28 Zeilen,
126 // die die Regeln beanstandeten, traegt ihren eigenen
127 // `eslint-disable-next-line ... -- <Grund>`; die ausfuehrliche
128 // Begruendung stand ohnehin schon als Kommentar darueber. Der eine Fall,
129 // der wirklich tot war (migrate.ts - `hasIntergreen` enthielt die
130 // Abfrage schon), ist entfernt, und die vier Faelle der
131 // Vollstaendigkeitspruefung sind ueber die Einstellung darunter
132 // erledigt statt ueber einen Vermerk.
133 //
134 // Wichtiger als die Vermerke ist `reportUnusedDisableDirectives` weiter
135 // oben: Ein Vermerk, den es nicht mehr braucht, ist selbst ein Fehler.
136 // Ohne diese Zeile waeren 28 Ausnahmen nur eine leisere Art, denselben
137 // Rueckstand zu fuehren - sie blieben stehen, wenn TypeScript eines
138 // Tages besser verengt oder die Stelle umgeschrieben wird.
139 //
140 // WARUM DIE PRUEFUNGEN NICHT EINFACH WEGFALLEN: Sie stehen fast alle vor
141 // Fremddaten - IPC-Nutzlast, Projektdatei, Satz aus IndexedDB, Antwort
142 // eines Kartendienstes. Der Typ ist dort die Behauptung des Aufrufers,
143 // keine Messung; die Pruefung ist die Wache dahinter. Sie zu streichen,
144 // weil der Uebersetzer sie fuer ueberfluessig haelt, waere die
145 // gefaehrliche Richtung. Dazu kommen zu grosszuegige Typpraedikate
146 // (`isFiniteNumber(x): x is number` weist Infinity und NaN ab, obwohl
147 // beide Zahlen sind - der Nein-Zweig wird dadurch falsch verengt),
148 // veraenderliche Felder ueber `await` hinweg, Faehigkeitsproben
149 // (`crypto?.randomUUID`, `unref?.()`) und Rueckfaelle in der
150 // Fehlerbehandlung, die eine Fehlermeldung nicht selbst zum Absturz
151 // bringen sollen.
152 //
153 // Die Durchsicht vom 03.09. und 07.09.2026, die zu dieser Einstufung
154 // gefuehrt hat, ist damit nicht mehr eine Liste hier, sondern steht Zeile
155 // fuer Zeile im Quelltext:
156 //
157 // * no-unnecessary-condition: 27 Vermerke, die groesste Haeufung mit
158 // sieben in electron/main.ts, dazu einer im Rauchtest. In tests/ ist
159 // die Regel abgeschaltet (Block weiter unten), in e2e/ nicht.
160 // * no-unnecessary-type-assertion: ein Vermerk (electron/karte.ts).
161 // Gemessen: Ohne die Zusicherung verengt der Uebersetzer auf
162 // Uint8Array, und die Abfrage `wert === undefined` eine Zeile darunter
163 // wird ihrerseits beanstandet - Aufraeumen verschoebe die Meldung,
164 // statt sie abzutragen.
165 '@typescript-eslint/no-unnecessary-condition': 'error',
166 '@typescript-eslint/no-unnecessary-type-assertion': 'error',
167 // Ein default-Zweig macht den Schalter vollstaendig. Das ist hier keine
168 // Nachsicht, sondern die Hausregel: Alle vier Faelle, die die Regel
169 // beanstandete, haben einen begruendeten default - intergreen.ts und
170 // engine.ts geben dort denselben Rueckgabewert wie ein eigener case,
171 // assistent.ts ergaenzt bewusst nur fuer Sonderformen einen Satz. Ohne
172 // die Einstellung muesste jede dieser Stellen einen Ausnahmevermerk
173 // tragen; mit ihr faengt die Regel weiterhin, was sie fangen soll: einen
174 // Schalter OHNE default, dem ein Fall fehlt.
175 '@typescript-eslint/switch-exhaustiveness-check': [
176 'error',
177 { considerDefaultExhaustiveForUnions: true },
178 ],
179
180 // Nicht benutzte Bezeichner meldet schon tsc (noUnusedLocals). Hier
181 // bleibt die Regel trotzdem an, aber mit der ueblichen Ausnahme fuer
182 // absichtlich ungenutzte Parameter.
183 '@typescript-eslint/no-unused-vars': [
184 'error',
185 { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
186 ],
187
188 // --- Bewusst entschaerft -----------------------------------------
189 // Die Anwendung liest Projektdateien fremder Herkunft ein; an der
190 // Grenze ist `any` unvermeidlich, dahinter wird geprueft (schema.ts).
191 // Diese Regeln schluegen dort an, ohne einen Fehler zu zeigen.
192 '@typescript-eslint/no-unsafe-assignment': 'off',
193 '@typescript-eslint/no-unsafe-member-access': 'off',
194 '@typescript-eslint/no-unsafe-argument': 'off',
195 '@typescript-eslint/no-unsafe-call': 'off',
196 '@typescript-eslint/no-unsafe-return': 'off',
197 },
198 },
199
200 {
201 // Konfigurationsdateien und Hilfsskripte stehen in keiner tsconfig - fuer
202 // sie gibt es keine Typinformationen, also auch keine typbewussten Regeln.
203 // Geprueft werden sie trotzdem, nur eben flacher.
204 files: ['**/*.mjs', '**/*.cjs', '*.config.ts'],
205 extends: [tseslint.configs.disableTypeChecked],
206 languageOptions: { globals: globals.node },
207 },
208
209 {
210 // Hauptprozess: laeuft in Node, kein DOM.
211 files: ['electron/**/*.ts', '**/*.mjs', '**/*.cjs', 'tools/**/*.ts'],
212 languageOptions: { globals: globals.node },
213 },
214
215 {
216 // Renderer: laeuft im Browser.
217 files: ['src/**/*.ts'],
218 languageOptions: { globals: globals.browser },
219 },
220
221 {
222 // Ausgabetests und die Pruefsummen pruefen genau die Zeichen, die die
223 // Regel beanstandet: geschuetzte und schmale Leerzeichen in CSV- und
224 // PDF-Ausgaben. Sie sind dort der Pruefgegenstand, nicht ein Versehen.
225 files: ['tests/export/**/*.ts', 'scripts/pruefsummen.mjs'],
226 rules: { 'no-irregular-whitespace': 'off' },
227 },
228
229 {
230 // Tests duerfen mehr: Vitest-Globale, und die Sorgfalt gegenueber
231 // schwebenden Zusagen ist dort weniger kritisch als im Programm.
232 files: ['tests/**/*.ts'],
233 languageOptions: { globals: { ...globals.node, ...globals.browser } },
234 rules: {
235 '@typescript-eslint/no-unnecessary-condition': 'off',
236 // Eine Testfunktion ohne await ist gaengige Schreibweise und kein Mangel.
237 '@typescript-eslint/require-await': 'off',
238 },
239 },
240 );