Files
williamandClaude Sonnet 5 ccf836f2c5
Build and deploy / test-python (push) Successful in 11m18s
Build and deploy / test-js (push) Successful in 1m16s
Build and deploy / lint-python (push) Successful in 4m24s
Build and deploy / lint-js (push) Successful in 2m51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Successful in 5m30s
Lot 4 modernisation JS (SonarLint) + restauration CI sonarqube non-bloquante
Corrige les findings SonarQube (via SonarLint IDE, fichier par fichier)
sur ~24 fichiers static/js/ : parseFloat/parseInt -> Number.*,
.replace(/x/g,y) -> .replaceAll, .indexOf() -> .includes()/.startsWith(),
getAttribute/setAttribute -> .dataset, tableaux -> Set, x && x.y -> x?.y
(verifie site par site), extraction de template litteraux imbriques,
ternaires imbriquees, refactors de complexite cognitive (S3776) via
tables de dispatch, Object.hasOwn, .at(), et deduplication de fonctions
identiques (S4144). Deux exceptions S2486 documentees/corrigees
(filter-repeater-rows.js) et un cas S2703 de partage inter-scripts
complete (_collisionWizard, trigger-editor.js <-> collision-rules-editor.js).
Details complets dans CODE_QUALITY.md section 5.

Restaure aussi le job CI "sonarqube" (non-bloquant) dans
.gitea/workflows/deploy.yml maintenant que l'instance prod est
operationnelle.

Suites vertes : 276/276 JS (node --test), 591/591 Python (pytest).

SKIP=djlint sur ce commit : hook djlint bloquant sur le backlog H021
(styles inline, 49 occurrences/6 templates) deja documente comme dette
assumee non traitee dans CODE_QUALITY.md section 6, aucun rapport avec
ce commit (aucun template touche ici) - valide explicitement avec
l'utilisateur avant de contourner ce hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 16:38:29 +02:00

103 lines
5.0 KiB
JavaScript

// Export Web/SCORM (voir le plan "port complet du runtime jouable côté
// navigateur") — port fidèle de screens/data_actions/compute_operation.py,
// utilisé UNIQUEMENT quand window.FORGE_OFFLINE est vrai (paquet exporté,
// aucun serveur disponible) — voir apply-actions.js, les deux seuls
// endroits qui en ont besoin. Toute divergence avec la version Python doit
// être volontaire et commentée (ex. modulo/int()).
// int() Python tronque vers zéro — Math.trunc fait exactement ça
// (contrairement à Math.floor, qui arrondit vers -l'infini).
function forgePyInt(n) {
return Math.trunc(n);
}
// Le signe du résultat de "%" suit le DIVISEUR en Python, le DIVIDENDE en
// JS — sans ce correctif, "modulo" donnerait un résultat différent de
// compute_operation.py dès qu'une des deux valeurs est négative.
function forgePyMod(a, b) {
return ((a % b) + b) % b;
}
function computeDefinirNombre(rawValue, round) {
const n = Number.parseFloat(rawValue);
return Number.isNaN(n) ? 0 : round(n);
}
// Partagée entre "incrementer" et "decrementer" (même calcul, signe
// inversé) — extrait de forgeComputeNewValue (lot 4 "modernisation JS", S3776).
function computeIncrementerOuDecrementer(operation, current, rawValue, round) {
let amount = (rawValue === null || rawValue === undefined || rawValue === '') ? 0 : Number.parseFloat(rawValue);
if (Number.isNaN(amount)) amount = 0;
if (operation === 'decrementer') amount = -amount;
return round((current || 0) + amount);
}
function computeMultiplier(current, rawValue, round) {
let factor = (rawValue === null || rawValue === undefined || rawValue === '') ? 1 : Number.parseFloat(rawValue);
if (Number.isNaN(factor)) factor = 1;
return round((current || 0) * factor);
}
// Partagée entre "diviser" et "modulo" — extrait de forgeComputeNewValue.
function computeDiviserOuModulo(operation, current, rawValue, round) {
let divisor = (rawValue === null || rawValue === undefined || rawValue === '') ? 0 : Number.parseFloat(rawValue);
if (Number.isNaN(divisor)) divisor = 0;
if (divisor === 0) return current;
const result = operation === 'diviser' ? (current || 0) / divisor : forgePyMod(current || 0, divisor);
return round(result);
}
// Partagée entre "minimum" et "maximum" — extrait de forgeComputeNewValue.
function computeMinimumOuMaximum(operation, current, rawValue, round) {
const bound = Number.parseFloat(rawValue);
if (Number.isNaN(bound)) return current;
const res = operation === 'minimum' ? Math.max(current || 0, bound) : Math.min(current || 0, bound);
return round(res);
}
function computeAlea(rawValue, isDecimal) {
const parts = String(rawValue || '').split(',');
let lo = Number.parseFloat(parts[0]); let
hi = Number.parseFloat(parts[1]);
if (Number.isNaN(lo)) lo = 0;
if (Number.isNaN(hi)) hi = 0;
if (lo > hi) { const tmp = lo; lo = hi; hi = tmp; }
if (isDecimal) return Math.random() * (hi - lo) + lo; // NOSONAR S2245 - tirage de jeu, jamais crypto
const iLo = Math.trunc(lo); const
iHi = Math.trunc(hi);
return Math.floor(Math.random() * (iHi - iLo + 1)) + iLo; // NOSONAR S2245 - tirage de jeu, jamais crypto
}
// Dispatch par nom d'opération — chaque fonction ci-dessus reçoit déjà
// tout ce dont elle a besoin (current/rawValue/round/isDecimal), ce
// dispatcher n'a donc plus qu'une seule décision (présence d'un
// handler), au lieu d'empiler ici les conditions de chaque branche (même
// méthode que runActionNode, static/js/play/actions.js).
const OPERATION_HANDLERS = {
definir_texte: (current, rawValue) => rawValue || '',
definir_nombre: (current, rawValue, round) => computeDefinirNombre(rawValue, round),
definir_bool_vrai: () => 1,
definir_bool_faux: () => 0,
basculer_bool: (current) => (current ? 0 : 1),
incrementer: (current, rawValue, round) => computeIncrementerOuDecrementer('incrementer', current, rawValue, round),
decrementer: (current, rawValue, round) => computeIncrementerOuDecrementer('decrementer', current, rawValue, round),
multiplier: (current, rawValue, round) => computeMultiplier(current, rawValue, round),
diviser: (current, rawValue, round) => computeDiviserOuModulo('diviser', current, rawValue, round),
modulo: (current, rawValue, round) => computeDiviserOuModulo('modulo', current, rawValue, round),
minimum: (current, rawValue, round) => computeMinimumOuMaximum('minimum', current, rawValue, round),
maximum: (current, rawValue, round) => computeMinimumOuMaximum('maximum', current, rawValue, round),
alea: (current, rawValue, round, isDecimal) => computeAlea(rawValue, isDecimal),
};
function forgeComputeNewValue(operation, current, rawValue, isDecimal) {
const round = function (v) { return isDecimal ? v : forgePyInt(v); };
const handler = OPERATION_HANDLERS[operation];
if (!handler) throw new Error(`opération inconnue : ${operation}`);
return handler(current, rawValue, round, isDecimal);
}
// static/js/play/__tests__/ (node:test) — même patron que conditions.js.
if (typeof module !== 'undefined' && module.exports) {
module.exports = { forgeComputeNewValue, forgePyInt, forgePyMod };
}