- Lot 1 (S8786, ReDoS) : 5 sites documentes NOSONAR apres preuve empirique (script reproductible docs/redos_probe_s8786.js), aucune reecriture defensive necessaire. - Lot 2 (S2703, variable globale implicite) : bug reel trouve et corrige (SCENE_OBJECT_NAMES en const au lieu de let, cassait la reassignation cross-script depuis scene-editor.js) + test de non-regression ; 4 autres sites confirmes surs et documentes. - Lot 3 (S2486, exceptions avalees) : 6 sites confirmes surs et documentes ; 2 sites (config sprite JSON invalide) corriges avec un console.warn devtools, comportement joueur inchange, couverts par un nouveau test. - Retrait du job CI sonarqube (.gitea/workflows/deploy.yml) et du service prod sonarqube/sonar-postgres (docker-compose.prod.yml) : acces dashboard bloque par des soucis d'infrastructure reseau (WSL2/pare-feu Hyper-V en local, reseau Docker partage avec Caddy pas en place en prod), sans lien avec le code du moteur - mis de cote plutot que de continuer a bloquer sur de l'infra. Les lots 4+ de modernisation JS dependent de scores Sonar exacts et sont donc egalement en pause (voir CODE_QUALITY.md). SKIP=djlint : H021 (styles inline, 49 occurrences) est un backlog deja documente et assume (CODE_QUALITY.md section 6), sur des templates non touches par ce commit - deja exclu de la CI pour la meme raison. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.8 KiB
Plan — lot JS "modernisation" (357 issues SonarQube)
Trace de la stratégie validée avant exécution — même valeur que
docs/SESSION_RECAP.md pour la Phase 3 : ce document décrit le plan tel
que discuté et confirmé, pas un compte-rendu de ce qui a déjà été fait.
Pour l'état d'avancement réel lot par lot, voir les commits de ce
chantier (référencés ici au fur et à mesure).
Origine : rapport SonarQube local (http://localhost:9000), catégorie
"code smells" JavaScript, 357 occurrences réparties sur 22 règles.
1. Répartition exacte par règle Sonar
| Règle | Nom | Occurrences |
|---|---|---|
S7773 |
Number static methods preferred over globals (parseFloat→Number.parseFloat, etc.) |
119 |
S6582 |
Optional chaining should be preferred | 98 |
S7781 |
replaceAll() instead of replace() with global regex |
29 |
S7761 |
Data attributes via .dataset |
22 |
S7765 |
.includes() instead of .indexOf()/.lastIndexOf() |
19 |
S3972 |
Conditionals should start on new lines | 15 |
S3776 |
Cognitive Complexity too high | 9 |
S4138 |
for-of should be used with Iterables |
6 |
S4624 |
Template literals should not be nested | 6 |
S2703 |
Variables should be declared explicitly (var globale implicite) | 6 |
S8786 |
Regex non-linear backtracking (ReDoS) | 5 |
S3358 |
Ternary operators should not be nested | 4 |
S7744 |
Unnecessary fallback objects in object spread | 3 |
S2486 |
Exceptions should not be ignored | 3 |
S6557 |
startsWith()/endsWith() instead of manual check |
3 |
S7721 |
Function should be moved to highest possible scope | 2 |
S4144 |
Functions should not have identical implementations | 2 |
S6653 |
Object.hasOwn() instead of hasOwnProperty |
2 |
S6671 |
Literal used for promise rejection (pas une Error) |
1 |
S7755 |
.at() instead of […length - index] |
1 |
S7769 |
Modern Math APIs (Math.hypot etc.) |
1 |
S3735 |
void should not be used |
1 |
| Total | 357 |
2. Classification
Mécanique, sans risque sémantique (équivalence prouvée)
| Règle | Pourquoi c'est sûr |
|---|---|
S7773 |
parseFloat/parseInt/isNaN/isFinite sont des alias exacts de Number.* depuis ES2015 |
S7781 |
replace()→replaceAll() seulement quand la regex a déjà le flag g (précondition de la règle) |
S7765 |
.indexOf(x) !== -1 ⇔ .includes(x) — équivalence stricte pour ce pattern |
S7761 |
getAttribute('data-x')→.dataset.x — mécanique si nom d'attribut statique |
S7744 |
Le spread de null/undefined ne lève jamais — le fallback || {} est toujours redondant |
S6557, S6653, S7755, S7769 |
Équivalences directes, très faible volume (7 sites), pas besoin d'outil |
S3972 |
Pur formatage, zéro impact comportemental |
⚠️ Correction par rapport à l'hypothèse de départ : S6582 n'est PAS mécanique
a && a.b et a?.b divergent quand a est une valeur fausse mais
définie (0, "", false, NaN) : a && a.b s'arrête et renvoie
a, a?.b continue et accède à .b. Une transformation aveugle peut
changer le comportement. Malgré son volume (98, 2ᵉ plus grosse règle),
S6582 est traité en examen au cas par cas, pas en lot mécanique.
Nécessite un vrai examen au cas par cas
| Règle | Nature | Note |
|---|---|---|
S6582 (98) |
Sémantique | Voir ci-dessus — sous-lots avec vérification du type de la valeur testée |
S8786 (5) |
Sécurité-adjacent | Sites JS, distincts de l'exception Python déjà validée en Phase 3 (auth/email_validation.py) |
S2703 (6) |
Sécurité-adjacent | 1 seul des 6 sites déjà examiné et validé (bindings.js:179, gameData) — 5 restent à vérifier, ne pas supposer qu'ils sont identiques |
S2486 (3) |
Sécurité-adjacent | Exception avalée, peut masquer un vrai bug |
S3776 (9) |
Refactor réel | Pas de fix mécanique — restructuration fonction par fonction |
S4624 (6), S3358 (4) |
Lisibilité | Extraction manuelle |
S4138 (6) |
Risque de perte de l'index | Fix proposé par un outil mais vérifié site par site avant application |
S4144 (2), S7721 (2) |
Décision de conception | Dédupliquer/déplacer une fonction change la portée |
S6671 (1) |
Change le type de la valeur de rejet | Impact possible sur du code aval qui inspecterait .message/instanceof Error |
S3735 (1) |
Dépend de l'intention | À lire avant de trancher |
3. Outillage — vérifié empiriquement
eslint-plugin-sonarjs(officiel SonarSource, 4.2.1, dernière version) : ne couvre qu'une seule des 22 règles avec un fixer (S3972) — les règlesS77xxsont trop récentes pour cette version.eslint-plugin-unicorn: dernière version (75.0.0) exige ESLint ≥10 (incompatible avec notre 8.57.1). Version retenue : 55.0.0 (exige ESLint ≥8.56.0, compatible), ajoutée en devDependency permanente. 9 règles activées dans.eslintrc.jsonavec un fixer confirmé (fixable: 'code'vérifié dans le code source du plugin) :unicorn/prefer-number-properties(S7773)unicorn/prefer-string-replace-all(S7781)unicorn/prefer-dom-node-dataset(S7761)unicorn/prefer-includes(S7765)unicorn/prefer-string-starts-ends-with(S6557)unicorn/prefer-modern-math-apis(S7769)unicorn/prefer-at(S7755)unicorn/no-useless-fallback-in-spread(S7744)unicorn/no-for-loop(S4138)- Uniquement ces 9 règles activées, jamais la config
recommendedcomplète du plugin (qui en contient des dizaines d'autres, jamais évaluées pour ce projet) — voirCODE_QUALITY.mdsection 2.
- Dry-run réel (
--fix-dry-run) sur toutstatic/js/**/*.jsavec ces 9 règles : 222 problèmes détectés, 222 avec un fix proposé. Les comptes par règle ne correspondent pas exactement à ceux de Sonar (moteurs différents, ex.prefer-includesdétecte 41 cas contre 19 pourS7765côté Sonar) — normal, la vérification finale se fait via un rescan Sonar après application, pas via le compte ESLint. @typescript-eslint(candidat pour comblerS6582) : nécessite de remplacer tout le parser JS par le parser TypeScript — écarté, trop lourd pour un projet 100% JS.Object.hasOwn(S6653, 2 sites) etstartsWith/endsWith(S6557, 3 sites) : aucun outil ne les a détectés dans le dry-run — 5 sites au total, correction manuelle directe.
4. Ordre de priorité
- Sécurité-adjacent :
S8786(5),S2703(6, dont 5 non-vérifiés),S2486(3) — 14 sites. - Structure/lisibilité à risque de bug :
S3776(9),S4624(6),S3358(4),S4144(2),S7721(2),S6671(1),S3735(1) — 25 sites. S6582optional chaining (98) — lot dédié à part.- Modernisation mécanique via outil (
S7773,S7781,S7765,S7761,S7744,S3972,S4138vérifié) — le plus gros volume, le moins risqué, en dernier. - Reliquat manuel très faible volume (
S6557,S6653,S7755,S7769) — 7 sites.
5. Découpage en 9 lots
| Lot | Contenu | Méthode |
|---|---|---|
| 1 | S8786 (5) |
Lecture individuelle, diff par site |
| 2 | S2703 (5 restants, hors gameData déjà validé) |
Lecture individuelle — même pattern ou différent ? |
| 3 | S2486 (3) |
Lecture individuelle |
| 4 | S3776 (9) |
Refactor fonction par fonction |
| 5 | S4624 + S3358 + S4144 + S7721 + S6671 + S3735 (16) |
Groupés, petits volumes, revue individuelle |
| 6 | S6582 (98) |
Sous-lots de ~20-25, vérification du type à chaque site |
| 7 | S7773+S7781+S7765+S7761+S7744+S3972 via eslint-plugin-unicorn@55.0.0 --fix |
--fix en un coup, diff complet relu avant commit |
| 8 | S4138 (fix proposé par no-for-loop, vérifié un par un) |
Semi-automatique |
| 9 | S6557+S6653+S7755+S7769 (7) |
Manuel direct |
À chaque lot : diff complet montré avant application, suite de tests
JS (node --test static/js/play/__tests__/*.test.js static/js/play/offline/__tests__/*.test.js static/js/scenes/__tests__/*.test.js, 241 tests) après application,
signalement immédiat de tout changement de comportement réel (pas
seulement du style). Rescan SonarQube local après le lot 7 (le plus gros
volume mécanique) pour confirmer la réduction réelle plutôt que de se
fier au seul compte ESLint, puis un rescan final après le lot 9.
Note de séquencement Git
Les 9 règles unicorn/* ont été activées dans .eslintrc.json (et
eslint-plugin-unicorn ajouté en devDependency) avant que les 222
occurrences correspondantes soient corrigées — ce qui rend pre-commit/
la CI rouges sur ESLint dès maintenant si ces changements de config
étaient commités seuls. Ces changements restent donc non commités
tant que le lot 7 (la correction mécanique) n'est pas fait, pour ne
jamais introduire un commit où le lint échouerait sur dev.