Interdire les assertions de type
Activez @typescript-eslint/consistent-type-assertions avec assertionStyle: 'never' dans un package et remplacez tous les casts as X par des patterns que le compilateur peut vérifier.
Philosophie centrale
Choisissez le chemin strictement correct, pas le plus simple.
Chaque assertion as est un endroit où le développeur a dit au compilateur « fais-moi confiance ». L'objectif est de faire vérifier le compilateur à la place. Si vous remplacez as Foo par un type guard tout aussi non vérifié, vous n'avez rien amélioré -- vous avez juste déplacé l'assertion.
Référence rapide
- Règle :
@typescript-eslint/consistent-type-assertions - Config :
{ assertionStyle: 'never' } - Localisation :
packages/<name>/.eslintrc.js
Workflow
1. Activer la règle
Ajoutez à .eslintrc.js du package :
rules: {
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
}
2. Énumérer les violations
cd packages/<name> && npm run lint 2>&1 | grep "consistent-type-assertions"
Groupez les violations par fichier et par pattern avant de les corriger.
3. Rechercher avant de corriger
Avant d'écrire du code de remplacement :
- Vérifier les schémas zod existants -- grep pour
Schemaà côté du nom du type dans@factory/commonet dans le repo. - Vérifier si des schémas existent mais ne sont pas exportés -- si oui, les exporter plutôt que d'en créer de nouveaux.
- Vérifier les types/interfaces dupliqués entre les packages -- les consolider dans
@factory/commonsi trouvés. - Comprendre le flux de données -- s'agit-il d'une limite d'analyse (données externes), d'un site de narrowing (type union), ou d'une lacune de type de bibliothèque ?
4. Corriger les violations en utilisant la hiérarchie de patterns
Tier 1 : Parsing Zod (pour les limites de données externes)
À utiliser pour toute donnée entrant dans le système depuis JSON, le disque, le réseau, IPC, etc. Cela donne une validation à l'exécution, pas juste une annotation de type.
// MAUVAIS
const data = JSON.parse(raw) as MyType;
// BON
const data = MySchema.parse(JSON.parse(raw));
Utilisez safeParse quand vous devez gérer les erreurs gracieusement (par ex., retourner une réponse d'erreur avec contexte comme un id de requête) :
// MAUVAIS : lance avant que vous puissiez extraire l'id de requête
const request = RequestSchema.parse(JSON.parse(raw));
// BON : safeParse vous permet de retourner une erreur correcte
const parsed = RequestSchema.safeParse(JSON.parse(raw));
if (!parsed.success) {
return errorResponse(rawObj?.id ?? null, INVALID_PARAMS, parsed.error.message);
}
const request = parsed.data;
Tier 2 : Narrowing de flux de contrôle (pour les types union)
Utilisez switch, in, instanceof, ou les unions discriminées :
// MAUVAIS
(error as NodeJS.ErrnoException).code
// BON
if (error instanceof Error && 'code' in error) {
const code = error.code;
}
// MAUVAIS
if (METHODS.has(method as Method)) { ... }
// BON : switch narrow de manière exhaustive
switch (method) {
case 'foo':
case 'bar':
return handle(method); // narrowed
}
Tier 3 : eslint-disable avec justification (dernier recours)
Uniquement pour les cas véritablement inévitables (lacunes de type de bibliothèque, paramètres génériques qui ne peuvent pas être déduits). Expliquez toujours pourquoi :
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- la bibliothèque ws nécessite un paramètre générique
ws.on('message', handler);
Anti-Pattern : Type Guards qui sont des assertions déguisées
// PAS une amélioration -- vérifie la forme mais pas le contenu
function isDaemonRequest(x: unknown): x is DaemonRequest {
return typeof x === 'object' && x !== null && 'method' in x;
}
Un schéma zod valide les valeurs. Un type guard comme celui-ci est une assertion non vérifiée avec des étapes supplémentaires. N'utilisez les type guards que quand la logique de narrowing est vraiment suffisante.
5. Utiliser des schémas stricts, pas des schémas permissifs
Quand un schéma existe (par ex., SessionSettingsSchema), utilisez-le strictement plutôt que z.record(z.unknown()). Cela garantit la compatibilité avant -- si des champs sont supprimés lors d'une migration, les données obsolètes sont nettoyées à la lecture.
// MAUVAIS : accepte n'importe quoi
const settings = z.record(z.unknown()).parse(raw);
// BON : valide contre la vraie forme
const settings = SessionSettingsSchema.parse(raw);
6. Promouvoir les schémas partagés à @factory/common
Si vous trouvez des interfaces, types ou schémas dupliqués entre les packages, consolidez-les :
- Créez le schéma dans
@factory/common/<domain>/<subdomain>/schema.ts - Mettez les enums dans un fichier sibling
enums.ts(requis parfactory/enum-file-organization) - Exportez via un subpath (par ex.,
@factory/common/session/summary), pas le barrelindex.ts - Supprimez tous les doublons locaux
- Mettez à jour tous les consommateurs pour importer depuis le subpath commun
- Exécutez
npm run knipà la racine du repo pour détecter les réexportations de barrel inutilisées
7. Corriger les mocks de test pour correspondre aux schémas
Une fois que vous remplacez as X par .parse(), les mocks de test qui reposaient sur l'assertion échoueront la validation. Corrigez les mocks -- ne désactivez pas la règle dans les tests.
Créez des fonctions d'aide pour centraliser les fixtures de test valides :
function mockSessionSummary(
overrides?: Partial<SessionSummaryEvent>,
): SessionSummaryEvent {
return {
type: 'session_start',
id: 'test-id',
title: 'Test Session',
owner: 'test-owner',
...overrides,
};
}
8. Parser à la limite, à l'intérieur de la gestion d'erreurs
Assurez-vous que le parsing se fait où les défaillances produisent des réponses d'erreur correctes, pas des exceptions non gérées :
// MAUVAIS : parser en dehors du try/catch -- s'il lance, vous perdez le contexte
const request = RequestSchema.parse(data);
try { handle(request); } catch { ... }
// BON : safeParse avant try, gérez l'erreur avec contexte
const parsed = RequestSchema.safeParse(data);
if (!parsed.success) {
return errorResponse(rawData?.id ?? null, INVALID_PARAMS, parsed.error.message);
}
try { handle(parsed.data); } catch { ... }
Vérification
Exécutez pour tous les packages affectés (une modification dans @factory/common peut casser le lint en aval) :
# Lint (tous les packages affectés)
cd packages/<name> && npm run lint
# Typecheck
npm run typecheck
# Tests
npm run test
# Exports inutilisés (racine du repo)
npm run knip
Rappels
factory/enum-file-organizationexige que les enums TypeScript vivent dans des fichiers nommésenums.tsno-barrel-filesempêche la réexportation de types depuis les barrel files -- les consommateurs doivent importer directement depuis le subpath- Quand vous promouvez des types à common, ajoutez une entrée
package.jsonexports pour le nouveau subpath si elle n'existe pas - Les overrides de test pour la règle dans
.eslintrc.jspeuvent être nécessaires si les fichiers de test utilisent la syntaxe d'assertion dans la configuration des mocks -- mais préférez corriger les mocks plutôt que de désactiver la règle