@mocoto/mahoraga 0.14.1 → 0.14.2
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/dist/analysts/architects/analyst-structure.js +1 -1
- package/dist/analysts/architects/archetypes-custom.js +8 -13
- package/dist/analysts/architects/detector-archetypes.js +39 -35
- package/dist/analysts/architects/orchestrator-archetypes.js +3 -3
- package/dist/analysts/architects/signals-project-advanced.js +2 -2
- package/dist/analysts/architects/signals-project.js +1 -1
- package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +20 -21
- package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +0 -3
- package/dist/analysts/circleci/analysts/analyst-circleci.js +22 -24
- package/dist/analysts/circleci/detectors/detector-circleci-structure.js +4 -7
- package/dist/analysts/converters/convert-registry.js +1 -1
- package/dist/analysts/converters/index.js +1 -1
- package/dist/analysts/corrections/auto-fix-engine.js +1 -1
- package/dist/analysts/corrections/corrector-structure.js +8 -7
- package/dist/analysts/corrections/map-reversion.js +2 -2
- package/dist/analysts/corrections/reorganize-barrels.js +3 -3
- package/dist/analysts/corrections/rewrite-testes-aliases.js +23 -22
- package/dist/analysts/corrections/type-safety/type-analyzer.js +3 -3
- package/dist/analysts/corrections/type-safety/type-validator.js +2 -2
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +3 -3
- package/dist/analysts/css/analysts/analyst-css.js +27 -28
- package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +8 -15
- package/dist/analysts/detectors/detector-architecture.js +2 -2
- package/dist/analysts/detectors/detector-bugs-ml.js +8 -8
- package/dist/analysts/detectors/detector-code-fragile.js +11 -10
- package/dist/analysts/detectors/detector-dependencies.js +5 -5
- package/dist/analysts/detectors/detector-duplications.js +5 -8
- package/dist/analysts/detectors/detector-monorepo.js +10 -9
- package/dist/analysts/detectors/detector-performance.js +2 -1
- package/dist/analysts/detectors/detector-recommendation-architecture.js +1 -1
- package/dist/analysts/detectors/detector-security.js +1 -1
- package/dist/analysts/detectors/detector-structure.js +3 -3
- package/dist/analysts/github-actions/analysts/analyst-github-actions.js +5 -8
- package/dist/analysts/github-actions/detectors/detector-workflow-accessibility.js +1 -9
- package/dist/analysts/github-actions/detectors/detector-workflow-structure.js +1 -4
- package/dist/analysts/github-actions/detectors/detector-workflow-trigger-unsafe.js +6 -5
- package/dist/analysts/gitlab-ci/analysts/analyst-gitlab-ci.js +2 -2
- package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-structure.js +0 -3
- package/dist/analysts/go/analysts/analyst-best-practices.js +2 -2
- package/dist/analysts/go/analysts/analyst-concurrency.js +1 -1
- package/dist/analysts/go/analysts/analyst-security.js +1 -1
- package/dist/analysts/go/detectors/detector-conventions.js +1 -1
- package/dist/analysts/html/analysts/analyst-html.js +10 -12
- package/dist/analysts/html/detectors/detector-html.js +3 -3
- package/dist/analysts/js-ts/analysts/analyst-commands-cli.js +22 -22
- package/dist/analysts/js-ts/analysts/analyst-functions-long.js +5 -5
- package/dist/analysts/js-ts/analysts/analyst-patterns-usage.js +6 -2
- package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +81 -9
- package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +35 -6
- package/dist/analysts/js-ts/detectors/detector-context-smart.js +6 -6
- package/dist/analysts/js-ts/detectors/detector-leaks-memory.js +1 -1
- package/dist/analysts/js-ts/detectors/detector-types-unsafe.js +33 -2
- package/dist/analysts/php/analysts/analyst-php.js +3 -3
- package/dist/analysts/php/detectors/detector-php-best-practices.js +5 -5
- package/dist/analysts/php/detectors/detector-php-security.js +5 -5
- package/dist/analysts/php/scorers/scorer-php.js +2 -2
- package/dist/analysts/plugins/detector-documentation.js +5 -6
- package/dist/analysts/plugins/detector-markdown.js +7 -7
- package/dist/analysts/python/analysts/analyst-python.js +2 -5
- package/dist/analysts/react/analysts/analyst-react-hooks.js +63 -8
- package/dist/analysts/react/analysts/analyst-react.js +45 -1
- package/dist/analysts/react/corrections/correction-react.js +40 -0
- package/dist/analysts/react/detectors/detector-react-best-practices.js +4 -4
- package/dist/analysts/react/scorers/scorer-react.js +36 -1
- package/dist/analysts/rust/analysts/analyst-best-practices.js +42 -3
- package/dist/analysts/rust/analysts/analyst-memory.js +2 -2
- package/dist/analysts/rust/analysts/analyst-rust.js +52 -5
- package/dist/analysts/rust/analysts/analyst-security.js +29 -1
- package/dist/analysts/scorers/scorer.js +5 -5
- package/dist/analysts/shell/analysts/analyst-shell.js +81 -5
- package/dist/analysts/shell/corrections/correction-shell.js +34 -1
- package/dist/analysts/shell/detectors/detector-shell-security.js +88 -0
- package/dist/analysts/shell/scorers/scorer-shell.js +58 -1
- package/dist/analysts/strategists/operator-structure.js +5 -7
- package/dist/analysts/strategists/suggestions-contextuais.js +5 -2
- package/dist/analysts/svg/analysts/analyst-svg.js +1 -0
- package/dist/analysts/tailwind/analysts/analyst-tailwind.js +79 -55
- package/dist/analysts/tailwind/corrections/correction-tailwind.js +116 -9
- package/dist/analysts/tailwind/detectors/detector-tailwind-best-practices.js +122 -7
- package/dist/analysts/tailwind/scorers/scorer-tailwind.js +62 -10
- package/dist/analysts/xml/analysts/analyst-xml.js +2 -2
- package/dist/app/index.js +1 -0
- package/dist/bin/cli-bootstrap.js +6 -4
- package/dist/bin/index.js +6 -4
- package/dist/caretakers/scoring.js +1 -0
- package/dist/cli/commands/command-analysts.js +1 -1
- package/dist/cli/commands/command-azure.js +1 -1
- package/dist/cli/commands/command-circleci.js +1 -1
- package/dist/cli/commands/command-compliance.js +1 -1
- package/dist/cli/commands/command-diagnose.js +10 -13
- package/dist/cli/commands/command-fix-types.js +9 -32
- package/dist/cli/commands/command-formatters.js +2 -2
- package/dist/cli/commands/command-gitlab-ci.js +1 -1
- package/dist/cli/commands/command-guardian.js +1 -1
- package/dist/cli/commands/command-imports.js +6 -6
- package/dist/cli/commands/command-jenkins.js +1 -1
- package/dist/cli/commands/command-licenses.js +1 -1
- package/dist/cli/commands/command-marketplace.js +3 -3
- package/dist/cli/commands/command-names.js +3 -8
- package/dist/cli/commands/command-perf.js +7 -9
- package/dist/cli/commands/command-plugins.js +4 -4
- package/dist/cli/diagnostic/auto-fix.js +2 -2
- package/dist/cli/diagnostic/exporters/json-exporter.js +1 -1
- package/dist/cli/diagnostic/exporters/sharded-exporter.js +0 -3
- package/dist/cli/diagnostic/exporters/svg-optimization-exporter.js +1 -1
- package/dist/cli/diagnostic/handlers/archetype-handler.js +3 -6
- package/dist/cli/diagnostic/handlers/auto-fix-handler.js +4 -4
- package/dist/cli/diagnostic/handlers/guardian-handler.js +1 -1
- package/dist/cli/diagnostic/processing-diagnostic.js +31 -35
- package/dist/cli/diagnostic/processing-helpers.js +2 -2
- package/dist/cli/handlers/fix-types-exporter.js +1 -4
- package/dist/cli/helpers/display-frame.js +5 -5
- package/dist/core/config/auto/auto-fix-config.js +0 -6
- package/dist/core/config/chalk-safe.js +2 -2
- package/dist/core/config/config.js +6 -8
- package/dist/core/config/conventions.js +1 -1
- package/dist/core/config/security.js +2 -2
- package/dist/core/execution/analysis-cache.js +5 -8
- package/dist/core/execution/executor.js +8 -19
- package/dist/core/execution/inquisitor.js +24 -21
- package/dist/core/execution/results.js +1 -0
- package/dist/core/execution/scanner.js +3 -3
- package/dist/core/messages/en/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/en/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/en/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/en/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/en/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/en/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/en/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/en/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/en/core/plugin-messages.js +31 -3
- package/dist/core/messages/en/log/log.js +4 -4
- package/dist/core/messages/en/ui/filter-config.js +1 -5
- package/dist/core/messages/i18n-system.js +3 -3
- package/dist/core/messages/index.js +3 -3
- package/dist/core/messages/ja/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/ja/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/ja/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/ja/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/ja/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/ja/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/ja/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/ja/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/ja/core/plugin-messages.js +31 -3
- package/dist/core/messages/ja/log/log.js +4 -4
- package/dist/core/messages/ja/ui/filter-config.js +1 -1
- package/dist/core/messages/pt/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/pt/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/pt/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/pt/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/pt/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/pt/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/pt/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/pt/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/pt/core/plugin-messages.js +31 -3
- package/dist/core/messages/pt/log/log-engine.js +6 -6
- package/dist/core/messages/pt/log/log.js +3 -3
- package/dist/core/messages/pt/ui/filter-config.js +1 -1
- package/dist/core/messages/shared/icons.js +3 -3
- package/dist/core/messages/zh/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/zh/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/zh/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/zh/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/zh/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/zh/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/zh/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/zh/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/zh/core/plugin-messages.js +31 -3
- package/dist/core/messages/zh/log/log.js +3 -3
- package/dist/core/messages/zh/ui/filter-config.js +1 -5
- package/dist/core/parsing/filters.js +3 -3
- package/dist/core/parsing/langs/json.js +1 -1
- package/dist/core/parsing/langs/python.js +1 -1
- package/dist/core/parsing/langs/typescript.js +1 -1
- package/dist/core/parsing/parser.js +2 -2
- package/dist/core/registry/file-registry.js +3 -2
- package/dist/core/registry/paths.js +3 -3
- package/dist/core/reporting/default-reporter.js +2 -2
- package/dist/core/schema/version.js +6 -6
- package/dist/core/workers/worker-executor.js +3 -3
- package/dist/core/workers/worker-pool.js +0 -0
- package/dist/guardian/baseline.js +1 -1
- package/dist/guardian/gpg.js +3 -1
- package/dist/guardian/integrity.js +2 -7
- package/dist/guardian/records.js +6 -1
- package/dist/guardian/result.js +1 -1
- package/dist/guardian/sentinel.js +6 -1
- package/dist/guardian/watcher-hidden.js +5 -1
- package/dist/licenses/fs-utils.js +4 -1
- package/dist/licenses/generate-notices.js +1 -1
- package/dist/licenses/header-options.js +0 -1
- package/dist/licenses/index.js +1 -0
- package/dist/licenses/normalizer.js +10 -9
- package/dist/node.loader.js +224 -4
- package/dist/reports/analise-async-patterns.js +1 -1
- package/dist/reports/filter-smart.js +2 -5
- package/dist/reports/generator-report.js +4 -4
- package/dist/reports/report-archetypes.js +10 -10
- package/dist/reports/report-caretaker-health.js +2 -1
- package/dist/sdk/analyzer.js +1 -1
- package/dist/shared/data-processing/fragment-report.js +10 -10
- package/dist/shared/data-processing/json.js +1 -1
- package/dist/shared/data-processing/occurrences.js +3 -2
- package/dist/shared/formatters/code-min-formatter.js +1 -1
- package/dist/shared/formatters/engines/prettier.js +1 -1
- package/dist/shared/formatters/engines/stylelint.js +6 -9
- package/dist/shared/formatters/formatter.js +8 -8
- package/dist/shared/formatters/formatters/code.js +3 -3
- package/dist/shared/formatters/formatters/css.js +2 -10
- package/dist/shared/formatters/formatters/dotfiles.js +1 -1
- package/dist/shared/formatters/formatters/html.js +1 -5
- package/dist/shared/formatters/formatters/json.js +0 -3
- package/dist/shared/formatters/formatters/markdown.js +18 -23
- package/dist/shared/formatters/formatters/properties.js +2 -2
- package/dist/shared/formatters/formatters/shell.js +1 -4
- package/dist/shared/formatters/formatters/sql.js +0 -3
- package/dist/shared/formatters/formatters/xml.js +5 -7
- package/dist/shared/formatters/formatters/yaml.js +2 -7
- package/dist/shared/formatters/index.js +1 -1
- package/dist/shared/helpers/framework-detector.js +1 -1
- package/dist/shared/helpers/json.js +1 -1
- package/dist/shared/helpers/magic-constants-whitelist.js +1 -1
- package/dist/shared/helpers/masking.js +17 -17
- package/dist/shared/helpers/reader-report.js +0 -12
- package/dist/shared/helpers/structural-hash.js +3 -2
- package/dist/shared/helpers/structure.js +1 -1
- package/dist/shared/marketplace/registry.js +1 -1
- package/dist/shared/marketplace/remote.js +4 -4
- package/dist/shared/persistence/persistence.js +0 -1
- package/dist/shared/plugins/core-plugin.js +3 -1
- package/dist/shared/plugins/registry.js +3 -2
- package/dist/types/caretakers/imports-barrel.js +1 -0
- package/dist/types/caretakers/index.js +4 -0
- package/dist/types/caretakers/map-reversion.js +1 -0
- package/dist/types/caretakers/pruning.js +1 -0
- package/dist/types/caretakers/scoring.js +1 -0
- package/dist/types/common/analysts.js +1 -1
- package/dist/types/core/config/filters.js +1 -0
- package/dist/types/core/config/index.js +2 -0
- package/dist/types/core/execution/ambiente.js +1 -0
- package/dist/types/core/execution/linguagens.js +1 -0
- package/dist/types/core/execution/parse-errors.js +1 -0
- package/dist/types/core/execution/results.js +1 -0
- package/dist/types/core/execution/structure-json.js +1 -0
- package/dist/types/core/index.js +1 -0
- package/dist/types/core/messages/index.js +1 -0
- package/dist/types/core/messages/log.js +1 -0
- package/dist/types/core/parsing/babel-narrow.js +1 -0
- package/dist/types/core/parsing/index.js +1 -0
- package/dist/types/core/runtime.js +1 -0
- package/dist/types/guardian/index.js +2 -0
- package/dist/types/guardian/integrity.js +7 -0
- package/dist/types/guardian/snapshot.js +1 -0
- package/dist/types/index.js +4 -4
- package/dist/types/licenses/index.js +2 -0
- package/dist/types/processing/display.js +16 -13
- package/dist/types/processing/filters.js +3 -7
- package/dist/types/reports/compliance.js +1 -0
- package/dist/types/reports/streaming.js +1 -0
- package/dist/types/scripts/index.js +1 -0
- package/dist/types/scripts/migrar-aliases.js +1 -0
- package/dist/types/sdk/index.js +1 -0
- package/dist/types/shared/index.js +2 -0
- package/dist/types/vulnerabilities/index.js +2 -0
- package/dist/vulnerabilities/npm-audit.js +0 -1
- package/dist/vulnerabilities/scanner.js +2 -1
- package/dist/vulnerabilities/vulnerabilities.js +0 -1
- package/package.json +11 -12
|
@@ -107,7 +107,7 @@ const visitorMemoria = {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
// Detect closures that return functions referencing large objects
|
|
110
|
-
if (
|
|
110
|
+
if (textoTitulo.isBlockStatement(path.node.body)) {
|
|
111
111
|
let hasReturnFunction = false;
|
|
112
112
|
let referencesLargeData = false;
|
|
113
113
|
try {
|
|
@@ -60,8 +60,8 @@ const ANALISTA = {
|
|
|
60
60
|
let sugestao = '';
|
|
61
61
|
// Detectar padrões específicos
|
|
62
62
|
if (anyMatch[1] !== 'any') {
|
|
63
|
-
mensagem = messages.DetectorJsTsMensagens.tipoAnyComUtilidade(varNome ?? '', anyMatch[1]
|
|
64
|
-
sugestao = messages.DetectorJsTsMensagens.tipoAnyComUtilidadeSugestao(anyMatch[1]
|
|
63
|
+
mensagem = messages.DetectorJsTsMensagens.tipoAnyComUtilidade(varNome ?? '', anyMatch[1]);
|
|
64
|
+
sugestao = messages.DetectorJsTsMensagens.tipoAnyComUtilidadeSugestao(anyMatch[1]);
|
|
65
65
|
}
|
|
66
66
|
else if (/catch\s*\(\s*\w+\s*:\s*any\s*\)/.test(lineContext)) {
|
|
67
67
|
mensagem = messages.DetectorJsTsMensagens.anyCatchBlock(varNome ?? undefined);
|
|
@@ -252,6 +252,37 @@ const ANALISTA = {
|
|
|
252
252
|
contexto: lineContext
|
|
253
253
|
});
|
|
254
254
|
}
|
|
255
|
+
// ─── DETECTAR 'as' QUE PODEM SER SUBSTITUÍDOS POR 'satisfies' ─
|
|
256
|
+
const asTypePadrao = /\bas\s+([A-Za-z_$][A-Za-z0-9_$.]+)\b(?!\s*[=(])/g;
|
|
257
|
+
let asTypeMatch;
|
|
258
|
+
while ((asTypeMatch = asTypePadrao.exec(src)) !== null) {
|
|
259
|
+
const position = asTypeMatch.index ?? 0;
|
|
260
|
+
if (isInStringOrComment(src, position)) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const linha = splitLines(src.substring(0, position)).length;
|
|
264
|
+
const lineContext = splitLines(src)[linha - 1]?.trim() || '';
|
|
265
|
+
// Skip if any type as it's already caught above
|
|
266
|
+
if (asTypeMatch[1] === 'any') {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
// Skip if used in destructuring patterns (as newName)
|
|
270
|
+
if (/:\s*\w+\s+as\s+\w+/.test(lineContext)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (shouldSuppressOccurrence('tipo-inseguro-as-satisfies', relPath)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const mensagemCompleta = `${messages.DetectorJsTsMensagens.satisfiesAvailable} | [IDEIA] ${messages.DetectorJsTsMensagens.satisfiesAvailableSugestao}`;
|
|
277
|
+
ocorrencias.push({
|
|
278
|
+
tipo: 'tipo-inseguro-as-satisfies',
|
|
279
|
+
nivel: 'info',
|
|
280
|
+
mensagem: mensagemCompleta,
|
|
281
|
+
relPath,
|
|
282
|
+
linha,
|
|
283
|
+
contexto: lineContext
|
|
284
|
+
});
|
|
285
|
+
}
|
|
255
286
|
// Detectar uso de unknown
|
|
256
287
|
const unknownPadrao = /:\s*unknown\b/g;
|
|
257
288
|
let unknownMatch;
|
|
@@ -83,7 +83,7 @@ function collectPhpIssues(src, relPath) {
|
|
|
83
83
|
// --- Code Quality: long function/method ---
|
|
84
84
|
const funcMatches = src.matchAll(/(?:^|\n)\s*(?:public|private|protected|static)?\s*function\s+\w+\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{/g);
|
|
85
85
|
for (const funcMatch of funcMatches) {
|
|
86
|
-
const startIdx =
|
|
86
|
+
const startIdx = funcMatch.index + funcMatch[0].length;
|
|
87
87
|
let braceCount = 1;
|
|
88
88
|
let endIdx = startIdx;
|
|
89
89
|
for (let i = startIdx; i < src.length && braceCount > 0; i++) {
|
|
@@ -98,7 +98,7 @@ function collectPhpIssues(src, relPath) {
|
|
|
98
98
|
const body = src.slice(startIdx, endIdx);
|
|
99
99
|
const lines = body.split('\n').length;
|
|
100
100
|
if (lines > 60) {
|
|
101
|
-
const line = lineOf(funcMatch.index
|
|
101
|
+
const line = lineOf(funcMatch.index);
|
|
102
102
|
ocorrencias.push(warn(messages.PhpMensagens.longFunction, relPath, line));
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -111,7 +111,7 @@ function collectPhpIssues(src, relPath) {
|
|
|
111
111
|
}
|
|
112
112
|
// --- Style: naming convention (snake_case) ---
|
|
113
113
|
for (const match of scanWithoutStrings.matchAll(/\bfunction\s+([a-z][a-zA-Z0-9]*)\s*\(/g)) {
|
|
114
|
-
const funcName = match[1]
|
|
114
|
+
const funcName = match[1];
|
|
115
115
|
if (/^[a-z]+[A-Z]/.test(funcName)) {
|
|
116
116
|
const line = lineOf(match.index);
|
|
117
117
|
ocorrencias.push(warn(messages.PhpMensagens.camelCaseFunction, relPath, line, messages.SeverityNiveis.suggestion));
|
|
@@ -48,7 +48,7 @@ function detectBestPracticesIssues(src, relPath) {
|
|
|
48
48
|
// Magic numbers (numeric literals that aren't 0, 1, -1, or common config values)
|
|
49
49
|
for (const match of scan.matchAll(/(?<!\$)[\s(,=[+\-*/%&|^]=?](\d{3,})\b(?!\s*=>)/g)) {
|
|
50
50
|
const line = lineOf(match.index);
|
|
51
|
-
const context = scan.slice(Math.max(0,
|
|
51
|
+
const context = scan.slice(Math.max(0, match.index - 40), match.index);
|
|
52
52
|
if (!context.includes('define') && !context.includes('const') && !context.includes('LIMIT') && !context.includes('offset') && !context.includes('count') && !context.includes('max') && !context.includes('min') && !context.includes('timeout') && !context.includes('port') && !context.includes('sleep')) {
|
|
53
53
|
ocorrencias.push({
|
|
54
54
|
relPath,
|
|
@@ -62,8 +62,8 @@ function detectBestPracticesIssues(src, relPath) {
|
|
|
62
62
|
}
|
|
63
63
|
// Complex functions (cyclomatic complexity via brace counting)
|
|
64
64
|
for (const funcMatch of scan.matchAll(/(?:^|\n)\s*(?:public|private|protected|static)?\s*function\s+\w+\s*\([^)]*\)\s*(?::\s*\w+(?:\s*\|\s*\w+)*)?\s*\{/g)) {
|
|
65
|
-
const funcStart = funcMatch.index
|
|
66
|
-
const bodyStart = funcStart +
|
|
65
|
+
const funcStart = funcMatch.index;
|
|
66
|
+
const bodyStart = funcStart + funcMatch[0].length;
|
|
67
67
|
let braceCount = 1;
|
|
68
68
|
let bodyEnd = bodyStart;
|
|
69
69
|
for (let i = bodyStart; i < scan.length && braceCount > 0; i++) {
|
|
@@ -95,7 +95,7 @@ function detectBestPracticesIssues(src, relPath) {
|
|
|
95
95
|
for (const op of riskyOps) {
|
|
96
96
|
for (const match of scan.matchAll(new RegExp(`\\b${op}\\s*\\(`, 'gi'))) {
|
|
97
97
|
const line = lineOf(match.index);
|
|
98
|
-
const before = scan.slice(Math.max(0,
|
|
98
|
+
const before = scan.slice(Math.max(0, match.index - 100), match.index);
|
|
99
99
|
if (!before.includes('@') && !before.includes('try') && !before.includes('if') && !/(?:if\s*\(|try\s*\{)/.test(before)) {
|
|
100
100
|
ocorrencias.push({
|
|
101
101
|
relPath,
|
|
@@ -111,7 +111,7 @@ function detectBestPracticesIssues(src, relPath) {
|
|
|
111
111
|
// Superglobal access without filtering
|
|
112
112
|
for (const match of scan.matchAll(/\$(?:_GET|_POST|_REQUEST|_COOKIE|_SERVER)\s*\[/gi)) {
|
|
113
113
|
const line = lineOf(match.index);
|
|
114
|
-
const before = scan.slice(Math.max(0,
|
|
114
|
+
const before = scan.slice(Math.max(0, match.index - 150), match.index);
|
|
115
115
|
if (!before.includes('filter_input') && !before.includes('filter_var') && !before.includes('htmlspecialchars') && !before.includes('trim') && !before.includes('strip_tags') && !before.includes('intval') && !/=\s*(int|float|string|bool)\s*\$/.test(before)) {
|
|
116
116
|
ocorrencias.push({
|
|
117
117
|
relPath,
|
|
@@ -10,7 +10,7 @@ function detectSecurityIssues(src, relPath) {
|
|
|
10
10
|
// Loose comparison (== vs ===) in security-sensitive contexts
|
|
11
11
|
for (const match of scan.matchAll(/(?:if|elseif|while)\s*\([^)]*==[^=][^)]*\)/gi)) {
|
|
12
12
|
const line = lineOf(match.index);
|
|
13
|
-
const snippet = scan.slice(Math.max(0,
|
|
13
|
+
const snippet = scan.slice(Math.max(0, match.index - 40), match.index + 120);
|
|
14
14
|
if (/\$_(?:GET|POST|REQUEST|COOKIE|SERVER)/.test(snippet) || /password|senha|token|auth|login/i.test(snippet)) {
|
|
15
15
|
ocorrencias.push({
|
|
16
16
|
relPath,
|
|
@@ -37,7 +37,7 @@ function detectSecurityIssues(src, relPath) {
|
|
|
37
37
|
// XSS: echo/print with user input without htmlspecialchars
|
|
38
38
|
for (const match of scan.matchAll(/(?:echo|print)\s+.*\$(?:_GET|_POST|_REQUEST|_COOKIE|_SERVER)/gi)) {
|
|
39
39
|
const line = lineOf(match.index);
|
|
40
|
-
const before = scan.slice(Math.max(0,
|
|
40
|
+
const before = scan.slice(Math.max(0, match.index - 120), match.index);
|
|
41
41
|
if (!before.includes('htmlspecialchars') && !before.includes('htmlentities') && !before.includes('strip_tags') && !before.includes('filter_var') && !before.includes('esc_html') && !before.includes('e(')) {
|
|
42
42
|
ocorrencias.push({
|
|
43
43
|
relPath,
|
|
@@ -51,7 +51,7 @@ function detectSecurityIssues(src, relPath) {
|
|
|
51
51
|
}
|
|
52
52
|
// CSRF protection missing
|
|
53
53
|
for (const match of scan.matchAll(/<form[^>]*>/gi)) {
|
|
54
|
-
const formPos = match.index
|
|
54
|
+
const formPos = match.index;
|
|
55
55
|
const formTag = match[0];
|
|
56
56
|
const formEnd = scan.indexOf('</form>', formPos);
|
|
57
57
|
const formBlock = scan.slice(formPos, formEnd > 0 ? formEnd + 7 : formPos + 500);
|
|
@@ -138,8 +138,8 @@ function detectSecurityIssues(src, relPath) {
|
|
|
138
138
|
if (/(?:md5|sha1)\s*\(\s*\$.*pass/i.test(scan)) {
|
|
139
139
|
for (const match of scan.matchAll(/(?:md5|sha1)\s*\(/gi)) {
|
|
140
140
|
const line = lineOf(match.index);
|
|
141
|
-
const before = scan.slice(Math.max(0,
|
|
142
|
-
if (/pass/i.test(before) || /pass/i.test(scan.slice(match.index
|
|
141
|
+
const before = scan.slice(Math.max(0, match.index - 60), match.index);
|
|
142
|
+
if (/pass/i.test(before) || /pass/i.test(scan.slice(match.index, match.index + 80))) {
|
|
143
143
|
ocorrencias.push({
|
|
144
144
|
relPath,
|
|
145
145
|
mensagem: messages.PhpMensagens.weakPasswordHash,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import { splitLines } from '../../../shared/helpers/index.js';
|
|
3
3
|
import { detectorPhpBestPractices, detectorPhpSecurity } from '../detectors/index.js';
|
|
4
|
-
export
|
|
4
|
+
export function pontuarPhp(src, relPath) {
|
|
5
5
|
let score = 0;
|
|
6
6
|
const maxScore = 100;
|
|
7
7
|
const issues = [];
|
|
@@ -38,7 +38,7 @@ export async function pontuarPhp(src, relPath) {
|
|
|
38
38
|
let hasComplex = false;
|
|
39
39
|
let longFns = 0;
|
|
40
40
|
for (const fn of fnMatches) {
|
|
41
|
-
const bodyStart =
|
|
41
|
+
const bodyStart = fn.index + fn[0].length;
|
|
42
42
|
let braceCount = 1;
|
|
43
43
|
let bodyEnd = bodyStart;
|
|
44
44
|
for (let i = bodyStart; i < src.length && braceCount > 0; i++) {
|
|
@@ -239,13 +239,11 @@ function detectarProblemasDocumentacaoAST(ast, problemas) {
|
|
|
239
239
|
}
|
|
240
240
|
function agruparPorPrioridade(problemas) {
|
|
241
241
|
return problemas.reduce((acc, problema) => {
|
|
242
|
-
const prioridade = problema.prioridade;
|
|
243
|
-
if (prioridade) {
|
|
244
|
-
|
|
245
|
-
acc[prioridade] = [];
|
|
246
|
-
}
|
|
247
|
-
acc[prioridade].push(problema);
|
|
242
|
+
const prioridade = problema.prioridade ?? 'media';
|
|
243
|
+
if (!(prioridade in acc)) {
|
|
244
|
+
acc[prioridade] = [];
|
|
248
245
|
}
|
|
246
|
+
acc[prioridade].push(problema);
|
|
249
247
|
return acc;
|
|
250
248
|
}, {});
|
|
251
249
|
}
|
|
@@ -257,6 +255,7 @@ function mapearPrioridadeParaNivel(prioridade) {
|
|
|
257
255
|
case 'media':
|
|
258
256
|
return 'aviso';
|
|
259
257
|
case 'baixa':
|
|
258
|
+
return 'info';
|
|
260
259
|
case undefined: {
|
|
261
260
|
throw new Error('Not implemented yet: undefined case');
|
|
262
261
|
}
|
|
@@ -100,9 +100,9 @@ function mergeWhitelist(base, override, mode) {
|
|
|
100
100
|
const overrideConfig = override ?? {};
|
|
101
101
|
if (mode === 'replace') {
|
|
102
102
|
return {
|
|
103
|
-
paths:
|
|
104
|
-
patterns:
|
|
105
|
-
dirs:
|
|
103
|
+
paths: overrideConfig.paths ?? [],
|
|
104
|
+
patterns: overrideConfig.patterns ?? [],
|
|
105
|
+
dirs: overrideConfig.dirs ?? []
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
const uniq = (arr) => Array.from(new Set(arr.map(String)));
|
|
@@ -110,9 +110,9 @@ function mergeWhitelist(base, override, mode) {
|
|
|
110
110
|
const overridePatterns = Array.isArray(overrideConfig.patterns) ? overrideConfig.patterns : [];
|
|
111
111
|
const overrideDirs = Array.isArray(overrideConfig.dirs) ? overrideConfig.dirs : [];
|
|
112
112
|
return {
|
|
113
|
-
paths: uniq([...
|
|
114
|
-
patterns: uniq([...
|
|
115
|
-
dirs: uniq([...
|
|
113
|
+
paths: uniq([...base.paths, ...overridePaths]),
|
|
114
|
+
patterns: uniq([...base.patterns, ...overridePatterns]),
|
|
115
|
+
dirs: uniq([...base.dirs, ...overrideDirs])
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
@@ -263,7 +263,7 @@ export const detectorMarkdown = {
|
|
|
263
263
|
test: (relPath) => {
|
|
264
264
|
return relPath.toLowerCase().endsWith('.md');
|
|
265
265
|
},
|
|
266
|
-
aplicar:
|
|
266
|
+
aplicar: (src, relPath, _ast, fullCaminho) => {
|
|
267
267
|
if (!fullCaminho) {
|
|
268
268
|
log.aviso(getMessages().LicensasMensagens.detectorMarkdownPath.replace('{arquivo}', relPath));
|
|
269
269
|
return [];
|
|
@@ -67,7 +67,7 @@ export function collectPythonIssues(src, relPath) {
|
|
|
67
67
|
ocorrencias.push(warn(messages.PythonMensagens.pickleUsage, relPath, line, messages.SeverityNiveis.error));
|
|
68
68
|
}
|
|
69
69
|
for (const match of scan.matchAll(/\byaml\.load\s*\([\s\S]*?\)/gi)) {
|
|
70
|
-
const text = match[0]
|
|
70
|
+
const text = match[0];
|
|
71
71
|
const hasSafeLoader = /\bLoader\s*=\s*yaml\.(?:SafeLoader|CSafeLoader)\b/.test(text);
|
|
72
72
|
const hasFullLoader = /\bLoader\s*=\s*yaml\.(?:FullLoader|CFullLoader)\b/.test(text);
|
|
73
73
|
const hasExplicitLoader = /\bLoader\s*=/.test(text);
|
|
@@ -85,7 +85,7 @@ export function collectPythonIssues(src, relPath) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
for (const match of scanNoComentarios.matchAll(/\b(?:execute|executemany|executescript)\s*\(\s*f(['"])(?:SELECT|INSERT|UPDATE|DELETE)[\s\S]*?\1/gi)) {
|
|
88
|
-
const text = match[0]
|
|
88
|
+
const text = match[0];
|
|
89
89
|
if (!/\{[^}]+\}/.test(text)) {
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
@@ -133,9 +133,6 @@ export function collectPythonIssues(src, relPath) {
|
|
|
133
133
|
}
|
|
134
134
|
// except blocks without logging the exception
|
|
135
135
|
for (const match of src.matchAll(/except\s+(\w+)\s+as\s+(\w+)\s*:((?:[ \t]+.*\n?)*?)(?=\n\S|\n\s*(?:except|else|finally|def|class)|$)/gm)) {
|
|
136
|
-
if (match[3] === undefined) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
136
|
const excVar = match[2];
|
|
140
137
|
const block = match[3];
|
|
141
138
|
if (!block || block.trim() === '' || block.trim() === 'pass') {
|
|
@@ -5,8 +5,19 @@ import { messages } from '../../../core/messages/index.js';
|
|
|
5
5
|
import { createLineLookup } from '../../../shared/helpers/index.js';
|
|
6
6
|
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
|
|
7
7
|
const disableEnv = process.env.SUKUNA_DISABLE_PLUGIN_REACT_HOOKS === '1';
|
|
8
|
+
const REACT_18_HOOKS = [
|
|
9
|
+
'useState', 'useEffect', 'useMemo', 'useCallback', 'useRef',
|
|
10
|
+
'useContext', 'useReducer', 'useLayoutEffect', 'useImperativeHandle',
|
|
11
|
+
'useDebugValue', 'useDeferredValue', 'useTransition',
|
|
12
|
+
'useSyncExternalStore', 'useInsertionEffect', 'useId',
|
|
13
|
+
];
|
|
14
|
+
// React 19+ built-in hooks
|
|
15
|
+
const REACT_19_HOOKS = [
|
|
16
|
+
'use', 'useOptimistic', 'useActionState', 'useFormStatus',
|
|
17
|
+
];
|
|
18
|
+
const ALL_BUILTIN_HOOKS = [...REACT_18_HOOKS, ...REACT_19_HOOKS];
|
|
8
19
|
function hasHooksUsage(src) {
|
|
9
|
-
return
|
|
20
|
+
return new RegExp(`\\b(?:${ALL_BUILTIN_HOOKS.join('|')})\\b`).test(src);
|
|
10
21
|
}
|
|
11
22
|
function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
|
|
12
23
|
return criarOcorrencia({
|
|
@@ -43,9 +54,10 @@ function extractHookCallContent(src, startIndex) {
|
|
|
43
54
|
function collectHookIssues(src, relPath) {
|
|
44
55
|
const ocorrencias = [];
|
|
45
56
|
const lineOf = createLineLookup(src).lineAt;
|
|
57
|
+
// Check useEffect/useLayoutEffect for deps
|
|
46
58
|
const effectMatches = [...src.matchAll(/use(Layout)?Effect\s*\(/g)];
|
|
47
59
|
effectMatches.forEach(m => {
|
|
48
|
-
const hookInicio = m.index
|
|
60
|
+
const hookInicio = m.index;
|
|
49
61
|
const fullCall = extractHookCallContent(src, hookInicio + m[0].length - 1);
|
|
50
62
|
const hasDepsArg = /,\s*(\[[\s\S]*?\]|\w+)\s*\)$/.test(fullCall);
|
|
51
63
|
const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
|
|
@@ -65,9 +77,10 @@ function collectHookIssues(src, relPath) {
|
|
|
65
77
|
}
|
|
66
78
|
}
|
|
67
79
|
});
|
|
80
|
+
// useMemo/useCallback deps check
|
|
68
81
|
const memoMatches = [...src.matchAll(/use(Memo|Callback)\s*\(/g)];
|
|
69
82
|
memoMatches.forEach(m => {
|
|
70
|
-
const hookInicio = m.index
|
|
83
|
+
const hookInicio = m.index;
|
|
71
84
|
const fullCall = extractHookCallContent(src, hookInicio + m[0].length - 1);
|
|
72
85
|
const hasDepsArg = /,\s*(\[[\s\S]*?\]|\w+)\s*\)$/.test(fullCall);
|
|
73
86
|
const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
|
|
@@ -76,15 +89,41 @@ function collectHookIssues(src, relPath) {
|
|
|
76
89
|
ocorrencias.push(warn(messages.ReactHooksMensagens.memoCallbackNoDeps, relPath, line));
|
|
77
90
|
}
|
|
78
91
|
});
|
|
92
|
+
// Conditional hooks
|
|
79
93
|
const conditionalHooks = [...src.matchAll(/(if|for|while)\s*\([^)]*\)\s*\{[\s\S]{0,160}?use[A-Z][A-Za-z0-9_]*/g)];
|
|
80
94
|
conditionalHooks.forEach(m => {
|
|
81
95
|
const line = lineOf(m.index || 0);
|
|
82
96
|
ocorrencias.push(warn(messages.ReactHooksMensagens.hookInConditional, relPath, line));
|
|
83
97
|
});
|
|
98
|
+
// React 19: useOptimistic detected
|
|
99
|
+
for (const match of src.matchAll(/\buseOptimistic\s*\(/g)) {
|
|
100
|
+
const line = lineOf(match.index);
|
|
101
|
+
ocorrencias.push(warn('useOptimistic (React 19) detected', relPath, line, messages.SeverityNiveis.info));
|
|
102
|
+
}
|
|
103
|
+
// React 19: useActionState detected
|
|
104
|
+
for (const match of src.matchAll(/\buseActionState\s*\(/g)) {
|
|
105
|
+
const line = lineOf(match.index);
|
|
106
|
+
ocorrencias.push(warn('useActionState (React 19) detected — verify form action integration', relPath, line, messages.SeverityNiveis.info));
|
|
107
|
+
}
|
|
108
|
+
// React 19: useFormStatus detected
|
|
109
|
+
for (const match of src.matchAll(/\buseFormStatus\s*\(/g)) {
|
|
110
|
+
const line = lineOf(match.index);
|
|
111
|
+
ocorrencias.push(warn('useFormStatus (React 19) detected — access pending form state', relPath, line, messages.SeverityNiveis.info));
|
|
112
|
+
}
|
|
113
|
+
// React 19: use() hook — suspends, must be in Suspense boundary
|
|
114
|
+
for (const match of src.matchAll(/\buse\s*\(/g)) {
|
|
115
|
+
const line = lineOf(match.index);
|
|
116
|
+
// Avoid matching "useState", "useEffect", etc.
|
|
117
|
+
if (src[match.index - 1]?.match(/[a-zA-Z]/)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
ocorrencias.push(warn('React 19 use() hook detected — must be wrapped in <Suspense> boundary', relPath, line, messages.SeverityNiveis.info));
|
|
121
|
+
}
|
|
122
|
+
// Custom hooks naming convention
|
|
84
123
|
const customHooks = [...src.matchAll(/\buse[A-Z][A-Za-z0-9_]*\s*=\s*\(/g)];
|
|
85
124
|
customHooks.forEach(m => {
|
|
86
125
|
const hookName = m[0].replace(/\s*=\s*\(/, '');
|
|
87
|
-
if (
|
|
126
|
+
if (!ALL_BUILTIN_HOOKS.includes(hookName)) {
|
|
88
127
|
if (/^use[A-Z]/.test(hookName)) {
|
|
89
128
|
const line = lineOf(m.index || 0);
|
|
90
129
|
ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line));
|
|
@@ -120,7 +159,11 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
120
159
|
const declaredRefs = new Set();
|
|
121
160
|
const isHookCallee = (callee) => {
|
|
122
161
|
if (t.isIdentifier(callee)) {
|
|
123
|
-
|
|
162
|
+
const name = callee.name;
|
|
163
|
+
// React 19: use() is a special case (not prefixed with use but still a hook)
|
|
164
|
+
if (name === 'use')
|
|
165
|
+
return { name };
|
|
166
|
+
return /^use[A-Z0-9_]/.test(name) ? { name } : null;
|
|
124
167
|
}
|
|
125
168
|
if (t.isMemberExpression(callee)) {
|
|
126
169
|
const obj = callee.object;
|
|
@@ -135,7 +178,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
135
178
|
};
|
|
136
179
|
const isEffectLike = (name) => name === 'useEffect' || name === 'useLayoutEffect' || name === 'useInsertionEffect';
|
|
137
180
|
const isMemoLike = (name) => name === 'useMemo' || name === 'useCallback';
|
|
138
|
-
const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name);
|
|
181
|
+
const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name) || name === 'use';
|
|
139
182
|
const inConditionalOrLoop = (path) => {
|
|
140
183
|
return Boolean(path.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression()));
|
|
141
184
|
};
|
|
@@ -207,6 +250,18 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
207
250
|
}
|
|
208
251
|
}
|
|
209
252
|
}
|
|
253
|
+
// React 19: use() hook should be in Suspense boundary
|
|
254
|
+
if (name === 'use') {
|
|
255
|
+
pushOnce(warn('React 19 use() hook — ensure wrapped in <Suspense> boundary', relPath, locLine));
|
|
256
|
+
}
|
|
257
|
+
// React 19: useOptimistic should have reducer function
|
|
258
|
+
if (name === 'useOptimistic' && path.node.arguments.length < 2) {
|
|
259
|
+
pushOnce(warn('useOptimistic expects initial state and update function (React 19)', relPath, locLine));
|
|
260
|
+
}
|
|
261
|
+
// React 19: useActionState should have form action
|
|
262
|
+
if (name === 'useActionState') {
|
|
263
|
+
pushOnce(warn('useActionState (React 19) — verify form action handler', relPath, locLine));
|
|
264
|
+
}
|
|
210
265
|
if (isEffectLike(name) && path.node.arguments.length >= 2) {
|
|
211
266
|
const callback = path.node.arguments[0];
|
|
212
267
|
const deps = path.node.arguments[1];
|
|
@@ -271,7 +326,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
271
326
|
}
|
|
272
327
|
}
|
|
273
328
|
}
|
|
274
|
-
if (isAnyHook(name) && !isEffectLike(name) && !isMemoLike(name) && name !== 'useState' && name !== 'useRef' && name !== 'useContext' && name !== 'useReducer' && name !== 'useDebugValue' && name !== 'useId') {
|
|
329
|
+
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') {
|
|
275
330
|
pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine));
|
|
276
331
|
}
|
|
277
332
|
}
|
|
@@ -290,7 +345,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
290
345
|
export const analistaReactHooks = criarAnalista({
|
|
291
346
|
nome: 'analista-react-hooks',
|
|
292
347
|
categoria: 'framework',
|
|
293
|
-
descricao: 'Heurísticas de Hooks com análise de dependências
|
|
348
|
+
descricao: 'Heurísticas de Hooks (React 18/19) com análise de dependências.',
|
|
294
349
|
global: false,
|
|
295
350
|
test: (relPath) => /\.(jsx|tsx|js|ts)$/i.test(relPath),
|
|
296
351
|
aplicar: (src, relPath) => {
|
|
@@ -79,6 +79,50 @@ function collectReactIssues(src, relPath) {
|
|
|
79
79
|
ocorrencias.push(warn(messages.ReactMensagens.locationHrefRedirect, relPath, line));
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
// React 19: <form action={...}>
|
|
83
|
+
for (const match of src.matchAll(/<form[^>]*>/gi)) {
|
|
84
|
+
const hasAction = /\saction\s*=\s*\{/.test(match[0]);
|
|
85
|
+
if (hasAction) {
|
|
86
|
+
const line = lineOf(match.index);
|
|
87
|
+
ocorrencias.push(warn('React 19 form action detected', relPath, line, messages.SeverityNiveis.info));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// React 19: use() hook (called at top level in render)
|
|
91
|
+
for (const match of src.matchAll(/\bconst\s+\w+\s*=\s*use\s*\(/g)) {
|
|
92
|
+
const line = lineOf(match.index);
|
|
93
|
+
if (!/use[A-Z]/.test(match[0])) {
|
|
94
|
+
ocorrencias.push(warn('React 19 use() hook detected — ensure wrapped in Suspense boundary', relPath, line, messages.SeverityNiveis.info));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 'use client' / 'use server' directives
|
|
98
|
+
if (/^['"]use (client|server)['"];/m.test(src)) {
|
|
99
|
+
ocorrencias.push(warn('Server Component directive detected', relPath, 1, messages.SeverityNiveis.info));
|
|
100
|
+
}
|
|
101
|
+
if (!/^['"]use client['"];/m.test(src) && /useState|useEffect|useContext|onClick/.test(src)) {
|
|
102
|
+
// This file uses client-side features without 'use client'
|
|
103
|
+
const hasClientFeatures = /useState|useEffect|useContext|onClick/.test(src);
|
|
104
|
+
if (hasClientFeatures) {
|
|
105
|
+
const firstLine = src.split('\n')[0]?.trim() ?? '';
|
|
106
|
+
if (firstLine.startsWith('import')) {
|
|
107
|
+
ocorrencias.push(warn('File uses client-side features (hooks/events) but missing \'use client\' directive (React 19 Server Components)', relPath, 1, messages.SeverityNiveis.suggestion));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// React 19: forwardRef deprecation
|
|
112
|
+
for (const match of src.matchAll(/\bforwardRef\s*\(/g)) {
|
|
113
|
+
const line = lineOf(match.index);
|
|
114
|
+
ocorrencias.push(warn('forwardRef is deprecated in React 19 — pass ref as a regular prop instead', relPath, line, messages.SeverityNiveis.suggestion));
|
|
115
|
+
}
|
|
116
|
+
// React 19: React.FC / React.FunctionComponent / React.VoidFunctionComponent
|
|
117
|
+
for (const match of src.matchAll(/\bReact\.(FC|FunctionComponent|VoidFunctionComponent)\b/g)) {
|
|
118
|
+
const line = lineOf(match.index);
|
|
119
|
+
ocorrencias.push(warn(`React.${match[1]} is deprecated in React 19 — type props directly: (props: Props) => JSX.Element`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
120
|
+
}
|
|
121
|
+
// React 19: defaultProps
|
|
122
|
+
for (const match of src.matchAll(/\.defaultProps\s*=\s*\{/g)) {
|
|
123
|
+
const line = lineOf(match.index);
|
|
124
|
+
ocorrencias.push(warn('defaultProps is deprecated in React 19 — use default parameter values instead', relPath, line, messages.SeverityNiveis.suggestion));
|
|
125
|
+
}
|
|
82
126
|
return ocorrencias;
|
|
83
127
|
}
|
|
84
128
|
function normalizeStringValue(valor) {
|
|
@@ -92,7 +136,7 @@ function attrsToFlatList(attrs) {
|
|
|
92
136
|
return [];
|
|
93
137
|
}
|
|
94
138
|
return attrs.filter(isBabelNode).filter((arquivo) => textoTitulo.isJSXAttribute(arquivo)).filter(arquivo => textoTitulo.isJSXIdentifier(arquivo.name)).map(arquivo => {
|
|
95
|
-
const name =
|
|
139
|
+
const name = arquivo.name.name;
|
|
96
140
|
let value;
|
|
97
141
|
if (!arquivo.value) {
|
|
98
142
|
value = null;
|
|
@@ -87,6 +87,46 @@ export function detectarCorrecoesReact(src, _relPath) {
|
|
|
87
87
|
confianca: 50
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
// React 19: forwardRef replacement
|
|
91
|
+
if (/\bforwardRef\s*\(/.test(linha)) {
|
|
92
|
+
correcoes.push({
|
|
93
|
+
tipo: 'react-forwardref-to-prop',
|
|
94
|
+
original: linha.trim(),
|
|
95
|
+
sugerido: '// React 19: passe ref como prop em vez de forwardRef\n// Ex: function MeuComponente({ ref, ...props }) { ... }',
|
|
96
|
+
linha: numeroLinha,
|
|
97
|
+
confianca: 85
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// React 19: React.FC replacement
|
|
101
|
+
if (/\bReact\.(FC|FunctionComponent)\s*</.test(linha)) {
|
|
102
|
+
correcoes.push({
|
|
103
|
+
tipo: 'react-fc-to-direct-type',
|
|
104
|
+
original: linha.trim(),
|
|
105
|
+
sugerido: '// React 19: tipagem direta de props\n// Ex: function MeuComponente({ prop1, prop2 }: Props)',
|
|
106
|
+
linha: numeroLinha,
|
|
107
|
+
confianca: 85
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// React 19: defaultProps replacement
|
|
111
|
+
if (/\.defaultProps\s*=/.test(linha)) {
|
|
112
|
+
correcoes.push({
|
|
113
|
+
tipo: 'react-default-props-to-params',
|
|
114
|
+
original: linha.trim(),
|
|
115
|
+
sugerido: '// React 19: use default parameter values\n// Ex: function MeuComponente({ prop = valor })',
|
|
116
|
+
linha: numeroLinha,
|
|
117
|
+
confianca: 90
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// React 19: 'use client' directive missing
|
|
121
|
+
if (/^\s*import\s+/.test(linha) && /useState|useEffect|onClick/.test(linha)) {
|
|
122
|
+
correcoes.push({
|
|
123
|
+
tipo: 'react-use-client-missing',
|
|
124
|
+
original: linha.trim(),
|
|
125
|
+
sugerido: "'use client';\n\n" + linha.trim(),
|
|
126
|
+
linha: numeroLinha,
|
|
127
|
+
confianca: 70
|
|
128
|
+
});
|
|
129
|
+
}
|
|
90
130
|
});
|
|
91
131
|
return correcoes;
|
|
92
132
|
}
|
|
@@ -78,7 +78,7 @@ function detectWithBabel(src, relPath) {
|
|
|
78
78
|
out.push(o);
|
|
79
79
|
};
|
|
80
80
|
const _lineOf = createLineLookup(src).lineAt;
|
|
81
|
-
const isHookName = (name) => /^use[A-Z0-9_]/.test(name)
|
|
81
|
+
const isHookName = (name) => /^use[A-Z0-9_]/.test(name);
|
|
82
82
|
const declaredVars = new Set();
|
|
83
83
|
const visitor = {
|
|
84
84
|
VariableDeclarator(path) {
|
|
@@ -134,7 +134,7 @@ function detectWithBabel(src, relPath) {
|
|
|
134
134
|
}
|
|
135
135
|
if (name === 'useEffect' || name === 'useLayoutEffect' || name === 'useInsertionEffect') {
|
|
136
136
|
const bodyArg = node.arguments[0];
|
|
137
|
-
if (
|
|
137
|
+
if ((t.isFunctionExpression(bodyArg) || t.isArrowFunctionExpression(bodyArg)) && t.isBlockStatement(bodyArg.body)) {
|
|
138
138
|
const bodyStmts = bodyArg.body.body;
|
|
139
139
|
const hasCleanup = bodyStmts.some(stmt => t.isReturnStatement(stmt) && stmt.argument && (t.isFunctionExpression(stmt.argument) || t.isArrowFunctionExpression(stmt.argument)));
|
|
140
140
|
const hasSubscriptions = /\b(subscribe|addEventListener|on\()/.test(src.slice(bodyArg.start ?? 0, bodyArg.end ?? 0));
|
|
@@ -174,7 +174,7 @@ function detectWithBabel(src, relPath) {
|
|
|
174
174
|
}
|
|
175
175
|
return false;
|
|
176
176
|
});
|
|
177
|
-
if (stateCalls.length > 1
|
|
177
|
+
if (stateCalls.length > 1) {
|
|
178
178
|
pushOnce(warn('react-infinite-loop', D.infiniteLoopPattern, relPath, locLine, D.infiniteLoopPatternSugestao, 'aviso'));
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -273,7 +273,7 @@ function detectWithBabel(src, relPath) {
|
|
|
273
273
|
}
|
|
274
274
|
const fragmentParent = path.findParent(p => p.isJSXFragment());
|
|
275
275
|
if (fragmentParent?.isJSXFragment()) {
|
|
276
|
-
const children = fragmentParent.node.children.filter(c => !t.isJSXText(c) ||
|
|
276
|
+
const children = fragmentParent.node.children.filter(c => !t.isJSXText(c) || c.value.trim() !== '');
|
|
277
277
|
if (children.length <= 1) {
|
|
278
278
|
pushOnce(warn('react-unnecessary-fragment', D.unnecessaryFragment, relPath, locLine, undefined, 'info'));
|
|
279
279
|
}
|
|
@@ -6,8 +6,43 @@ export function pontuarReact(src, _relPath) {
|
|
|
6
6
|
const issues = [];
|
|
7
7
|
const strengths = [];
|
|
8
8
|
const srcCompleto = src;
|
|
9
|
+
// React 19 patterns: forwardRef deprecation, React.FC, Server Components
|
|
10
|
+
const hasForwardRef = /\bforwardRef\s*\(/.test(srcCompleto);
|
|
11
|
+
if (hasForwardRef) {
|
|
12
|
+
issues.push('forwardRef é deprecated no React 19 — passe ref como prop direta');
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
score += 3;
|
|
16
|
+
strengths.push('Sem forwardRef (estilo React 19)');
|
|
17
|
+
}
|
|
18
|
+
const hasReactFC = /\bReact\.(FC|FunctionComponent)\b/.test(srcCompleto);
|
|
19
|
+
if (hasReactFC) {
|
|
20
|
+
issues.push('React.FC/FunctionComponent é deprecated no React 19');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
score += 2;
|
|
24
|
+
strengths.push('Sem React.FC (tipagem direta de props)');
|
|
25
|
+
}
|
|
26
|
+
const hasUseClient = /^['"]use client['"];/m.test(srcCompleto);
|
|
27
|
+
const hasServerFeatures = /useState|useEffect|useContext|onClick/.test(srcCompleto);
|
|
28
|
+
if (hasServerFeatures && !hasUseClient) {
|
|
29
|
+
const firstLine = srcCompleto.split('\n')[0]?.trim() ?? '';
|
|
30
|
+
if (firstLine.startsWith('import')) {
|
|
31
|
+
issues.push('Componente usa hooks/eventos sem diretiva \'use client\' (React 19)');
|
|
32
|
+
score = Math.max(0, score - 3);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (hasUseClient) {
|
|
36
|
+
score += 3;
|
|
37
|
+
strengths.push('Diretiva \'use client\' presente (React 19)');
|
|
38
|
+
}
|
|
39
|
+
const hasFormAction = /<form[^>]*\saction\s*=/.test(srcCompleto);
|
|
40
|
+
if (hasFormAction) {
|
|
41
|
+
strengths.push('Uso de form action (React 19)');
|
|
42
|
+
score += 2;
|
|
43
|
+
}
|
|
9
44
|
// Hooks usage & compliance (20 pontos)
|
|
10
|
-
const usaHooks = /use(State|Effect|Memo|Callback|Reducer|Ref|Context|LayoutEffect|ImperativeHandle|Transition|DeferredValue|SyncExternalStore|InsertionEffect|Id)/.test(srcCompleto);
|
|
45
|
+
const usaHooks = /use(State|Effect|Memo|Callback|Reducer|Ref|Context|LayoutEffect|ImperativeHandle|Transition|DeferredValue|SyncExternalStore|InsertionEffect|Id|Optimistic|ActionState|FormStatus)|\buse\s*\(/.test(srcCompleto);
|
|
11
46
|
let hookPontos = 0;
|
|
12
47
|
if (usaHooks) {
|
|
13
48
|
hookPontos = 10;
|