Spaces:
Runtime error
Runtime error
| // scripts/check/check-cognitive-complexity.mjs | |
| // Ratchet bloqueante para complexidade cognitiva (sonarjs/cognitive-complexity). | |
| // Fase 7 INT: promovido de ADVISORY para RATCHET. | |
| // | |
| // Roda o ESLint sobre src+open-sse usando um config flat STANDALONE | |
| // (eslint.sonarjs.config.mjs) que liga APENAS `sonarjs/cognitive-complexity` — | |
| // mantendo a contagem ISOLADA do orçamento de warnings do lint principal. | |
| // | |
| // Lê o baseline de quality-baseline.json (metrics.cognitiveComplexity). | |
| // Falha com exit 1 se a contagem SUBIR. Suporta --update. | |
| // | |
| // Saída canônica: cognitiveComplexity=N (parseable por collect-metrics.mjs) | |
| // | |
| // Uso: | |
| // node scripts/check/check-cognitive-complexity.mjs | |
| // node scripts/check/check-cognitive-complexity.mjs --quiet # só a linha canônica | |
| // node scripts/check/check-cognitive-complexity.mjs --update # ratcheta baseline se melhorou | |
| import { execFileSync } from "node:child_process"; | |
| import fs from "node:fs"; | |
| import path from "node:path"; | |
| import { pathToFileURL } from "node:url"; | |
| const ROOT = process.cwd(); | |
| const QUIET = process.argv.includes("--quiet"); | |
| const UPDATE = process.argv.includes("--update"); | |
| const CONFIG_PATH = path.join(ROOT, "eslint.sonarjs.config.mjs"); | |
| const ESLINT_BIN = path.join(ROOT, "node_modules", ".bin", "eslint"); | |
| const BASELINE_PATH = path.resolve( | |
| process.argv.includes("--baseline") | |
| ? process.argv[process.argv.indexOf("--baseline") + 1] | |
| : path.join(ROOT, "config/quality/quality-baseline.json") | |
| ); | |
| const ESLINT_ARGS = [ | |
| "--no-config-lookup", | |
| "--config", | |
| CONFIG_PATH, | |
| "--format", | |
| "json", | |
| "src", | |
| "open-sse", | |
| ]; | |
| /** | |
| * Parses the ESLint JSON output (array of file results) and counts total | |
| * `sonarjs/cognitive-complexity` violations. | |
| * | |
| * Exported so unit tests can call it directly with synthetic data. | |
| * | |
| * @param {Array<{messages: Array<{ruleId: string}>}>} report | |
| * @returns {number} | |
| */ | |
| export function countCognitiveViolations(report) { | |
| let count = 0; | |
| for (const file of report) { | |
| for (const msg of file.messages) { | |
| if (msg.ruleId === "sonarjs/cognitive-complexity") { | |
| count++; | |
| } | |
| } | |
| } | |
| return count; | |
| } | |
| /** | |
| * Avalia a contagem atual de violações cognitivas contra o baseline. | |
| * Direction: down (contagem só pode CAIR). | |
| * | |
| * Exported for unit testing. | |
| * | |
| * @param {number} current | |
| * @param {number} baseline | |
| * @returns {{ regressed: boolean, improved: boolean }} | |
| */ | |
| export function evaluateCognitiveComplexity(current, baseline) { | |
| return { | |
| regressed: current > baseline, | |
| improved: current < baseline, | |
| }; | |
| } | |
| function runEslint() { | |
| let stdout; | |
| try { | |
| stdout = execFileSync(ESLINT_BIN, ESLINT_ARGS, { | |
| encoding: "utf8", | |
| maxBuffer: 64 * 1024 * 1024, | |
| }); | |
| } catch (err) { | |
| // ESLint exits non-zero when there are lint errors; the JSON report is still | |
| // in stdout. Re-throw only if there is no parseable output. | |
| stdout = err.stdout ? String(err.stdout) : ""; | |
| if (!stdout.trim()) throw err; | |
| } | |
| return JSON.parse(stdout); | |
| } | |
| function main() { | |
| if (!fs.existsSync(BASELINE_PATH)) { | |
| process.stderr.write( | |
| `[cognitive-complexity] FAIL — ${path.basename(BASELINE_PATH)} ausente.\n` | |
| ); | |
| process.exit(2); | |
| } | |
| const baselineJson = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8")); | |
| const baselineMetric = baselineJson.metrics && baselineJson.metrics.cognitiveComplexity; | |
| if (!baselineMetric || typeof baselineMetric.value !== "number") { | |
| process.stderr.write( | |
| "[cognitive-complexity] FAIL — metrics.cognitiveComplexity ausente em quality-baseline.json.\n" | |
| ); | |
| process.exit(2); | |
| } | |
| const baselineValue = baselineMetric.value; | |
| const report = runEslint(); | |
| const count = countCognitiveViolations(report); | |
| // Canonical machine-readable output consumed by collect-metrics.mjs and shell scripts. | |
| console.log(`cognitiveComplexity=${count}`); | |
| if (!QUIET) { | |
| console.log( | |
| `[cognitive-complexity] ${count} function(s) exceed the cognitive-complexity threshold (15).` | |
| ); | |
| } | |
| const { regressed, improved } = evaluateCognitiveComplexity(count, baselineValue); | |
| if (UPDATE && improved) { | |
| baselineJson.metrics.cognitiveComplexity.value = count; | |
| fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineJson, null, 2) + "\n"); | |
| console.log(`[cognitive-complexity] baseline ratcheado: ${count} (era ${baselineValue})`); | |
| } | |
| if (regressed) { | |
| process.stderr.write( | |
| `[cognitive-complexity] REGRESSÃO — ${count} violações > baseline ${baselineValue}\n` + | |
| ` → Quebre as funções complexas em helpers menores, ou rode\n` + | |
| ` 'node scripts/check/check-cognitive-complexity.mjs --update' se a contagem caiu legitimamente.\n` | |
| ); | |
| process.exit(1); | |
| } | |
| if (!QUIET) { | |
| console.log(`[cognitive-complexity] OK — ${count} violações (baseline ${baselineValue})`); | |
| } | |
| process.exit(0); | |
| } | |
| if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main(); | |