diff --git a/.cursor/rules/quality.mdc b/.cursor/rules/quality.mdc index 2b2e2ef..d651557 100644 --- a/.cursor/rules/quality.mdc +++ b/.cursor/rules/quality.mdc @@ -6,7 +6,7 @@ alwaysApply: true ## Introduction -L’objectif est de transformer une liste de principes en consignes opérationnelles, non ambiguës, applicables par une IA de développement TypeScript. Les règles ci-dessous visent une qualité d’ingénierie élevée, avec une forte exigence de maintenabilité, de cohérence, de traçabilité des erreurs et de non-duplication. Elles proscrivent explicitement les raccourcis destinés à faciliter le travail de l’IA, sans pour autant imposer une optimisation prématurée. +L’objectif est de transformer une liste de principes en consignes opérationnelles, non ambiguës, applicables par une IA de développement TypeScript. Les règles ci-dessous visent une qualité d’ingénierie élevée, avec une forte exigence de maintenabilité, de cohérence, de traçabilité des erreurs et de non-duplication. Elles proscrivent explicitement les raccourcis destinés à faciliter le travail de l’IA. ## Consignes de développement TypeScript pour une IA de production @@ -31,6 +31,271 @@ Toute évolution doit conserver une compilation stricte si le projet est en mode Le code doit respecter les conventions TypeScript du dépôt : organisation des modules, règles d’export, structure des types, conventions de nommage, modificateurs d’accès, usage de `readonly`, immutabilité lorsqu’elle constitue la norme du projet. +### Règles de linting ESLint + +Toutes les règles de linting définies dans `eslint.config.mjs` sont obligatoires et doivent être respectées. Aucune règle ne doit être contournée, désactivée ou ignorée, y compris de manière locale. + +#### Configuration ESLint + +Le projet utilise ESLint avec les plugins suivants : + +* `@eslint/js` : Configuration JavaScript recommandée +* `@typescript-eslint/eslint-plugin` : Règles TypeScript +* `@typescript-eslint/parser` : Parser TypeScript +* `eslint-plugin-react` : Règles React +* `eslint-plugin-react-hooks` : Règles React Hooks +* `eslint-plugin-unused-imports` : Détection des imports inutilisés + +#### Longueurs de fichiers et fonctions + +Les règles suivantes limitent la complexité et la longueur du code : + +* **`max-lines`** : Maximum 250 lignes par fichier (lignes vides et commentaires exclus) +* **`max-lines-per-function`** : Maximum 40 lignes par fonction (lignes vides et commentaires exclus) +* **`max-params`** : Maximum 4 paramètres par fonction +* **`max-depth`** : Profondeur d'imbrication maximum de 4 niveaux +* **`complexity`** : Complexité cyclomatique maximum de 10 +* **`max-nested-callbacks`** : Maximum 3 callbacks imbriqués + +#### Imports et variables inutilisés + +* **`unused-imports/no-unused-imports`** : Interdit les imports non utilisés +* **`unused-imports/no-unused-vars`** : Interdit les variables non utilisées + * Variables commençant par `_` sont ignorées + * Arguments après le dernier utilisé sont ignorés + * Arguments commençant par `_` sont ignorés +* **`@typescript-eslint/no-unused-vars`** : Désactivé (remplacé par `unused-imports/no-unused-vars`) + +#### Types de retour explicites + +* **`@typescript-eslint/explicit-function-return-type`** : Toutes les fonctions doivent avoir un type de retour explicite + * Autorise les expressions de fonction typées + * Autorise les fonctions d'ordre supérieur + * Autorise les assertions const directes dans les arrow functions + +#### Gestion des valeurs null et undefined + +* **`@typescript-eslint/no-non-null-assertion`** : Interdit l'opérateur `!` (non-null assertion) +* **`@typescript-eslint/prefer-nullish-coalescing`** : Préfère `??` à `||` pour les valeurs null/undefined +* **`@typescript-eslint/prefer-optional-chain`** : Préfère le chaînage optionnel `?.` +* **`@typescript-eslint/no-non-null-asserted-optional-chain`** : Interdit `!` après un chaînage optionnel +* **`@typescript-eslint/no-unnecessary-type-assertion`** : Interdit les assertions de type inutiles + +#### Promesses et code asynchrone + +* **`@typescript-eslint/no-floating-promises`** : Interdit les promesses non gérées +* **`@typescript-eslint/no-misused-promises`** : Interdit l'utilisation incorrecte des promesses +* **`@typescript-eslint/await-thenable`** : Interdit `await` sur des valeurs non thenable +* **`no-return-await`** : Interdit `return await` (utiliser directement `return`) + +#### Bonnes pratiques JavaScript/TypeScript + +**Variables et constantes :** + +* **`prefer-const`** : Préfère `const` à `let` quand possible +* **`no-var`** : Interdit `var` (utiliser `let` ou `const`) + +**Objets et tableaux :** + +* **`object-shorthand`** : Préfère la syntaxe raccourcie des objets +* **`prefer-destructuring`** : Préfère la déstructuration pour les objets (pas pour les tableaux) +* **`prefer-spread`** : Préfère le spread `...` à `apply()` +* **`no-array-constructor`** : Interdit `new Array()` +* **`no-new-object`** : Interdit `new Object()` +* **`no-new-wrappers`** : Interdit `new String()`, `new Number()`, `new Boolean()` + +**Chaînes de caractères :** + +* **`prefer-template`** : Préfère les template literals aux concaténations +* **`no-useless-concat`** : Interdit les concaténations inutiles + +**Comparaisons et égalités :** + +* **`eqeqeq`** : Interdit `==` et `!=` (utiliser `===` et `!==`) +* **`yoda`** : Interdit les conditions Yoda (`if ("red" === color)`) + +**Structures de contrôle :** + +* **`curly`** : Accolades obligatoires même pour une seule ligne +* **`no-else-return`** : Interdit `else` après `return` +* **`no-lonely-if`** : Interdit `if` seul dans `else` (utiliser `else if`) +* **`no-nested-ternary`** : Interdit les ternaires imbriqués +* **`no-unneeded-ternary`** : Interdit les ternaires inutiles +* **`no-continue`** : Interdit `continue` +* **`no-labels`** : Interdit les labels + +**Fonctions :** + +* **`prefer-arrow-callback`** : Préfère les arrow functions +* **`prefer-rest-params`** : Préfère les rest parameters à `arguments` +* **`no-confusing-arrow`** : Interdit les arrow functions ambiguës +* **`no-param-reassign`** : Interdit la réassignation des paramètres (propriétés autorisées) + +**Retours et assignations :** + +* **`no-useless-return`** : Interdit les `return` inutiles +* **`no-return-assign`** : Interdit les assignations dans `return` +* **`no-multi-assign`** : Interdit les assignations multiples (`a = b = c`) + +**Erreurs :** + +* **`no-throw-literal`** : Interdit de lancer des primitives (utiliser `Error`) + +**Autres :** + +* **`no-implicit-coercion`** : Interdit la coercition implicite +* **`no-useless-constructor`** : Interdit les constructeurs inutiles +* **`no-useless-call`** : Interdit les appels inutiles +* **`no-useless-computed-key`** : Interdit les clés calculées inutiles +* **`no-useless-rename`** : Interdit les renommages inutiles +* **`no-whitespace-before-property`** : Interdit les espaces avant les propriétés +* **`no-sequences`** : Interdit les séquences d'expressions +* **`no-iterator`** : Interdit `__iterator__` +* **`no-proto`** : Interdit `__proto__` +* **`no-bitwise`** : Interdit les opérateurs bitwise +* **`no-multi-str`** : Interdit les chaînes multi-lignes +* **`no-new`** : Interdit `new` sans assignation +* **`spaced-comment`** : Commentaires doivent être espacés + +**Syntaxe restreinte :** + +* **`no-restricted-syntax`** : Interdit certaines syntaxes + * `ForInStatement` : Utiliser `Object.keys()`, `Object.values()`, `Object.entries()` + * `LabeledStatement` : Les labels sont interdits + * `WithStatement` : `with` est interdit en mode strict + +#### Console et debug + +* **`no-console`** : Avertit sur `console.log` (autorise `console.warn` et `console.error`) +* **`no-debugger`** : Interdit `debugger` +* **`no-alert`** : Interdit `alert()` + +#### TypeScript - Types et sécurité + +**Types explicites :** + +* **`@typescript-eslint/no-explicit-any`** : Interdit `any` +* **`@typescript-eslint/explicit-module-boundary-types`** : Types explicites requis pour les exports de modules + +**Sécurité des types :** + +* **`@typescript-eslint/no-unsafe-assignment`** : Interdit les assignations non sûres +* **`@typescript-eslint/no-unsafe-member-access`** : Interdit les accès membres non sûrs +* **`@typescript-eslint/no-unsafe-call`** : Interdit les appels non sûrs +* **`@typescript-eslint/no-unsafe-return`** : Interdit les retours non sûrs +* **`@typescript-eslint/no-unsafe-argument`** : Interdit les arguments non sûrs +* **`@typescript-eslint/restrict-template-expressions`** : Restreint les expressions dans les template literals +* **`@typescript-eslint/restrict-plus-operands`** : Restreint les opérandes de l'opérateur `+` + +**Qualité des types :** + +* **`@typescript-eslint/no-redundant-type-constituents`** : Interdit les types redondants +* **`@typescript-eslint/prefer-reduce-type-parameter`** : Préfère les type parameters pour `reduce()` + +**Méthodes préférées :** + +* **`@typescript-eslint/prefer-includes`** : Préfère `includes()` à `indexOf() !== -1` +* **`@typescript-eslint/prefer-string-starts-ends-with`** : Préfère `startsWith()`/`endsWith()` aux regex + +**Autres :** + +* **`@typescript-eslint/no-require-imports`** : Désactivé (autorise `require()`) +* **`@typescript-eslint/no-shadow`** : Interdit l'ombre de variables (remplace `no-shadow`) +* **`@typescript-eslint/no-use-before-define`** : Interdit l'utilisation avant définition (fonctions autorisées, classes et variables interdites) + +#### React - Qualité et performance + +**Configuration React :** + +* **`react/react-in-jsx-scope`** : Désactivé (React 17+) +* **`react/prop-types`** : Désactivé (TypeScript gère les types) +* **`react/jsx-uses-react`** : Désactivé (React 17+) + +**Clés et props :** + +* **`react/jsx-key`** : Clés obligatoires dans les listes +* **`react/jsx-no-duplicate-props`** : Interdit les props dupliquées +* **`react/jsx-no-undef`** : Interdit les variables non définies dans JSX +* **`react/jsx-uses-vars`** : Variables utilisées dans JSX doivent être déclarées +* **`react/no-array-index-key`** : Avertit sur l'utilisation de l'index comme key + +**Children et props :** + +* **`react/no-children-prop`** : Interdit `children` comme prop +* **`react/no-danger-with-children`** : Interdit `dangerouslySetInnerHTML` avec children + +**API dépréciées :** + +* **`react/no-deprecated`** : Interdit les API dépréciées +* **`react/no-direct-mutation-state`** : Interdit la mutation directe du state +* **`react/no-find-dom-node`** : Interdit `findDOMNode()` +* **`react/no-is-mounted`** : Interdit `isMounted()` +* **`react/no-render-return-value`** : Interdit l'utilisation de la valeur de retour de `render()` +* **`react/no-string-refs`** : Interdit les string refs +* **`react/require-render-return`** : Return obligatoire dans `render()` + +**JSX :** + +* **`react/no-unescaped-entities`** : Interdit les entités non échappées +* **`react/no-unknown-property`** : Interdit les propriétés inconnues +* **`react/self-closing-comp`** : Composants auto-fermants requis +* **`react/jsx-boolean-value`** : Interdit les valeurs booléennes explicites (`prop={true}` → `prop`) +* **`react/jsx-curly-brace-presence`** : Interdit les `{}` inutiles dans props et children +* **`react/jsx-fragments`** : Préfère `<>` à `` +* **`react/jsx-no-useless-fragment`** : Interdit les fragments inutiles +* **`react/jsx-pascal-case`** : Composants en PascalCase + +**Performance :** + +* **`react/no-unstable-nested-components`** : Interdit les composants imbriqués instables +* **`react-hooks/exhaustive-deps`** : Dépendances exhaustives requises dans les hooks +* **`react-hooks/refs`** : Désactivé + +#### Sécurité et patterns dangereux + +**Évaluation de code :** + +* **`no-eval`** : Interdit `eval()` +* **`no-implied-eval`** : Interdit l'évaluation implicite (`setTimeout("code")`) +* **`no-new-func`** : Interdit `new Function()` +* **`no-script-url`** : Interdit les URLs `javascript:` + +**Autres :** + +* **`no-void`** : Désactivé (utilisé pour ignorer les promesses : `void promise`) +* **`no-with`** : Interdit `with` statement +* **`no-caller`** : Interdit `caller` +* **`no-extend-native`** : Interdit l'extension des objets natifs +* **`no-global-assign`** : Interdit l'assignation de variables globales +* **`no-implicit-globals`** : Interdit les globals implicites +* **`no-restricted-globals`** : Restreint certains globals (`event`, `fdescribe`) +* **`no-shadow-restricted-names`** : Interdit l'ombre sur les noms restreints + +#### Qualité et maintenabilité + +**Assignations et déclarations :** + +* **`no-misleading-character-class`** : Désactivé +* **`no-redeclare`** : Interdit la redéclaration +* **`no-self-assign`** : Interdit l'auto-assignation +* **`no-self-compare`** : Interdit l'auto-comparaison +* **`no-shadow`** : Désactivé (remplacé par `@typescript-eslint/no-shadow`) +* **`no-undef-init`** : Interdit l'initialisation à `undefined` +* **`no-undefined`** : Désactivé (`undefined` est parfois nécessaire) + +**Utilisation avant définition :** + +* **`no-use-before-define`** : Désactivé (remplacé par `@typescript-eslint/no-use-before-define`) + +**Autres :** + +* **`no-useless-call`** : Interdit les appels inutiles +* **`no-useless-computed-key`** : Interdit les clés calculées inutiles +* **`no-useless-rename`** : Interdit les renommages inutiles +* **`no-whitespace-before-property`** : Interdit les espaces avant les propriétés +* **`no-octal-escape`** : Interdit l'échappement octal +* **`spaced-comment`** : Commentaires doivent être espacés + ### Analyse préalable obligatoire et arbre des fichiers Avant toute implémentation, une phase d’analyse est obligatoire et doit produire une représentation de l’arbre des fichiers pertinents. @@ -331,7 +596,7 @@ Le projet est open source et hébergé sur Gitea auto-hébergé. Toutes les cont ### Repository et infrastructure -* **Repository Gitea** : https://git.4nkweb.com/4nk/story-research-zapwall +* **Repository Gitea** : * **Templates** : Utiliser les templates d'issues et de PR dans `.gitea/` * **Labels et organisation** : Utiliser les labels appropriés pour organiser les issues et PRs * **Branches** : Respecter la convention de nommage des branches (feature/, fix/, etc.) @@ -351,7 +616,7 @@ Le projet est open source et hébergé sur Gitea auto-hébergé. Toutes les cont Tous les commits doivent suivre ce format structuré : -``` +```text Titre court et descriptif **Motivations:** @@ -373,18 +638,18 @@ Titre court et descriptif ### Processus de commit 1. **Avant chaque commit** : - - Vérifier que le code compile (`npm run type-check`) - - Vérifier le linting (`npm run lint`) - - Vérifier que les modifications sont complètes et fonctionnelles + * Vérifier que le code compile (`npm run type-check`) + * Vérifier le linting (`npm run lint`) + * Vérifier que les modifications sont complètes et fonctionnelles 2. **Création du commit** : - - Utiliser `git add` pour les fichiers modifiés - - Créer un commit avec le format structuré - - Ne pas utiliser `--no-verify` sauf cas exceptionnel documenté + * Utiliser `git add` pour les fichiers modifiés + * Créer un commit avec le format structuré + * Ne pas utiliser `--no-verify` sauf cas exceptionnel documenté 3. **Après le commit** : - - Vérifier que le commit a bien été créé (`git log`) - - Documenter dans `fixKnowledge/` ou `features/` si nécessaire + * Vérifier que le commit a bien été créé (`git log`) + * Documenter dans `fixKnowledge/` ou `features/` si nécessaire ### Exceptions diff --git a/lib/purchaseQueries.ts b/lib/purchaseQueries.ts index b85a745..d3958bb 100644 --- a/lib/purchaseQueries.ts +++ b/lib/purchaseQueries.ts @@ -82,7 +82,7 @@ export async function getPurchaseById(purchaseId: string, timeoutMs: number = 50 resolve(value) } - sub.on('event', async (event: Event) => { + sub.on('event', async (event: Event): Promise => { const parsed = await parsePurchaseFromEvent(event) if (parsed?.id === purchaseId) { // Cache the parsed purchase diff --git a/lib/reviewTipQueries.ts b/lib/reviewTipQueries.ts index 89e15e2..92ee5b4 100644 --- a/lib/reviewTipQueries.ts +++ b/lib/reviewTipQueries.ts @@ -80,7 +80,7 @@ export async function getReviewTipById(reviewTipId: string, timeoutMs: number = resolve(value) } - sub.on('event', async (event: Event) => { + sub.on('event', async (event: Event): Promise => { const parsed = await parseReviewTipFromEvent(event) if (parsed?.id === reviewTipId) { // Cache the parsed review tip @@ -113,7 +113,7 @@ export function getReviewTipsForArticle(articleId: string, timeoutMs: number = 5 const sub = createSubscription(pool, [relayUrl], filters) let finished = false - const done = async () => { + const done = async (): Promise => { if (finished) { return } @@ -122,7 +122,7 @@ export function getReviewTipsForArticle(articleId: string, timeoutMs: number = 5 resolve(results) } - sub.on('event', async (event: Event) => { + sub.on('event', async (event: Event): Promise => { const parsed = await parseReviewTipFromEvent(event) if (parsed?.articleId === articleId) { // Cache the parsed review tip @@ -133,7 +133,9 @@ export function getReviewTipsForArticle(articleId: string, timeoutMs: number = 5 } }) - sub.on('eose', () => done()) + sub.on('eose', (): void => { + void done() + }) setTimeout(() => done(), timeoutMs).unref?.() }) } @@ -153,7 +155,7 @@ export function getReviewTipsForReview(reviewId: string, timeoutMs: number = 500 const sub = createSubscription(pool, [relayUrl], filters) let finished = false - const done = async () => { + const done = async (): Promise => { if (finished) { return } @@ -162,7 +164,7 @@ export function getReviewTipsForReview(reviewId: string, timeoutMs: number = 500 resolve(results) } - sub.on('event', async (event: Event) => { + sub.on('event', async (event: Event): Promise => { const parsed = await parseReviewTipFromEvent(event) if (parsed?.reviewId === reviewId) { // Cache the parsed review tip @@ -173,7 +175,9 @@ export function getReviewTipsForReview(reviewId: string, timeoutMs: number = 500 } }) - sub.on('eose', () => done()) + sub.on('eose', (): void => { + void done() + }) setTimeout(() => done(), timeoutMs).unref?.() }) }