@mocoto/mahoraga 0.14.4 → 0.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -39
- package/dist/analysts/architects/archetypes-custom.js +1 -1
- package/dist/analysts/architects/detector-archetypes.js +1 -0
- package/dist/analysts/architects/strategist-structure.js +1 -1
- package/dist/analysts/azure-pipelines/corrections/correction-azure.js +1 -0
- package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +1 -0
- package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +1 -0
- package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +1 -0
- package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +1 -0
- package/dist/analysts/circleci/detectors/detector-circleci-performance.js +1 -0
- package/dist/analysts/circleci/detectors/detector-circleci-security.js +1 -0
- package/dist/analysts/circleci/detectors/detector-circleci-structure.js +9 -10
- package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +1 -0
- package/dist/analysts/corrections/corrector-structure.js +2 -2
- package/dist/analysts/corrections/fix-md-fences.js +1 -0
- package/dist/analysts/corrections/map-reversion.js +1 -0
- package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +1 -0
- package/dist/analysts/corrections/reorganize-barrels.js +1 -0
- package/dist/analysts/corrections/scoring-shared.js +1 -0
- package/dist/analysts/corrections/type-safety/context-analyzer.js +1 -1
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +1 -0
- package/dist/analysts/css/analysts/analyst-css.js +3 -2
- package/dist/analysts/css/corrections/correction-css.js +1 -0
- package/dist/analysts/css/detectors/detector-css-best-practices.js +1 -0
- package/dist/analysts/css/scorers/scorer-css.js +1 -0
- package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +1 -0
- package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +1 -1
- package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +2 -2
- package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +2 -2
- package/dist/analysts/detectors/detector-bugs-ml.js +4 -2
- package/dist/analysts/detectors/detector-dependencies.js +1 -0
- package/dist/analysts/detectors/detector-monorepo.js +1 -0
- package/dist/analysts/detectors/detector-security.js +1 -1
- package/dist/analysts/github-actions/corrections/correction-workflow.js +1 -0
- package/dist/analysts/github-actions/detectors/detector-dependencies-vulnerable.js +1 -0
- package/dist/analysts/github-actions/detectors/detector-workflow-compliance.js +1 -0
- package/dist/analysts/github-actions/detectors/detector-workflow-performance.js +1 -0
- package/dist/analysts/github-actions/detectors/detector-workflow-security.js +1 -0
- package/dist/analysts/github-actions/detectors/detector-workflow-structure.js +1 -0
- package/dist/analysts/github-actions/detectors/detector-workflow-trigger-unsafe.js +1 -0
- package/dist/analysts/github-actions/scorers/scorer-workflow.js +1 -0
- package/dist/analysts/gitlab-ci/analysts/analyst-gitlab-ci.js +1 -0
- package/dist/analysts/gitlab-ci/corrections/correction-gitlab-ci.js +1 -0
- package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-performance.js +1 -0
- package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-security.js +1 -0
- package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-structure.js +1 -0
- package/dist/analysts/go/analysts/analyst-best-practices.js +1 -0
- package/dist/analysts/go/analysts/analyst-concurrency.js +1 -0
- package/dist/analysts/go/analysts/analyst-go.js +1 -0
- package/dist/analysts/go/analysts/analyst-resources.js +1 -0
- package/dist/analysts/go/detectors/detector-conventions.js +1 -0
- package/dist/analysts/go/detectors/detector-errors-un-handled.js +1 -0
- package/dist/analysts/go/detectors/detector-vulnerabilities.js +1 -0
- package/dist/analysts/html/analysts/analyst-html.js +1 -0
- package/dist/analysts/html/corrections/correction-html.js +1 -0
- package/dist/analysts/html/detectors/detector-html-accessibility.js +1 -0
- package/dist/analysts/html/detectors/detector-html.js +1 -0
- package/dist/analysts/jenkins/analysts/analyst-jenkins.js +1 -0
- package/dist/analysts/jenkins/corrections/correction-jenkins.js +1 -0
- package/dist/analysts/jenkins/detectors/detector-jenkins-best-practices.js +1 -0
- package/dist/analysts/jenkins/detectors/detector-jenkins-security.js +1 -0
- package/dist/analysts/jenkins/scorers/scorer-jenkins.js +1 -0
- package/dist/analysts/js-ts/analysts/analyst-todo-comments.js +1 -6
- package/dist/analysts/js-ts/detectors/detector-context-smart.js +1 -0
- package/dist/analysts/js-ts/detectors/detector-interfaces-inline.js +1 -0
- package/dist/analysts/js-ts/detectors/detector-leaks-memory.js +1 -0
- package/dist/analysts/js-ts/detectors/detector-types-unsafe.js +88 -5
- package/dist/analysts/php/analysts/analyst-php.js +1 -0
- package/dist/analysts/php/detectors/detector-php-best-practices.js +1 -0
- package/dist/analysts/php/detectors/detector-php-security.js +1 -0
- package/dist/analysts/plugins/analyst-formatter.js +1 -0
- package/dist/analysts/plugins/detector-markdown.js +1 -0
- package/dist/analysts/python/analysts/analyst-python.js +2 -1
- package/dist/analysts/python/corrections/correction-python.js +1 -0
- package/dist/analysts/python/scorers/scorer-python.js +1 -0
- package/dist/analysts/react/analysts/analyst-react-hooks.js +109 -12
- package/dist/analysts/react/analysts/analyst-react.js +12 -0
- package/dist/analysts/react/detectors/detector-react-best-practices.js +24 -1
- package/dist/analysts/react/scorers/scorer-react.js +4 -1
- package/dist/analysts/registry/autodiscovery.js +1 -0
- package/dist/analysts/rust/analysts/analyst-best-practices.js +1 -0
- package/dist/analysts/rust/analysts/analyst-concurrency.js +1 -0
- package/dist/analysts/rust/analysts/analyst-memory.js +1 -0
- package/dist/analysts/rust/analysts/analyst-rust.js +1 -0
- package/dist/analysts/rust/analysts/analyst-security.js +1 -0
- package/dist/analysts/rust/detectors/detector-conventions.js +1 -0
- package/dist/analysts/rust/detectors/detector-errors-potential.js +1 -0
- package/dist/analysts/shell/analysts/analyst-shell.js +1 -0
- package/dist/analysts/shell/corrections/correction-shell.js +1 -0
- package/dist/analysts/shell/detectors/detector-shell-security.js +1 -0
- package/dist/analysts/shell/scorers/scorer-shell.js +1 -0
- package/dist/analysts/sql/corrections/correction-sql.js +1 -0
- package/dist/analysts/sql/detectors/detector-sql.js +1 -0
- package/dist/analysts/sql/scorers/scorer-sql.js +1 -0
- package/dist/analysts/strategists/operator-structure.js +1 -1
- package/dist/analysts/strategists/suggestions-contextuais.js +1 -0
- package/dist/analysts/svg/analysts/analyst-svg.js +1 -0
- package/dist/analysts/svg/corrections/correction-svg.js +1 -0
- package/dist/analysts/svg/detectors/detector-svg-security.js +1 -0
- package/dist/analysts/tailwind/analysts/analyst-tailwind.js +6 -2
- package/dist/analysts/tailwind/corrections/correction-tailwind.js +1 -0
- package/dist/analysts/tailwind/detectors/detector-tailwind-best-practices.js +1 -0
- package/dist/analysts/xml/analysts/analyst-xml.js +1 -0
- package/dist/analysts/xml/corrections/correction-xml.js +1 -0
- package/dist/analysts/xml/detectors/detector-xml-security.js +1 -0
- package/dist/analysts/xml/scorers/scorer-xml.js +1 -0
- package/dist/app/github.js +1 -0
- package/dist/bin/cli-bootstrap.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/caretakers/caretaker-imports.js +1 -0
- package/dist/cli/commands/command-analysts.js +1 -0
- package/dist/cli/commands/command-azure.js +1 -0
- package/dist/cli/commands/command-barrels.js +1 -0
- package/dist/cli/commands/command-circleci.js +1 -0
- package/dist/cli/commands/command-compliance.js +1 -0
- package/dist/cli/commands/command-convert.js +1 -0
- package/dist/cli/commands/command-diagnose.js +1 -1
- package/dist/cli/commands/command-fix-types.js +1 -1
- package/dist/cli/commands/command-format.js +1 -0
- package/dist/cli/commands/command-formatters.js +1 -0
- package/dist/cli/commands/command-github-actions.js +1 -0
- package/dist/cli/commands/command-gitlab-ci.js +1 -0
- package/dist/cli/commands/command-guardian.js +1 -1
- package/dist/cli/commands/command-imports.js +1 -0
- package/dist/cli/commands/command-jenkins.js +1 -0
- package/dist/cli/commands/command-licenses.js +1 -0
- package/dist/cli/commands/command-marketplace.js +1 -0
- package/dist/cli/commands/command-metrics.js +1 -1
- package/dist/cli/commands/command-names.js +1 -0
- package/dist/cli/commands/command-optimize-svg.js +1 -0
- package/dist/cli/commands/command-perf.js +1 -0
- package/dist/cli/commands/command-plugins.js +1 -0
- package/dist/cli/commands/command-prune.js +1 -0
- package/dist/cli/commands/command-reverter.js +1 -0
- package/dist/cli/commands/command-update.js +1 -0
- package/dist/cli/diagnostic/auto-fix.js +1 -0
- package/dist/cli/diagnostic/exporters/json-exporter.js +1 -0
- package/dist/cli/diagnostic/exporters/sharded-exporter.js +1 -1
- package/dist/cli/diagnostic/handlers/auto-fix-handler.js +1 -0
- package/dist/cli/diagnostic/handlers/guardian-handler.js +1 -0
- package/dist/cli/diagnostic/processing-diagnostic.js +1 -0
- package/dist/cli/diagnostic/processing-helpers.js +1 -0
- package/dist/cli/handlers/command-vulnerabilities.js +1 -0
- package/dist/cli/handlers/fix-types-exporter.js +1 -0
- package/dist/cli/handlers/guardian-exporter.js +1 -0
- package/dist/cli/helpers/display-frame.js +1 -0
- package/dist/core/config/auto/validation.js +1 -0
- package/dist/core/config/config.js +1 -0
- package/dist/core/config/excludes-padrao.js +1 -0
- package/dist/core/execution/executor.js +1 -0
- package/dist/core/execution/inquisitor.js +1 -0
- package/dist/core/formatters/registro-builtin.js +1 -0
- package/dist/core/messages/en/analysts/detector-react-messages.js +3 -0
- package/dist/core/messages/en/analysts/report-circleci-messages.js +5 -5
- package/dist/core/messages/en/cli/cli-command-marketplace-messages.js +1 -0
- package/dist/core/messages/en/core/plugin-messages.js +1 -1
- package/dist/core/messages/en/log/log-helper.js +1 -1
- package/dist/core/messages/en/ui/suggestions.js +1 -1
- package/dist/core/messages/ja/analysts/report-circleci-messages.js +5 -5
- package/dist/core/messages/ja/core/plugin-messages.js +1 -1
- package/dist/core/messages/ja/log/log-helper.js +1 -1
- package/dist/core/messages/ja/ui/suggestions.js +1 -1
- package/dist/core/messages/pt/analysts/detector-react-messages.js +3 -0
- package/dist/core/messages/pt/analysts/report-circleci-messages.js +5 -5
- package/dist/core/messages/pt/core/plugin-messages.js +1 -1
- package/dist/core/messages/pt/log/log-helper.js +1 -1
- package/dist/core/messages/pt/ui/suggestions.js +1 -1
- package/dist/core/messages/shared/icons.js +1 -0
- package/dist/core/messages/zh/analysts/report-circleci-messages.js +5 -5
- package/dist/core/messages/zh/core/plugin-messages.js +1 -1
- package/dist/core/messages/zh/log/log-helper.js +1 -1
- package/dist/core/messages/zh/ui/suggestions.js +1 -1
- package/dist/core/parsing/langs/css.js +1 -0
- package/dist/core/parsing/langs/html.js +1 -0
- package/dist/core/parsing/langs/json.js +1 -0
- package/dist/core/parsing/langs/php.js +1 -0
- package/dist/core/parsing/langs/python.js +1 -0
- package/dist/core/parsing/langs/rust.js +1 -0
- package/dist/core/parsing/langs/scss.js +1 -0
- package/dist/core/parsing/langs/shell.js +1 -0
- package/dist/core/parsing/langs/vue.js +1 -0
- package/dist/core/parsing/parser.js +1 -0
- package/dist/core/utils/exec-safe.js +23 -5
- package/dist/core/workers/worker-executor.js +1 -0
- package/dist/core/workers/worker-pool.js +0 -0
- package/dist/licenses/disclaimer.js +1 -0
- package/dist/licenses/generate-notices.js +1 -0
- package/dist/licenses/normalizer.js +1 -0
- package/dist/licenses/scanner.js +1 -0
- package/dist/node.loader.js +1 -1
- package/dist/reports/filter-smart.js +1 -1
- package/dist/reports/report-archetypes.js +1 -0
- package/dist/shared/data-processing/fragment-report.js +1 -0
- package/dist/shared/data-processing/json.js +1 -0
- package/dist/shared/formatters/engines/prettier.js +1 -0
- package/dist/shared/formatters/engines/stylelint.js +1 -0
- package/dist/shared/formatters/formatters/code.js +1 -0
- package/dist/shared/formatters/formatters/css.js +1 -0
- package/dist/shared/formatters/formatters/dockerfile.js +1 -0
- package/dist/shared/formatters/formatters/dotfiles.js +1 -0
- package/dist/shared/formatters/formatters/go.js +1 -0
- package/dist/shared/formatters/formatters/html.js +1 -0
- package/dist/shared/formatters/formatters/json.js +1 -0
- package/dist/shared/formatters/formatters/markdown.js +1 -0
- package/dist/shared/formatters/formatters/shell.js +1 -0
- package/dist/shared/formatters/formatters/sql.js +1 -0
- package/dist/shared/formatters/formatters/toml.js +1 -0
- package/dist/shared/formatters/formatters/xml.js +1 -0
- package/dist/shared/formatters/formatters/yaml.js +1 -0
- package/dist/shared/helpers/analyst-wrapper.js +1 -0
- package/dist/shared/helpers/detectors-common.js +1 -0
- package/dist/shared/helpers/framework-detector.js +1 -0
- package/dist/shared/helpers/imports.js +1 -1
- package/dist/shared/helpers/reader-report.js +1 -0
- package/dist/shared/helpers/rule-config.js +1 -0
- package/dist/shared/helpers/suppressao.js +1 -0
- package/dist/shared/marketplace/remote.js +1 -0
- package/dist/shared/persistence/persistence.js +3 -3
- package/dist/shared/plugins/registry.js +1 -0
- package/dist/types/analysts/css-in-js.js +2 -0
- package/dist/types/analysts/index.js +1 -0
- package/dist/types/processing/display.js +1 -1
- package/dist/types/processing/filters.js +1 -0
- package/dist/vulnerabilities/scanner.js +1 -0
- package/package.json +1 -1
- package/dist/index.js +0 -2
- package/dist/sdk/analyzer.js +0 -88
- package/dist/sdk/index.js +0 -2
- package/dist/sdk/sdk-legacy.js +0 -36
- package/dist/types/sdk/analyzer.js +0 -1
- package/dist/types/sdk/index.js +0 -1
|
@@ -125,6 +125,7 @@ const visitorMemoria = {
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
catch { }
|
|
128
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- path.traverse callbacks set these synchronously */
|
|
128
129
|
if (hasReturnFunction && referencesLargeData) {
|
|
129
130
|
state.closureLargeObjects.add(path.node.loc?.start.line ?? 0);
|
|
130
131
|
state.ocorrencias.push({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @mahoraga-disable ml-alta-probabilidade-bugs ml-deep-nesting
|
|
1
2
|
// SPDX-License-Identifier: MIT
|
|
2
3
|
/**
|
|
3
4
|
* Detector de tipos inseguros (any e unknown) - Versão Inteligente
|
|
@@ -253,6 +254,27 @@ const ANALISTA = {
|
|
|
253
254
|
});
|
|
254
255
|
}
|
|
255
256
|
// ─── DETECTAR 'as' QUE PODEM SER SUBSTITUÍDOS POR 'satisfies' ─
|
|
257
|
+
// Referência: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator
|
|
258
|
+
//
|
|
259
|
+
// PRINCÍPIO FUNDAMENTAL:
|
|
260
|
+
// 'as' (type assertion) MUDA o tipo da expressão — pode estreitar (narrow)
|
|
261
|
+
// de unknown→concreto, string→union, tipo→subtipo, ou até mentir.
|
|
262
|
+
// 'satisfies' apenas VALIDA — o tipo da expressão permanece o original.
|
|
263
|
+
//
|
|
264
|
+
// Portanto, 'satisfies' SÓ pode substituir 'as' sem quebrar o código
|
|
265
|
+
// quando a expressão é um LITERAL (objeto {...} ou array [...]).
|
|
266
|
+
// Nesse caso, 'satisfies' é ESTRITAMENTE MELHOR: valida a constraint
|
|
267
|
+
// sem widen (perda de inferência precisa).
|
|
268
|
+
//
|
|
269
|
+
// Para QUALQUER outra expressão (variável, função, propriedade, acesso
|
|
270
|
+
// indexado, parênteses, operador), 'as' está fazendo narrowing que
|
|
271
|
+
// 'satisfies' não replica — NUNCA sugerir satisfies.
|
|
272
|
+
//
|
|
273
|
+
// Exemplo clássico onde satisfies é melhor:
|
|
274
|
+
// const x = { red: [255,0,0], blue: [0,0,255] } as Record<string, number[]>
|
|
275
|
+
// → perde inferência de 'red' e 'blue' como tuplas
|
|
276
|
+
// const x = { red: [255,0,0], blue: [0,0,255] } satisfies Record<string, number[]>
|
|
277
|
+
// → valida a constraint, mantém inferência precisa das tuplas
|
|
256
278
|
const asTypePadrao = /\bas\s+([A-Za-z_$][A-Za-z0-9_$.]+)\b(?!\s*[=(])/g;
|
|
257
279
|
let asTypeMatch;
|
|
258
280
|
while ((asTypeMatch = asTypePadrao.exec(src)) !== null) {
|
|
@@ -262,12 +284,73 @@ const ANALISTA = {
|
|
|
262
284
|
}
|
|
263
285
|
const linha = splitLines(src.substring(0, position)).length;
|
|
264
286
|
const lineContext = splitLines(src)[linha - 1]?.trim() || '';
|
|
265
|
-
|
|
266
|
-
|
|
287
|
+
const tipoAsserido = asTypeMatch[1];
|
|
288
|
+
// Skip 'as any' — já detectado em seção anterior como erro
|
|
289
|
+
// Skip 'as const' — é literal narrowing, não type assertion
|
|
290
|
+
if (tipoAsserido === 'any' || tipoAsserido === 'const') {
|
|
267
291
|
continue;
|
|
268
292
|
}
|
|
269
|
-
//
|
|
270
|
-
|
|
293
|
+
// ─── FILTROS DE SEGURANÇA: padrões que NUNCA devem sugerir satisfies ───
|
|
294
|
+
// Import/export rename: 'import { x as y }', 'export { x as y }'
|
|
295
|
+
// Pega linhas que começam com import/export E linhas de continuação
|
|
296
|
+
// dentro de blocos import { ... } (linhas indentadas com 'as' entre vírgulas)
|
|
297
|
+
if (/^(import|export)\b/.test(lineContext)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
// Detecta 'as' dentro de blocos import/export em linhas de continuação:
|
|
301
|
+
// Padrão: linha indentada com 'identificador as identificador,' e há
|
|
302
|
+
// um import/export nas linhas anteriores próximas
|
|
303
|
+
const windowBefore = src.slice(Math.max(0, position - 500), position);
|
|
304
|
+
if (/^\s+\w+\s+as\s+\w+,?\s*$/.test(lineContext) &&
|
|
305
|
+
/(?:^|\n)\s*(?:import|export)\s*\{/.test(windowBefore)) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// Skip 'as' dentro de template literal ${...}
|
|
309
|
+
const beforePos = src.slice(Math.max(0, position - 120), position);
|
|
310
|
+
if (lineContext.includes('${') || beforePos.includes('${')) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
// Skip dupla asserção 'unknown as'
|
|
314
|
+
if (lineContext.includes('unknown as')) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
// Skip em arrow function: (x) => x as Type
|
|
318
|
+
if (/=>\s*\(?[^)]*\)?\s*as\s+\w+/.test(lineContext)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
// ─── VERIFICAÇÃO PRINCIPAL: expressão antes de 'as' é literal? ───
|
|
322
|
+
//
|
|
323
|
+
// A única situação onde 'satisfies' pode substituir 'as' com segurança
|
|
324
|
+
// é quando a expressão é um literal objeto `{...}` ou array `[...]`.
|
|
325
|
+
//
|
|
326
|
+
// Diferente da abordagem anterior (que usava 120 chars de janela e
|
|
327
|
+
// podia capturar { de linhas anteriores), aqui extraímos APENAS o
|
|
328
|
+
// texto na MESMA LINHA antes de 'as'.
|
|
329
|
+
//
|
|
330
|
+
// Critérios para identificar literal:
|
|
331
|
+
// 1. Objeto: texto (após remover declaração) começa com '{' e termina com '}'
|
|
332
|
+
// 2. Array: texto (após remover declaração) começa com '[' e termina com ']'
|
|
333
|
+
// 3. '{}' vazio → sentinel, pular
|
|
334
|
+
//
|
|
335
|
+
// Isso diferencia corretamente:
|
|
336
|
+
// - `{ a: 1 } as T` → objeto literal ✓
|
|
337
|
+
// - `[1, 2] as T` → array literal ✓
|
|
338
|
+
// - `arr[i] as T` → index access (começa com 'a', não '[') ✗
|
|
339
|
+
// - `found.ast as T` → property access (começa com 'f') ✗
|
|
340
|
+
// - `framework as T` → variável simples ✗
|
|
341
|
+
const lineText = splitLines(src)[linha - 1];
|
|
342
|
+
const colMatch = src.lastIndexOf('\n', Math.max(0, position - 1));
|
|
343
|
+
const posInLine = position - (colMatch >= 0 ? colMatch + 1 : 0);
|
|
344
|
+
const textBeforeOnLine = lineText.substring(0, Math.max(0, posInLine - 'as'.length)).trimEnd();
|
|
345
|
+
// Remove declaração (const/let/var/return/yield) para ver a expressão pura
|
|
346
|
+
const exprText = textBeforeOnLine
|
|
347
|
+
.replace(/^(?:const|let|var|return|yield)\s+\w+\s*=\s*/, '')
|
|
348
|
+
.trim();
|
|
349
|
+
// Verifica se é literal: abre e fecha com {}/{ ou [ /]
|
|
350
|
+
const isObjectLiteral = /\}\s*$/.test(exprText) && /^\s*\{/.test(exprText) && !/^\s*\{\s*\}\s*$/.test(exprText);
|
|
351
|
+
const isArrayLiteral = /\]\s*$/.test(exprText) && /^\s*\[/.test(exprText);
|
|
352
|
+
// Se NÃO é literal → é narrowing, NUNCA sugerir satisfies
|
|
353
|
+
if (!isObjectLiteral && !isArrayLiteral) {
|
|
271
354
|
continue;
|
|
272
355
|
}
|
|
273
356
|
if (shouldSuppressOccurrence('tipo-inseguro-as-satisfies', relPath)) {
|
|
@@ -276,7 +359,7 @@ const ANALISTA = {
|
|
|
276
359
|
const mensagemCompleta = `${messages.DetectorJsTsMensagens.satisfiesAvailable} | [IDEIA] ${messages.DetectorJsTsMensagens.satisfiesAvailableSugestao}`;
|
|
277
360
|
ocorrencias.push({
|
|
278
361
|
tipo: 'tipo-inseguro-as-satisfies',
|
|
279
|
-
nivel: '
|
|
362
|
+
nivel: 'aviso',
|
|
280
363
|
mensagem: mensagemCompleta,
|
|
281
364
|
relPath,
|
|
282
365
|
linha,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @mahoraga-disable ml-deep-nesting
|
|
1
2
|
// SPDX-License-Identifier: MIT
|
|
2
3
|
import { messages } from '../../../core/messages/index.js';
|
|
3
4
|
import { createLineLookup, maskPythonComments, maskPythonStringsAndComments, splitLines } from '../../../shared/helpers/index.js';
|
|
@@ -189,7 +190,7 @@ export function collectPythonIssues(src, relPath) {
|
|
|
189
190
|
ocorrencias.push(warn(messages.PythonMensagens.shadowBuiltin, relPath, line, messages.SeverityNiveis.info));
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
|
-
//
|
|
193
|
+
// Task/FIXME comments (on original src, before masking)
|
|
193
194
|
for (const match of src.matchAll(/#\s*(TODO|FIXME|HACK|XXX|BUG|WORKAROUND)[:\s]/gi)) {
|
|
194
195
|
const line = lineOf(match.index);
|
|
195
196
|
ocorrencias.push(warn(messages.PythonMensagens.todoFixme, relPath, line, messages.SeverityNiveis.info));
|
|
@@ -33,8 +33,63 @@ function extractHookCallContent(src, startIndex) {
|
|
|
33
33
|
let depth = 0;
|
|
34
34
|
let started = false;
|
|
35
35
|
let content = '';
|
|
36
|
+
let inSingle = false;
|
|
37
|
+
let inDouble = false;
|
|
38
|
+
let inBacktick = false;
|
|
39
|
+
let escaped = false;
|
|
36
40
|
for (let i = startIndex; i < src.length; i++) {
|
|
37
41
|
const char = src[i];
|
|
42
|
+
if (escaped) {
|
|
43
|
+
escaped = false;
|
|
44
|
+
if (started)
|
|
45
|
+
content += char;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '\\' && (inSingle || inDouble || inBacktick)) {
|
|
49
|
+
escaped = true;
|
|
50
|
+
if (started)
|
|
51
|
+
content += char;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (inSingle) {
|
|
55
|
+
if (char === "'")
|
|
56
|
+
inSingle = false;
|
|
57
|
+
if (started)
|
|
58
|
+
content += char;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (inDouble) {
|
|
62
|
+
if (char === '"')
|
|
63
|
+
inDouble = false;
|
|
64
|
+
if (started)
|
|
65
|
+
content += char;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (inBacktick) {
|
|
69
|
+
if (char === '`')
|
|
70
|
+
inBacktick = false;
|
|
71
|
+
if (started)
|
|
72
|
+
content += char;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (char === "'") {
|
|
76
|
+
inSingle = true;
|
|
77
|
+
if (started)
|
|
78
|
+
content += char;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"') {
|
|
82
|
+
inDouble = true;
|
|
83
|
+
if (started)
|
|
84
|
+
content += char;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (char === '`') {
|
|
88
|
+
inBacktick = true;
|
|
89
|
+
if (started)
|
|
90
|
+
content += char;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
38
93
|
if (char === '(') {
|
|
39
94
|
depth++;
|
|
40
95
|
started = true;
|
|
@@ -59,7 +114,22 @@ function collectHookIssues(src, relPath) {
|
|
|
59
114
|
effectMatches.forEach(m => {
|
|
60
115
|
const hookInicio = m.index;
|
|
61
116
|
const fullCall = extractHookCallContent(src, hookInicio + m[0].length - 1);
|
|
62
|
-
|
|
117
|
+
// More robust deps detection: find the last comma followed by an array or identifier before the closing paren
|
|
118
|
+
const hasDepsArg = (() => {
|
|
119
|
+
// Find the last ')' (closing paren of the full call)
|
|
120
|
+
const lastClosing = fullCall.lastIndexOf(')');
|
|
121
|
+
if (lastClosing === -1)
|
|
122
|
+
return false;
|
|
123
|
+
const beforeClosing = fullCall.slice(0, lastClosing);
|
|
124
|
+
// Find the last '[' or identifier before the closing paren
|
|
125
|
+
const arrayMatch = beforeClosing.match(/,\s*(\[[\s\S]*?\])\s*$/);
|
|
126
|
+
if (arrayMatch)
|
|
127
|
+
return true;
|
|
128
|
+
const identMatch = beforeClosing.match(/,\s*(\w+)\s*$/);
|
|
129
|
+
if (identMatch)
|
|
130
|
+
return true;
|
|
131
|
+
return false;
|
|
132
|
+
})();
|
|
63
133
|
const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
|
|
64
134
|
if (!hasDepsArg && !skip) {
|
|
65
135
|
const line = lineOf(hookInicio);
|
|
@@ -89,10 +159,18 @@ function collectHookIssues(src, relPath) {
|
|
|
89
159
|
ocorrencias.push(warn(messages.ReactHooksMensagens.memoCallbackNoDeps, relPath, line));
|
|
90
160
|
}
|
|
91
161
|
});
|
|
92
|
-
// Conditional hooks
|
|
93
|
-
|
|
162
|
+
// Conditional hooks — only flag when a hook call is directly inside a conditional block,
|
|
163
|
+
// not when the conditional is inside a hook callback (e.g. cleanup inside useEffect).
|
|
164
|
+
// The regex matches: if/for/while (...) { ... useXxx ... } but limits the match scope
|
|
165
|
+
// to avoid matching conditionals that appear inside hook callbacks.
|
|
166
|
+
const conditionalHooks = [...src.matchAll(/(?:^|(?<=[\n;]))\s*(if|for|while)\s*\([^)]*\)\s*\{[\s\S]{0,160}?use[A-Z][A-Za-z0-9_]*/g)];
|
|
94
167
|
conditionalHooks.forEach(m => {
|
|
95
168
|
const line = lineOf(m.index || 0);
|
|
169
|
+
// Heuristic: skip if the 'if' keyword is preceded by a closing paren or brace at the same indentation,
|
|
170
|
+
// which suggests it's inside a callback body rather than at the component top level.
|
|
171
|
+
const pre = src.slice(Math.max(0, (m.index ?? 0) - 80), m.index ?? 0);
|
|
172
|
+
if (/=>\s*$|\)\s*=>\s*$|\)\s*\{[\s\S]{0,40}$/.test(pre))
|
|
173
|
+
return;
|
|
96
174
|
ocorrencias.push(warn(messages.ReactHooksMensagens.hookInConditional, relPath, line));
|
|
97
175
|
});
|
|
98
176
|
// React 19: useOptimistic detected
|
|
@@ -103,14 +181,14 @@ function collectHookIssues(src, relPath) {
|
|
|
103
181
|
// React 19: useActionState detected
|
|
104
182
|
for (const match of src.matchAll(/\buseActionState\s*\(/g)) {
|
|
105
183
|
const line = lineOf(match.index);
|
|
106
|
-
ocorrencias.push(warn('useActionState (React 19) detected
|
|
184
|
+
ocorrencias.push(warn('useActionState (React 19) detected - verify form action integration', relPath, line, messages.SeverityNiveis.info));
|
|
107
185
|
}
|
|
108
186
|
// React 19: useFormStatus detected
|
|
109
187
|
for (const match of src.matchAll(/\buseFormStatus\s*\(/g)) {
|
|
110
188
|
const line = lineOf(match.index);
|
|
111
|
-
ocorrencias.push(warn('useFormStatus (React 19) detected
|
|
189
|
+
ocorrencias.push(warn('useFormStatus (React 19) detected - access pending form state', relPath, line, messages.SeverityNiveis.info));
|
|
112
190
|
}
|
|
113
|
-
// React 19: use() hook
|
|
191
|
+
// React 19: use() hook - suspends, must be in Suspense boundary
|
|
114
192
|
for (const match of src.matchAll(/\buse\s*\(/g)) {
|
|
115
193
|
const line = lineOf(match.index);
|
|
116
194
|
// Avoid matching "useState", "useEffect", etc.
|
|
@@ -118,7 +196,7 @@ function collectHookIssues(src, relPath) {
|
|
|
118
196
|
if (src[match.index - 1]?.match(/[a-zA-Z]/)) {
|
|
119
197
|
continue;
|
|
120
198
|
}
|
|
121
|
-
ocorrencias.push(warn('React 19 use() hook detected
|
|
199
|
+
ocorrencias.push(warn('React 19 use() hook detected - must be wrapped in <Suspense> boundary', relPath, line, messages.SeverityNiveis.error));
|
|
122
200
|
}
|
|
123
201
|
// Custom hooks naming convention
|
|
124
202
|
const customHooks = [...src.matchAll(/\buse[A-Z][A-Za-z0-9_]*\s*=\s*\(/g)];
|
|
@@ -127,7 +205,7 @@ function collectHookIssues(src, relPath) {
|
|
|
127
205
|
if (!ALL_BUILTIN_HOOKS.includes(hookName)) {
|
|
128
206
|
if (/^use[A-Z]/.test(hookName)) {
|
|
129
207
|
const line = lineOf(m.index || 0);
|
|
130
|
-
ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line));
|
|
208
|
+
ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line, messages.SeverityNiveis.info));
|
|
131
209
|
}
|
|
132
210
|
}
|
|
133
211
|
});
|
|
@@ -182,7 +260,26 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
182
260
|
const isMemoLike = (name) => name === 'useMemo' || name === 'useCallback';
|
|
183
261
|
const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name) || name === 'use';
|
|
184
262
|
const inConditionalOrLoop = (path) => {
|
|
185
|
-
|
|
263
|
+
// Only consider conditionals/loops in the SAME function scope, not inside nested callbacks.
|
|
264
|
+
// Stop climbing at the nearest function boundary to avoid false positives when
|
|
265
|
+
// a hook is used inside a callback that happens to be in a conditional context.
|
|
266
|
+
let current = path;
|
|
267
|
+
while (current) {
|
|
268
|
+
const parent = current.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression());
|
|
269
|
+
if (parent)
|
|
270
|
+
return true;
|
|
271
|
+
// Climb to the nearest function boundary; stop if we hit one
|
|
272
|
+
const fnBoundary = current.findParent(p => p.isFunction());
|
|
273
|
+
if (!fnBoundary)
|
|
274
|
+
break;
|
|
275
|
+
// If the fnBoundary is the component function itself, we've exhausted this scope
|
|
276
|
+
// Check if there's a conditional ABOVE this function boundary
|
|
277
|
+
const aboveFn = fnBoundary.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression());
|
|
278
|
+
if (aboveFn)
|
|
279
|
+
return true;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
186
283
|
};
|
|
187
284
|
const getIdentifiersInNode = (node) => {
|
|
188
285
|
const ids = new Set();
|
|
@@ -254,7 +351,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
254
351
|
}
|
|
255
352
|
// React 19: use() hook should be in Suspense boundary
|
|
256
353
|
if (name === 'use') {
|
|
257
|
-
pushOnce(warn('React 19 use() hook
|
|
354
|
+
pushOnce(warn('React 19 use() hook - ensure wrapped in <Suspense> boundary', relPath, locLine));
|
|
258
355
|
}
|
|
259
356
|
// React 19: useOptimistic should have reducer function
|
|
260
357
|
if (name === 'useOptimistic' && path.node.arguments.length < 2) {
|
|
@@ -262,7 +359,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
262
359
|
}
|
|
263
360
|
// React 19: useActionState should have form action
|
|
264
361
|
if (name === 'useActionState') {
|
|
265
|
-
pushOnce(warn('useActionState (React 19)
|
|
362
|
+
pushOnce(warn('useActionState (React 19) - verify form action handler', relPath, locLine));
|
|
266
363
|
}
|
|
267
364
|
if (isEffectLike(name) && path.node.arguments.length >= 2) {
|
|
268
365
|
const callback = path.node.arguments[0];
|
|
@@ -329,7 +426,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
329
426
|
}
|
|
330
427
|
}
|
|
331
428
|
if (isAnyHook(name) && !isEffectLike(name) && !isMemoLike(name) && name !== 'useState' && name !== 'useRef' && name !== 'useContext' && name !== 'useReducer' && name !== 'useDebugValue' && name !== 'useId' && name !== 'useOptimistic' && name !== 'useActionState' && name !== 'useFormStatus' && name !== 'use') {
|
|
332
|
-
pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine));
|
|
429
|
+
pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine, messages.SeverityNiveis.info));
|
|
333
430
|
}
|
|
334
431
|
}
|
|
335
432
|
catch {
|
|
@@ -38,6 +38,11 @@ function collectReactIssues(src, relPath) {
|
|
|
38
38
|
// dangerouslySetInnerHTML
|
|
39
39
|
for (const match of src.matchAll(/dangerouslySetInnerHTML/gi)) {
|
|
40
40
|
const line = lineOf(match.index);
|
|
41
|
+
// Skip if this is a <script type="speculationrules"> element — dangerouslySetInnerHTML
|
|
42
|
+
// is required for Speculation Rules API (no children allowed, only innerHTML).
|
|
43
|
+
const lineContent = src.split('\n')[line - 1] ?? '';
|
|
44
|
+
if (/speculationrules/.test(lineContent))
|
|
45
|
+
continue;
|
|
41
46
|
ocorrencias.push(warn(messages.ReactMensagens.dangerouslySetInnerHTML, relPath, line));
|
|
42
47
|
}
|
|
43
48
|
// <img> sem alt
|
|
@@ -233,6 +238,13 @@ function parseReactWithBabel(scan, relPath) {
|
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
if (findAttr(attrs, 'dangerouslySetInnerHTML')) {
|
|
241
|
+
// Skip if this is a <script type="speculationrules"> — dangerouslySetInnerHTML
|
|
242
|
+
// is required for Speculation Rules API (no children, only innerHTML).
|
|
243
|
+
if (tag.toLowerCase() === 'script') {
|
|
244
|
+
const typeAttr = findAttr(attrs, 'type');
|
|
245
|
+
if (typeAttr && /speculationrules/i.test(normalizeStringValue(typeAttr.value)))
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
236
248
|
pushOnce(warn(messages.ReactMensagens.dangerouslySetInnerHTML, relPath, locLine));
|
|
237
249
|
}
|
|
238
250
|
// Inline Styles
|
|
@@ -23,6 +23,9 @@ export const detectorReactBestPractices = {
|
|
|
23
23
|
linhas.forEach((linha, index) => {
|
|
24
24
|
const numeroLinha = index + 1;
|
|
25
25
|
if (/dangerouslySetInnerHTML/.test(linha)) {
|
|
26
|
+
// Skip <script type="speculationrules"> — dangerouslySetInnerHTML is required
|
|
27
|
+
if (/speculationrules/i.test(linha))
|
|
28
|
+
return;
|
|
26
29
|
ocorrencias.push(warn('react-dangerous-html', D.dangerousHtml, relPath, numeroLinha, D.dangerousHtmlSugestao, 'erro'));
|
|
27
30
|
}
|
|
28
31
|
if (/onClick=|onSubmit=|onChange=|onMouseOver=/.test(linha) && /{[^}]*=>/.test(linha)) {
|
|
@@ -101,7 +104,27 @@ function detectWithBabel(src, relPath) {
|
|
|
101
104
|
}
|
|
102
105
|
const locLine = path.node.loc?.start.line;
|
|
103
106
|
if (isHookName(name)) {
|
|
104
|
-
|
|
107
|
+
// Only flag hooks inside conditionals in the SAME function scope.
|
|
108
|
+
// Stop climbing at function boundaries (callbacks, event handlers, etc.)
|
|
109
|
+
// to avoid false positives when a conditional is inside a hook callback.
|
|
110
|
+
const parent = (() => {
|
|
111
|
+
let currentPath = path;
|
|
112
|
+
while (currentPath) {
|
|
113
|
+
const found = currentPath.findParent(p => p.isIfStatement() || p.isConditionalExpression() || p.isLogicalExpression() || p.isSwitchStatement() || p.isSwitchCase() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isLoop());
|
|
114
|
+
if (found)
|
|
115
|
+
return found;
|
|
116
|
+
// Stop at function boundaries
|
|
117
|
+
const fnBoundary = currentPath.findParent(p => p.isFunction());
|
|
118
|
+
if (!fnBoundary)
|
|
119
|
+
return null;
|
|
120
|
+
// Check above the function boundary
|
|
121
|
+
const aboveFn = fnBoundary.findParent(p => p.isIfStatement() || p.isConditionalExpression() || p.isLogicalExpression() || p.isSwitchStatement() || p.isSwitchCase() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isLoop());
|
|
122
|
+
if (aboveFn)
|
|
123
|
+
return aboveFn;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
})();
|
|
105
128
|
if (parent) {
|
|
106
129
|
pushOnce(warn('react-hook-conditional', D.hookConditionalCall, relPath, locLine, D.hookConditionalCallSugestao, 'erro'));
|
|
107
130
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
// @mahoraga-disable react-hooks/regra ml-deep-nesting
|
|
1
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Justificativa: O scorer analisa padrões de hooks (condicionais, useEffect sem deps, context)
|
|
4
|
+
// através de regex em STRINGS de mensagem, não contendo violações reais de regras de hooks.
|
|
2
5
|
// @mahoraga-disable react-hooks/rule
|
|
3
6
|
export function pontuarReact(src, _relPath) {
|
|
4
7
|
let score = 0;
|
|
@@ -9,7 +12,7 @@ export function pontuarReact(src, _relPath) {
|
|
|
9
12
|
// React 19 patterns: forwardRef deprecation, React.FC, Server Components
|
|
10
13
|
const hasForwardRef = /\bforwardRef\s*\(/.test(srcCompleto);
|
|
11
14
|
if (hasForwardRef) {
|
|
12
|
-
issues.push('forwardRef é deprecated no React 19
|
|
15
|
+
issues.push('forwardRef é deprecated no React 19 - passe ref como prop direta');
|
|
13
16
|
}
|
|
14
17
|
else {
|
|
15
18
|
score += 3;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
// @mahoraga-disable tipo-literal-inline-complexo ml-deep-nesting
|
|
1
2
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// @mahoraga-disable tipo-literal-inline-complexo
|
|
3
3
|
// Justificativa: tipos locais para operações de estrutura
|
|
4
4
|
import { config } from '../../core/config/index.js';
|
|
5
5
|
import { getMessages } from '../../core/messages/index.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @mahoraga-disable ml-deep-nesting
|
|
1
2
|
// SPDX-License-Identifier: MIT
|
|
2
3
|
import { messages } from '../../../core/messages/index.js';
|
|
3
4
|
import { createLineLookup } from '../../../shared/helpers/index.js';
|
|
@@ -310,8 +311,11 @@ function propertyKey(token) {
|
|
|
310
311
|
re: /^max-h(?:-|\[)/,
|
|
311
312
|
key: 'max-h'
|
|
312
313
|
}, {
|
|
313
|
-
re: /^(top|
|
|
314
|
-
key: 'position'
|
|
314
|
+
re: /^(top|bottom)(?:-|\[)/,
|
|
315
|
+
key: 'position-y'
|
|
316
|
+
}, {
|
|
317
|
+
re: /^(left|right)(?:-|\[)/,
|
|
318
|
+
key: 'position-x'
|
|
315
319
|
}, {
|
|
316
320
|
re: /^field-sizing(?:-|\[)/,
|
|
317
321
|
key: 'field-sizing'
|