ban-type-assertions

Par factory-ai · factory-plugins

Interdire les assertions de type `as` dans un package via la règle lint `@typescript-eslint/consistent-type-assertions`, en les remplaçant par des alternatives vérifiées par le compilateur et sûres au niveau des types. À utiliser lors de l'activation de l'interdiction des assertions dans un nouveau package ou lors de la correction de violations dans un package existant.

npx skills add https://github.com/factory-ai/factory-plugins --skill ban-type-assertions

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 :

  1. Vérifier les schémas zod existants -- grep pour Schema à côté du nom du type dans @factory/common et dans le repo.
  2. 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.
  3. Vérifier les types/interfaces dupliqués entre les packages -- les consolider dans @factory/common si trouvés.
  4. 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 :

  1. Créez le schéma dans @factory/common/<domain>/<subdomain>/schema.ts
  2. Mettez les enums dans un fichier sibling enums.ts (requis par factory/enum-file-organization)
  3. Exportez via un subpath (par ex., @factory/common/session/summary), pas le barrel index.ts
  4. Supprimez tous les doublons locaux
  5. Mettez à jour tous les consommateurs pour importer depuis le subpath commun
  6. 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-organization exige que les enums TypeScript vivent dans des fichiers nommés enums.ts
  • no-barrel-files empê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.json exports pour le nouveau subpath si elle n'existe pas
  • Les overrides de test pour la règle dans .eslintrc.js peuvent ê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

Skills similaires