@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.
Files changed (268) hide show
  1. package/dist/analysts/architects/analyst-structure.js +1 -1
  2. package/dist/analysts/architects/archetypes-custom.js +8 -13
  3. package/dist/analysts/architects/detector-archetypes.js +39 -35
  4. package/dist/analysts/architects/orchestrator-archetypes.js +3 -3
  5. package/dist/analysts/architects/signals-project-advanced.js +2 -2
  6. package/dist/analysts/architects/signals-project.js +1 -1
  7. package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +20 -21
  8. package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +0 -3
  9. package/dist/analysts/circleci/analysts/analyst-circleci.js +22 -24
  10. package/dist/analysts/circleci/detectors/detector-circleci-structure.js +4 -7
  11. package/dist/analysts/converters/convert-registry.js +1 -1
  12. package/dist/analysts/converters/index.js +1 -1
  13. package/dist/analysts/corrections/auto-fix-engine.js +1 -1
  14. package/dist/analysts/corrections/corrector-structure.js +8 -7
  15. package/dist/analysts/corrections/map-reversion.js +2 -2
  16. package/dist/analysts/corrections/reorganize-barrels.js +3 -3
  17. package/dist/analysts/corrections/rewrite-testes-aliases.js +23 -22
  18. package/dist/analysts/corrections/type-safety/type-analyzer.js +3 -3
  19. package/dist/analysts/corrections/type-safety/type-validator.js +2 -2
  20. package/dist/analysts/corrections/type-safety/usage-analyzer.js +3 -3
  21. package/dist/analysts/css/analysts/analyst-css.js +27 -28
  22. package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +8 -15
  23. package/dist/analysts/detectors/detector-architecture.js +2 -2
  24. package/dist/analysts/detectors/detector-bugs-ml.js +8 -8
  25. package/dist/analysts/detectors/detector-code-fragile.js +11 -10
  26. package/dist/analysts/detectors/detector-dependencies.js +5 -5
  27. package/dist/analysts/detectors/detector-duplications.js +5 -8
  28. package/dist/analysts/detectors/detector-monorepo.js +10 -9
  29. package/dist/analysts/detectors/detector-performance.js +2 -1
  30. package/dist/analysts/detectors/detector-recommendation-architecture.js +1 -1
  31. package/dist/analysts/detectors/detector-security.js +1 -1
  32. package/dist/analysts/detectors/detector-structure.js +3 -3
  33. package/dist/analysts/github-actions/analysts/analyst-github-actions.js +5 -8
  34. package/dist/analysts/github-actions/detectors/detector-workflow-accessibility.js +1 -9
  35. package/dist/analysts/github-actions/detectors/detector-workflow-structure.js +1 -4
  36. package/dist/analysts/github-actions/detectors/detector-workflow-trigger-unsafe.js +6 -5
  37. package/dist/analysts/gitlab-ci/analysts/analyst-gitlab-ci.js +2 -2
  38. package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-structure.js +0 -3
  39. package/dist/analysts/go/analysts/analyst-best-practices.js +2 -2
  40. package/dist/analysts/go/analysts/analyst-concurrency.js +1 -1
  41. package/dist/analysts/go/analysts/analyst-security.js +1 -1
  42. package/dist/analysts/go/detectors/detector-conventions.js +1 -1
  43. package/dist/analysts/html/analysts/analyst-html.js +10 -12
  44. package/dist/analysts/html/detectors/detector-html.js +3 -3
  45. package/dist/analysts/js-ts/analysts/analyst-commands-cli.js +22 -22
  46. package/dist/analysts/js-ts/analysts/analyst-functions-long.js +5 -5
  47. package/dist/analysts/js-ts/analysts/analyst-patterns-usage.js +6 -2
  48. package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +81 -9
  49. package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +35 -6
  50. package/dist/analysts/js-ts/detectors/detector-context-smart.js +6 -6
  51. package/dist/analysts/js-ts/detectors/detector-leaks-memory.js +1 -1
  52. package/dist/analysts/js-ts/detectors/detector-types-unsafe.js +33 -2
  53. package/dist/analysts/php/analysts/analyst-php.js +3 -3
  54. package/dist/analysts/php/detectors/detector-php-best-practices.js +5 -5
  55. package/dist/analysts/php/detectors/detector-php-security.js +5 -5
  56. package/dist/analysts/php/scorers/scorer-php.js +2 -2
  57. package/dist/analysts/plugins/detector-documentation.js +5 -6
  58. package/dist/analysts/plugins/detector-markdown.js +7 -7
  59. package/dist/analysts/python/analysts/analyst-python.js +2 -5
  60. package/dist/analysts/react/analysts/analyst-react-hooks.js +63 -8
  61. package/dist/analysts/react/analysts/analyst-react.js +45 -1
  62. package/dist/analysts/react/corrections/correction-react.js +40 -0
  63. package/dist/analysts/react/detectors/detector-react-best-practices.js +4 -4
  64. package/dist/analysts/react/scorers/scorer-react.js +36 -1
  65. package/dist/analysts/rust/analysts/analyst-best-practices.js +42 -3
  66. package/dist/analysts/rust/analysts/analyst-memory.js +2 -2
  67. package/dist/analysts/rust/analysts/analyst-rust.js +52 -5
  68. package/dist/analysts/rust/analysts/analyst-security.js +29 -1
  69. package/dist/analysts/scorers/scorer.js +5 -5
  70. package/dist/analysts/shell/analysts/analyst-shell.js +81 -5
  71. package/dist/analysts/shell/corrections/correction-shell.js +34 -1
  72. package/dist/analysts/shell/detectors/detector-shell-security.js +88 -0
  73. package/dist/analysts/shell/scorers/scorer-shell.js +58 -1
  74. package/dist/analysts/strategists/operator-structure.js +5 -7
  75. package/dist/analysts/strategists/suggestions-contextuais.js +5 -2
  76. package/dist/analysts/svg/analysts/analyst-svg.js +1 -0
  77. package/dist/analysts/tailwind/analysts/analyst-tailwind.js +79 -55
  78. package/dist/analysts/tailwind/corrections/correction-tailwind.js +116 -9
  79. package/dist/analysts/tailwind/detectors/detector-tailwind-best-practices.js +122 -7
  80. package/dist/analysts/tailwind/scorers/scorer-tailwind.js +62 -10
  81. package/dist/analysts/xml/analysts/analyst-xml.js +2 -2
  82. package/dist/app/index.js +1 -0
  83. package/dist/bin/cli-bootstrap.js +6 -4
  84. package/dist/bin/index.js +6 -4
  85. package/dist/caretakers/scoring.js +1 -0
  86. package/dist/cli/commands/command-analysts.js +1 -1
  87. package/dist/cli/commands/command-azure.js +1 -1
  88. package/dist/cli/commands/command-circleci.js +1 -1
  89. package/dist/cli/commands/command-compliance.js +1 -1
  90. package/dist/cli/commands/command-diagnose.js +10 -13
  91. package/dist/cli/commands/command-fix-types.js +9 -32
  92. package/dist/cli/commands/command-formatters.js +2 -2
  93. package/dist/cli/commands/command-gitlab-ci.js +1 -1
  94. package/dist/cli/commands/command-guardian.js +1 -1
  95. package/dist/cli/commands/command-imports.js +6 -6
  96. package/dist/cli/commands/command-jenkins.js +1 -1
  97. package/dist/cli/commands/command-licenses.js +1 -1
  98. package/dist/cli/commands/command-marketplace.js +3 -3
  99. package/dist/cli/commands/command-names.js +3 -8
  100. package/dist/cli/commands/command-perf.js +7 -9
  101. package/dist/cli/commands/command-plugins.js +4 -4
  102. package/dist/cli/diagnostic/auto-fix.js +2 -2
  103. package/dist/cli/diagnostic/exporters/json-exporter.js +1 -1
  104. package/dist/cli/diagnostic/exporters/sharded-exporter.js +0 -3
  105. package/dist/cli/diagnostic/exporters/svg-optimization-exporter.js +1 -1
  106. package/dist/cli/diagnostic/handlers/archetype-handler.js +3 -6
  107. package/dist/cli/diagnostic/handlers/auto-fix-handler.js +4 -4
  108. package/dist/cli/diagnostic/handlers/guardian-handler.js +1 -1
  109. package/dist/cli/diagnostic/processing-diagnostic.js +31 -35
  110. package/dist/cli/diagnostic/processing-helpers.js +2 -2
  111. package/dist/cli/handlers/fix-types-exporter.js +1 -4
  112. package/dist/cli/helpers/display-frame.js +5 -5
  113. package/dist/core/config/auto/auto-fix-config.js +0 -6
  114. package/dist/core/config/chalk-safe.js +2 -2
  115. package/dist/core/config/config.js +6 -8
  116. package/dist/core/config/conventions.js +1 -1
  117. package/dist/core/config/security.js +2 -2
  118. package/dist/core/execution/analysis-cache.js +5 -8
  119. package/dist/core/execution/executor.js +8 -19
  120. package/dist/core/execution/inquisitor.js +24 -21
  121. package/dist/core/execution/results.js +1 -0
  122. package/dist/core/execution/scanner.js +3 -3
  123. package/dist/core/messages/en/analysts/analyst-github-actions-messages.js +1 -1
  124. package/dist/core/messages/en/analysts/analyst-patterns-usage-messages.js +1 -0
  125. package/dist/core/messages/en/analysts/analyst-shell-messages.js +15 -0
  126. package/dist/core/messages/en/analysts/detector-constructions-syntactic-messages.js +6 -0
  127. package/dist/core/messages/en/analysts/detector-js-ts-messages.js +27 -0
  128. package/dist/core/messages/en/analysts/detector-react-messages.js +8 -0
  129. package/dist/core/messages/en/analysts/detector-shell-messages.js +17 -0
  130. package/dist/core/messages/en/analysts/detector-tailwind-messages.js +3 -3
  131. package/dist/core/messages/en/core/plugin-messages.js +31 -3
  132. package/dist/core/messages/en/log/log.js +4 -4
  133. package/dist/core/messages/en/ui/filter-config.js +1 -5
  134. package/dist/core/messages/i18n-system.js +3 -3
  135. package/dist/core/messages/index.js +3 -3
  136. package/dist/core/messages/ja/analysts/analyst-github-actions-messages.js +1 -1
  137. package/dist/core/messages/ja/analysts/analyst-patterns-usage-messages.js +1 -0
  138. package/dist/core/messages/ja/analysts/analyst-shell-messages.js +15 -0
  139. package/dist/core/messages/ja/analysts/detector-constructions-syntactic-messages.js +6 -0
  140. package/dist/core/messages/ja/analysts/detector-js-ts-messages.js +27 -0
  141. package/dist/core/messages/ja/analysts/detector-react-messages.js +8 -0
  142. package/dist/core/messages/ja/analysts/detector-shell-messages.js +17 -0
  143. package/dist/core/messages/ja/analysts/detector-tailwind-messages.js +3 -3
  144. package/dist/core/messages/ja/core/plugin-messages.js +31 -3
  145. package/dist/core/messages/ja/log/log.js +4 -4
  146. package/dist/core/messages/ja/ui/filter-config.js +1 -1
  147. package/dist/core/messages/pt/analysts/analyst-github-actions-messages.js +1 -1
  148. package/dist/core/messages/pt/analysts/analyst-patterns-usage-messages.js +1 -0
  149. package/dist/core/messages/pt/analysts/analyst-shell-messages.js +15 -0
  150. package/dist/core/messages/pt/analysts/detector-constructions-syntactic-messages.js +6 -0
  151. package/dist/core/messages/pt/analysts/detector-js-ts-messages.js +27 -0
  152. package/dist/core/messages/pt/analysts/detector-react-messages.js +8 -0
  153. package/dist/core/messages/pt/analysts/detector-shell-messages.js +17 -0
  154. package/dist/core/messages/pt/analysts/detector-tailwind-messages.js +3 -3
  155. package/dist/core/messages/pt/core/plugin-messages.js +31 -3
  156. package/dist/core/messages/pt/log/log-engine.js +6 -6
  157. package/dist/core/messages/pt/log/log.js +3 -3
  158. package/dist/core/messages/pt/ui/filter-config.js +1 -1
  159. package/dist/core/messages/shared/icons.js +3 -3
  160. package/dist/core/messages/zh/analysts/analyst-github-actions-messages.js +1 -1
  161. package/dist/core/messages/zh/analysts/analyst-patterns-usage-messages.js +1 -0
  162. package/dist/core/messages/zh/analysts/analyst-shell-messages.js +15 -0
  163. package/dist/core/messages/zh/analysts/detector-constructions-syntactic-messages.js +6 -0
  164. package/dist/core/messages/zh/analysts/detector-js-ts-messages.js +27 -0
  165. package/dist/core/messages/zh/analysts/detector-react-messages.js +8 -0
  166. package/dist/core/messages/zh/analysts/detector-shell-messages.js +17 -0
  167. package/dist/core/messages/zh/analysts/detector-tailwind-messages.js +3 -3
  168. package/dist/core/messages/zh/core/plugin-messages.js +31 -3
  169. package/dist/core/messages/zh/log/log.js +3 -3
  170. package/dist/core/messages/zh/ui/filter-config.js +1 -5
  171. package/dist/core/parsing/filters.js +3 -3
  172. package/dist/core/parsing/langs/json.js +1 -1
  173. package/dist/core/parsing/langs/python.js +1 -1
  174. package/dist/core/parsing/langs/typescript.js +1 -1
  175. package/dist/core/parsing/parser.js +2 -2
  176. package/dist/core/registry/file-registry.js +3 -2
  177. package/dist/core/registry/paths.js +3 -3
  178. package/dist/core/reporting/default-reporter.js +2 -2
  179. package/dist/core/schema/version.js +6 -6
  180. package/dist/core/workers/worker-executor.js +3 -3
  181. package/dist/core/workers/worker-pool.js +0 -0
  182. package/dist/guardian/baseline.js +1 -1
  183. package/dist/guardian/gpg.js +3 -1
  184. package/dist/guardian/integrity.js +2 -7
  185. package/dist/guardian/records.js +6 -1
  186. package/dist/guardian/result.js +1 -1
  187. package/dist/guardian/sentinel.js +6 -1
  188. package/dist/guardian/watcher-hidden.js +5 -1
  189. package/dist/licenses/fs-utils.js +4 -1
  190. package/dist/licenses/generate-notices.js +1 -1
  191. package/dist/licenses/header-options.js +0 -1
  192. package/dist/licenses/index.js +1 -0
  193. package/dist/licenses/normalizer.js +10 -9
  194. package/dist/node.loader.js +224 -4
  195. package/dist/reports/analise-async-patterns.js +1 -1
  196. package/dist/reports/filter-smart.js +2 -5
  197. package/dist/reports/generator-report.js +4 -4
  198. package/dist/reports/report-archetypes.js +10 -10
  199. package/dist/reports/report-caretaker-health.js +2 -1
  200. package/dist/sdk/analyzer.js +1 -1
  201. package/dist/shared/data-processing/fragment-report.js +10 -10
  202. package/dist/shared/data-processing/json.js +1 -1
  203. package/dist/shared/data-processing/occurrences.js +3 -2
  204. package/dist/shared/formatters/code-min-formatter.js +1 -1
  205. package/dist/shared/formatters/engines/prettier.js +1 -1
  206. package/dist/shared/formatters/engines/stylelint.js +6 -9
  207. package/dist/shared/formatters/formatter.js +8 -8
  208. package/dist/shared/formatters/formatters/code.js +3 -3
  209. package/dist/shared/formatters/formatters/css.js +2 -10
  210. package/dist/shared/formatters/formatters/dotfiles.js +1 -1
  211. package/dist/shared/formatters/formatters/html.js +1 -5
  212. package/dist/shared/formatters/formatters/json.js +0 -3
  213. package/dist/shared/formatters/formatters/markdown.js +18 -23
  214. package/dist/shared/formatters/formatters/properties.js +2 -2
  215. package/dist/shared/formatters/formatters/shell.js +1 -4
  216. package/dist/shared/formatters/formatters/sql.js +0 -3
  217. package/dist/shared/formatters/formatters/xml.js +5 -7
  218. package/dist/shared/formatters/formatters/yaml.js +2 -7
  219. package/dist/shared/formatters/index.js +1 -1
  220. package/dist/shared/helpers/framework-detector.js +1 -1
  221. package/dist/shared/helpers/json.js +1 -1
  222. package/dist/shared/helpers/magic-constants-whitelist.js +1 -1
  223. package/dist/shared/helpers/masking.js +17 -17
  224. package/dist/shared/helpers/reader-report.js +0 -12
  225. package/dist/shared/helpers/structural-hash.js +3 -2
  226. package/dist/shared/helpers/structure.js +1 -1
  227. package/dist/shared/marketplace/registry.js +1 -1
  228. package/dist/shared/marketplace/remote.js +4 -4
  229. package/dist/shared/persistence/persistence.js +0 -1
  230. package/dist/shared/plugins/core-plugin.js +3 -1
  231. package/dist/shared/plugins/registry.js +3 -2
  232. package/dist/types/caretakers/imports-barrel.js +1 -0
  233. package/dist/types/caretakers/index.js +4 -0
  234. package/dist/types/caretakers/map-reversion.js +1 -0
  235. package/dist/types/caretakers/pruning.js +1 -0
  236. package/dist/types/caretakers/scoring.js +1 -0
  237. package/dist/types/common/analysts.js +1 -1
  238. package/dist/types/core/config/filters.js +1 -0
  239. package/dist/types/core/config/index.js +2 -0
  240. package/dist/types/core/execution/ambiente.js +1 -0
  241. package/dist/types/core/execution/linguagens.js +1 -0
  242. package/dist/types/core/execution/parse-errors.js +1 -0
  243. package/dist/types/core/execution/results.js +1 -0
  244. package/dist/types/core/execution/structure-json.js +1 -0
  245. package/dist/types/core/index.js +1 -0
  246. package/dist/types/core/messages/index.js +1 -0
  247. package/dist/types/core/messages/log.js +1 -0
  248. package/dist/types/core/parsing/babel-narrow.js +1 -0
  249. package/dist/types/core/parsing/index.js +1 -0
  250. package/dist/types/core/runtime.js +1 -0
  251. package/dist/types/guardian/index.js +2 -0
  252. package/dist/types/guardian/integrity.js +7 -0
  253. package/dist/types/guardian/snapshot.js +1 -0
  254. package/dist/types/index.js +4 -4
  255. package/dist/types/licenses/index.js +2 -0
  256. package/dist/types/processing/display.js +16 -13
  257. package/dist/types/processing/filters.js +3 -7
  258. package/dist/types/reports/compliance.js +1 -0
  259. package/dist/types/reports/streaming.js +1 -0
  260. package/dist/types/scripts/index.js +1 -0
  261. package/dist/types/scripts/migrar-aliases.js +1 -0
  262. package/dist/types/sdk/index.js +1 -0
  263. package/dist/types/shared/index.js +2 -0
  264. package/dist/types/vulnerabilities/index.js +2 -0
  265. package/dist/vulnerabilities/npm-audit.js +0 -1
  266. package/dist/vulnerabilities/scanner.js +2 -1
  267. package/dist/vulnerabilities/vulnerabilities.js +0 -1
  268. 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 (path.node.body && textoTitulo.isBlockStatement(path.node.body)) {
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] ?? 'any');
64
- sugestao = messages.DetectorJsTsMensagens.tipoAnyComUtilidadeSugestao(anyMatch[1] ?? 'any');
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 = (funcMatch.index ?? 0) + (funcMatch[0].length ?? 0);
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 ?? 0);
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, (match.index ?? 0) - 40), match.index ?? 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 ?? 0;
66
- const bodyStart = funcStart + (funcMatch[0].length ?? 0);
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, (match.index ?? 0) - 100), match.index ?? 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, (match.index ?? 0) - 150), match.index ?? 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, (match.index ?? 0) - 40), (match.index ?? 0) + 120);
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, (match.index ?? 0) - 120), match.index ?? 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 ?? 0;
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, (match.index ?? 0) - 60), match.index ?? 0);
142
- if (/pass/i.test(before) || /pass/i.test(scan.slice(match.index ?? 0, (match.index ?? 0) + 80))) {
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 async function pontuarPhp(src, relPath) {
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 = (fn.index ?? 0) + (fn[0].length ?? 0);
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
- if (!acc[prioridade]) {
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: Array.isArray(overrideConfig.paths) ? overrideConfig.paths : [],
104
- patterns: Array.isArray(overrideConfig.patterns) ? overrideConfig.patterns : [],
105
- dirs: Array.isArray(overrideConfig.dirs) ? overrideConfig.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([...(base.paths || []), ...overridePaths]),
114
- patterns: uniq([...(base.patterns || []), ...overridePatterns]),
115
- dirs: uniq([...(base.dirs || []), ...overrideDirs])
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: async (src, relPath, _ast, fullCaminho) => {
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 /use(State|Effect|Memo|Callback|Reducer|Ref|LayoutEffect|ImperativeHandle|Transition|DeferredValue|SyncExternalStore|InsertionEffect|Id|DebugValue)/.test(src);
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 ?? 0;
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 ?? 0;
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 (!/^(useState|useEffect|useMemo|useCallback|useRef|useContext|useReducer|useLayoutEffect|useImperativeHandle|useDebugValue|useDeferredValue|useTransition|useSyncExternalStore|useInsertionEffect|useId)$/.test(hookName)) {
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
- return { name: callee.name };
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 (sem ESLint).',
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 = String(arquivo.name.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) || name === 'useEffect' || name === 'useLayoutEffect' || name === 'useMemo' || name === 'useCallback' || name === 'useState' || name === 'useReducer' || name === 'useRef' || name === 'useContext' || name === 'useImperativeHandle' || name === 'useInsertionEffect' || name === 'useDebugValue' || name === 'useDeferredValue' || name === 'useTransition' || name === 'useSyncExternalStore' || name === 'useId';
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 (bodyArg && (t.isFunctionExpression(bodyArg) || t.isArrowFunctionExpression(bodyArg)) && bodyArg.body && t.isBlockStatement(bodyArg.body)) {
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 && depsArg && t.isArrayExpression(depsArg)) {
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) || (c.value.trim() ?? '') !== '') ?? [];
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;