@mocoto/mahoraga 0.14.1 → 0.14.3

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 (306) hide show
  1. package/README.md +0 -34
  2. package/dist/analysts/architects/analyst-structure.js +20 -20
  3. package/dist/analysts/architects/archetypes-custom.js +16 -19
  4. package/dist/analysts/architects/detector-archetypes.js +43 -36
  5. package/dist/analysts/architects/orchestrator-archetypes.js +3 -3
  6. package/dist/analysts/architects/signals-project-advanced.js +26 -28
  7. package/dist/analysts/architects/signals-project.js +2 -2
  8. package/dist/analysts/architects/strategist-structure.js +47 -66
  9. package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +41 -35
  10. package/dist/analysts/azure-pipelines/corrections/correction-azure.js +22 -19
  11. package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +21 -18
  12. package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +25 -20
  13. package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +39 -40
  14. package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
  15. package/dist/analysts/circleci/analysts/analyst-circleci.js +33 -27
  16. package/dist/analysts/circleci/corrections/correction-circleci.js +22 -20
  17. package/dist/analysts/circleci/detectors/detector-circleci-performance.js +23 -19
  18. package/dist/analysts/circleci/detectors/detector-circleci-security.js +78 -82
  19. package/dist/analysts/circleci/detectors/detector-circleci-structure.js +22 -22
  20. package/dist/analysts/converters/convert-registry.js +1 -1
  21. package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
  22. package/dist/analysts/converters/index.js +1 -1
  23. package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
  24. package/dist/analysts/corrections/analyst-scoring.js +24 -21
  25. package/dist/analysts/corrections/auto-fix-engine.js +1 -1
  26. package/dist/analysts/corrections/corrector-structure.js +43 -42
  27. package/dist/analysts/corrections/fix-alias-imports.js +10 -6
  28. package/dist/analysts/corrections/fix-md-fences.js +23 -23
  29. package/dist/analysts/corrections/map-reversion.js +56 -53
  30. package/dist/analysts/corrections/pruning.js +19 -16
  31. package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +57 -61
  32. package/dist/analysts/corrections/quick-fixes/fix-unknown-to-specific-type.js +30 -30
  33. package/dist/analysts/corrections/reorganize-barrels.js +13 -13
  34. package/dist/analysts/corrections/rewrite-testes-aliases.js +23 -22
  35. package/dist/analysts/corrections/scoring-shared.js +19 -18
  36. package/dist/analysts/corrections/type-safety/context-analyzer.js +15 -10
  37. package/dist/analysts/corrections/type-safety/type-analyzer.js +3 -3
  38. package/dist/analysts/corrections/type-safety/type-validator.js +4 -3
  39. package/dist/analysts/corrections/type-safety/usage-analyzer.js +39 -42
  40. package/dist/analysts/css/analysts/analyst-css.js +42 -42
  41. package/dist/analysts/css/corrections/correction-css.js +19 -11
  42. package/dist/analysts/css/detectors/detector-css-best-practices.js +129 -118
  43. package/dist/analysts/css/scorers/scorer-css.js +23 -18
  44. package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +24 -17
  45. package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +31 -16
  46. package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +28 -30
  47. package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +79 -54
  48. package/dist/analysts/css-in-js/shared.js +7 -0
  49. package/dist/analysts/detectors/detector-architecture.js +91 -85
  50. package/dist/analysts/detectors/detector-bugs-ml.js +29 -27
  51. package/dist/analysts/detectors/detector-code-fragile.js +41 -36
  52. package/dist/analysts/detectors/detector-dependencies.js +5 -5
  53. package/dist/analysts/detectors/detector-duplications.js +26 -26
  54. package/dist/analysts/detectors/detector-monorepo.js +51 -45
  55. package/dist/analysts/detectors/detector-performance.js +24 -17
  56. package/dist/analysts/detectors/detector-recommendation-architecture.js +40 -36
  57. package/dist/analysts/detectors/detector-security.js +43 -42
  58. package/dist/analysts/detectors/detector-structure.js +31 -28
  59. package/dist/analysts/github-actions/analysts/analyst-github-actions.js +5 -8
  60. package/dist/analysts/github-actions/detectors/detector-workflow-accessibility.js +1 -9
  61. package/dist/analysts/github-actions/detectors/detector-workflow-structure.js +1 -4
  62. package/dist/analysts/github-actions/detectors/detector-workflow-trigger-unsafe.js +6 -5
  63. package/dist/analysts/gitlab-ci/analysts/analyst-gitlab-ci.js +2 -2
  64. package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-structure.js +0 -3
  65. package/dist/analysts/go/analysts/analyst-best-practices.js +2 -2
  66. package/dist/analysts/go/analysts/analyst-concurrency.js +1 -1
  67. package/dist/analysts/go/analysts/analyst-security.js +1 -1
  68. package/dist/analysts/go/detectors/detector-conventions.js +1 -1
  69. package/dist/analysts/html/analysts/analyst-html.js +10 -12
  70. package/dist/analysts/html/detectors/detector-html.js +3 -3
  71. package/dist/analysts/js-ts/analysts/analyst-commands-cli.js +22 -22
  72. package/dist/analysts/js-ts/analysts/analyst-functions-long.js +5 -5
  73. package/dist/analysts/js-ts/analysts/analyst-patterns-usage.js +6 -2
  74. package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +84 -9
  75. package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +33 -10
  76. package/dist/analysts/js-ts/detectors/detector-context-smart.js +6 -6
  77. package/dist/analysts/js-ts/detectors/detector-leaks-memory.js +1 -1
  78. package/dist/analysts/js-ts/detectors/detector-types-unsafe.js +33 -2
  79. package/dist/analysts/php/analysts/analyst-php.js +3 -3
  80. package/dist/analysts/php/detectors/detector-php-best-practices.js +5 -5
  81. package/dist/analysts/php/detectors/detector-php-security.js +5 -5
  82. package/dist/analysts/php/scorers/scorer-php.js +2 -2
  83. package/dist/analysts/plugins/detector-documentation.js +6 -6
  84. package/dist/analysts/plugins/detector-markdown.js +7 -7
  85. package/dist/analysts/python/analysts/analyst-python.js +2 -5
  86. package/dist/analysts/react/analysts/analyst-react-hooks.js +66 -9
  87. package/dist/analysts/react/analysts/analyst-react.js +46 -2
  88. package/dist/analysts/react/corrections/correction-react.js +41 -1
  89. package/dist/analysts/react/detectors/detector-react-best-practices.js +5 -5
  90. package/dist/analysts/react/scorers/scorer-react.js +36 -1
  91. package/dist/analysts/registry/autodiscovery.js +2 -2
  92. package/dist/analysts/rust/analysts/analyst-best-practices.js +42 -3
  93. package/dist/analysts/rust/analysts/analyst-memory.js +2 -2
  94. package/dist/analysts/rust/analysts/analyst-rust.js +52 -5
  95. package/dist/analysts/rust/analysts/analyst-security.js +29 -1
  96. package/dist/analysts/scorers/scorer.js +5 -5
  97. package/dist/analysts/shell/analysts/analyst-shell.js +81 -5
  98. package/dist/analysts/shell/corrections/correction-shell.js +34 -1
  99. package/dist/analysts/shell/detectors/detector-shell-security.js +88 -0
  100. package/dist/analysts/shell/scorers/scorer-shell.js +58 -1
  101. package/dist/analysts/strategists/operator-structure.js +5 -7
  102. package/dist/analysts/strategists/suggestions-contextuais.js +5 -2
  103. package/dist/analysts/svg/analysts/analyst-svg.js +1 -0
  104. package/dist/analysts/tailwind/analysts/analyst-tailwind.js +79 -55
  105. package/dist/analysts/tailwind/corrections/correction-tailwind.js +108 -9
  106. package/dist/analysts/tailwind/detectors/detector-tailwind-best-practices.js +122 -7
  107. package/dist/analysts/tailwind/scorers/scorer-tailwind.js +62 -11
  108. package/dist/analysts/xml/analysts/analyst-xml.js +2 -2
  109. package/dist/app/index.js +1 -0
  110. package/dist/bin/cli-bootstrap.js +6 -4
  111. package/dist/bin/index.js +6 -4
  112. package/dist/caretakers/scoring.js +1 -0
  113. package/dist/cli/commands/command-analysts.js +4 -3
  114. package/dist/cli/commands/command-azure.js +1 -1
  115. package/dist/cli/commands/command-circleci.js +1 -1
  116. package/dist/cli/commands/command-compliance.js +1 -1
  117. package/dist/cli/commands/command-diagnose.js +10 -13
  118. package/dist/cli/commands/command-fix-types.js +9 -32
  119. package/dist/cli/commands/command-formatters.js +2 -2
  120. package/dist/cli/commands/command-github-actions.js +1 -1
  121. package/dist/cli/commands/command-gitlab-ci.js +1 -1
  122. package/dist/cli/commands/command-guardian.js +1 -1
  123. package/dist/cli/commands/command-imports.js +6 -6
  124. package/dist/cli/commands/command-jenkins.js +1 -1
  125. package/dist/cli/commands/command-licenses.js +1 -1
  126. package/dist/cli/commands/command-marketplace.js +7 -7
  127. package/dist/cli/commands/command-names.js +3 -8
  128. package/dist/cli/commands/command-perf.js +7 -9
  129. package/dist/cli/commands/command-plugins.js +4 -4
  130. package/dist/cli/diagnostic/auto-fix.js +2 -2
  131. package/dist/cli/diagnostic/exporters/json-exporter.js +1 -1
  132. package/dist/cli/diagnostic/exporters/sharded-exporter.js +0 -3
  133. package/dist/cli/diagnostic/exporters/svg-optimization-exporter.js +1 -1
  134. package/dist/cli/diagnostic/filters.js +9 -9
  135. package/dist/cli/diagnostic/handlers/archetype-handler.js +3 -6
  136. package/dist/cli/diagnostic/handlers/auto-fix-handler.js +4 -4
  137. package/dist/cli/diagnostic/handlers/guardian-handler.js +1 -1
  138. package/dist/cli/diagnostic/processing-diagnostic.js +31 -35
  139. package/dist/cli/diagnostic/processing-helpers.js +2 -2
  140. package/dist/cli/handlers/fix-types-exporter.js +1 -4
  141. package/dist/cli/helpers/display-frame.js +5 -5
  142. package/dist/core/config/auto/auto-fix-config.js +0 -6
  143. package/dist/core/config/chalk-safe.js +6 -6
  144. package/dist/core/config/config.js +11 -13
  145. package/dist/core/config/conventions.js +1 -1
  146. package/dist/core/config/security.js +2 -2
  147. package/dist/core/execution/analysis-cache.js +5 -8
  148. package/dist/core/execution/ast-cache.js +1 -3
  149. package/dist/core/execution/executor.js +8 -21
  150. package/dist/core/execution/inquisitor.js +24 -21
  151. package/dist/core/execution/results.js +1 -0
  152. package/dist/core/execution/scanner.js +4 -4
  153. package/dist/core/messages/en/analysts/analyst-github-actions-messages.js +1 -1
  154. package/dist/core/messages/en/analysts/analyst-patterns-usage-messages.js +1 -0
  155. package/dist/core/messages/en/analysts/analyst-shell-messages.js +15 -0
  156. package/dist/core/messages/en/analysts/detector-constructions-syntactic-messages.js +6 -0
  157. package/dist/core/messages/en/analysts/detector-js-ts-messages.js +27 -0
  158. package/dist/core/messages/en/analysts/detector-react-messages.js +8 -0
  159. package/dist/core/messages/en/analysts/detector-shell-messages.js +17 -0
  160. package/dist/core/messages/en/analysts/detector-tailwind-messages.js +3 -3
  161. package/dist/core/messages/en/analysts/report-circleci-messages.js +7 -7
  162. package/dist/core/messages/en/caretakers/caretaker-messages.js +1 -7
  163. package/dist/core/messages/en/core/plugin-messages.js +31 -3
  164. package/dist/core/messages/en/log/log.js +4 -4
  165. package/dist/core/messages/en/ui/filter-config.js +1 -5
  166. package/dist/core/messages/i18n-system.js +3 -3
  167. package/dist/core/messages/index.js +3 -3
  168. package/dist/core/messages/ja/analysts/analyst-github-actions-messages.js +1 -1
  169. package/dist/core/messages/ja/analysts/analyst-patterns-usage-messages.js +1 -0
  170. package/dist/core/messages/ja/analysts/analyst-shell-messages.js +15 -0
  171. package/dist/core/messages/ja/analysts/detector-constructions-syntactic-messages.js +6 -0
  172. package/dist/core/messages/ja/analysts/detector-js-ts-messages.js +27 -0
  173. package/dist/core/messages/ja/analysts/detector-react-messages.js +8 -0
  174. package/dist/core/messages/ja/analysts/detector-shell-messages.js +17 -0
  175. package/dist/core/messages/ja/analysts/detector-tailwind-messages.js +3 -3
  176. package/dist/core/messages/ja/analysts/report-circleci-messages.js +7 -7
  177. package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
  178. package/dist/core/messages/ja/core/plugin-messages.js +31 -3
  179. package/dist/core/messages/ja/log/log.js +4 -4
  180. package/dist/core/messages/ja/ui/filter-config.js +1 -1
  181. package/dist/core/messages/pt/analysts/analyst-github-actions-messages.js +1 -1
  182. package/dist/core/messages/pt/analysts/analyst-patterns-usage-messages.js +1 -0
  183. package/dist/core/messages/pt/analysts/analyst-shell-messages.js +15 -0
  184. package/dist/core/messages/pt/analysts/detector-constructions-syntactic-messages.js +6 -0
  185. package/dist/core/messages/pt/analysts/detector-js-ts-messages.js +27 -0
  186. package/dist/core/messages/pt/analysts/detector-react-messages.js +8 -0
  187. package/dist/core/messages/pt/analysts/detector-shell-messages.js +17 -0
  188. package/dist/core/messages/pt/analysts/detector-tailwind-messages.js +3 -3
  189. package/dist/core/messages/pt/analysts/report-circleci-messages.js +7 -7
  190. package/dist/core/messages/pt/caretakers/caretaker-messages.js +1 -7
  191. package/dist/core/messages/pt/core/plugin-messages.js +31 -3
  192. package/dist/core/messages/pt/log/log-engine.js +6 -6
  193. package/dist/core/messages/pt/log/log.js +3 -3
  194. package/dist/core/messages/pt/ui/filter-config.js +1 -1
  195. package/dist/core/messages/shared/icons.js +3 -3
  196. package/dist/core/messages/zh/analysts/analyst-github-actions-messages.js +1 -1
  197. package/dist/core/messages/zh/analysts/analyst-patterns-usage-messages.js +1 -0
  198. package/dist/core/messages/zh/analysts/analyst-shell-messages.js +15 -0
  199. package/dist/core/messages/zh/analysts/detector-constructions-syntactic-messages.js +6 -0
  200. package/dist/core/messages/zh/analysts/detector-js-ts-messages.js +27 -0
  201. package/dist/core/messages/zh/analysts/detector-react-messages.js +8 -0
  202. package/dist/core/messages/zh/analysts/detector-shell-messages.js +17 -0
  203. package/dist/core/messages/zh/analysts/detector-tailwind-messages.js +3 -3
  204. package/dist/core/messages/zh/analysts/report-circleci-messages.js +7 -7
  205. package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
  206. package/dist/core/messages/zh/core/plugin-messages.js +31 -3
  207. package/dist/core/messages/zh/log/log.js +3 -3
  208. package/dist/core/messages/zh/ui/filter-config.js +1 -5
  209. package/dist/core/parsing/filters.js +3 -3
  210. package/dist/core/parsing/langs/json.js +1 -1
  211. package/dist/core/parsing/langs/python.js +1 -1
  212. package/dist/core/parsing/langs/typescript.js +1 -1
  213. package/dist/core/parsing/parser.js +3 -4
  214. package/dist/core/parsing/utils.js +1 -0
  215. package/dist/core/registry/file-registry.js +3 -2
  216. package/dist/core/registry/paths.js +3 -3
  217. package/dist/core/reporting/default-reporter.js +2 -2
  218. package/dist/core/schema/version.js +6 -6
  219. package/dist/core/workers/worker-executor.js +3 -3
  220. package/dist/core/workers/worker-pool.js +0 -0
  221. package/dist/guardian/baseline.js +1 -1
  222. package/dist/guardian/gpg.js +3 -1
  223. package/dist/guardian/integrity.js +2 -7
  224. package/dist/guardian/records.js +7 -1
  225. package/dist/guardian/result.js +1 -1
  226. package/dist/guardian/sentinel.js +7 -1
  227. package/dist/guardian/watcher-hidden.js +6 -1
  228. package/dist/licenses/fs-utils.js +5 -1
  229. package/dist/licenses/generate-notices.js +1 -1
  230. package/dist/licenses/index.js +1 -0
  231. package/dist/licenses/normalizer.js +10 -9
  232. package/dist/node.loader.js +224 -4
  233. package/dist/reports/analise-async-patterns.js +1 -1
  234. package/dist/reports/filter-smart.js +2 -5
  235. package/dist/reports/generator-report.js +7 -7
  236. package/dist/reports/report-archetypes.js +10 -10
  237. package/dist/reports/report-caretaker-health.js +2 -1
  238. package/dist/reports/report-patterns-usage.js +1 -1
  239. package/dist/sdk/analyzer.js +1 -1
  240. package/dist/shared/data-processing/fragment-report.js +11 -11
  241. package/dist/shared/data-processing/json.js +1 -1
  242. package/dist/shared/data-processing/occurrences.js +3 -2
  243. package/dist/shared/formatters/code-min-formatter.js +1 -1
  244. package/dist/shared/formatters/engines/prettier.js +1 -1
  245. package/dist/shared/formatters/engines/stylelint.js +43 -40
  246. package/dist/shared/formatters/formatter.js +8 -8
  247. package/dist/shared/formatters/formatters/code.js +3 -3
  248. package/dist/shared/formatters/formatters/css.js +2 -10
  249. package/dist/shared/formatters/formatters/dotfiles.js +1 -1
  250. package/dist/shared/formatters/formatters/html.js +1 -5
  251. package/dist/shared/formatters/formatters/json.js +0 -3
  252. package/dist/shared/formatters/formatters/markdown.js +19 -24
  253. package/dist/shared/formatters/formatters/properties.js +2 -2
  254. package/dist/shared/formatters/formatters/shell.js +1 -4
  255. package/dist/shared/formatters/formatters/sql.js +0 -3
  256. package/dist/shared/formatters/formatters/xml.js +5 -7
  257. package/dist/shared/formatters/formatters/yaml.js +2 -7
  258. package/dist/shared/formatters/index.js +1 -1
  259. package/dist/shared/helpers/framework-detector.js +6 -2
  260. package/dist/shared/helpers/json.js +1 -1
  261. package/dist/shared/helpers/magic-constants-whitelist.js +1 -1
  262. package/dist/shared/helpers/masking.js +17 -17
  263. package/dist/shared/helpers/reader-report.js +0 -12
  264. package/dist/shared/helpers/structural-hash.js +3 -2
  265. package/dist/shared/helpers/structure.js +1 -1
  266. package/dist/shared/marketplace/registry.js +1 -1
  267. package/dist/shared/marketplace/remote.js +4 -4
  268. package/dist/shared/persistence/persistence.js +13 -6
  269. package/dist/shared/plugins/core-plugin.js +5 -1
  270. package/dist/shared/plugins/registry.js +3 -2
  271. package/dist/types/analysts/tailwind-correction.js +2 -0
  272. package/dist/types/caretakers/imports-barrel.js +1 -0
  273. package/dist/types/caretakers/index.js +4 -0
  274. package/dist/types/caretakers/map-reversion.js +1 -0
  275. package/dist/types/caretakers/pruning.js +1 -0
  276. package/dist/types/caretakers/scoring.js +1 -0
  277. package/dist/types/common/analysts.js +1 -1
  278. package/dist/types/core/config/filters.js +1 -0
  279. package/dist/types/core/config/index.js +2 -0
  280. package/dist/types/core/execution/ambiente.js +1 -0
  281. package/dist/types/core/execution/linguagens.js +1 -0
  282. package/dist/types/core/execution/parse-errors.js +1 -0
  283. package/dist/types/core/execution/results.js +1 -0
  284. package/dist/types/core/execution/structure-json.js +1 -0
  285. package/dist/types/core/index.js +1 -0
  286. package/dist/types/core/messages/index.js +1 -0
  287. package/dist/types/core/messages/log.js +1 -0
  288. package/dist/types/core/parsing/babel-narrow.js +1 -0
  289. package/dist/types/core/parsing/index.js +1 -0
  290. package/dist/types/core/runtime.js +1 -0
  291. package/dist/types/guardian/index.js +2 -0
  292. package/dist/types/guardian/integrity.js +7 -0
  293. package/dist/types/guardian/snapshot.js +1 -0
  294. package/dist/types/index.js +4 -4
  295. package/dist/types/licenses/index.js +2 -0
  296. package/dist/types/processing/display.js +16 -13
  297. package/dist/types/processing/filters.js +8 -11
  298. package/dist/types/reports/compliance.js +1 -0
  299. package/dist/types/reports/streaming.js +1 -0
  300. package/dist/types/scripts/index.js +1 -0
  301. package/dist/types/scripts/migrar-aliases.js +1 -0
  302. package/dist/types/sdk/index.js +1 -0
  303. package/dist/types/shared/index.js +2 -0
  304. package/dist/types/vulnerabilities/index.js +2 -0
  305. package/dist/vulnerabilities/scanner.js +3 -2
  306. package/package.json +14 -20
@@ -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++) {
@@ -173,6 +173,7 @@ function detectarPadroesDocumentacao(src, problemas, relPath) {
173
173
  // 2. Tem exportações públicas E
174
174
  // 3. Não tem nenhum JSDoc E
175
175
  // 4. Não é arquivo de teste/config/setup
176
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- vars mutated inside forEach closure; TS can't track
176
177
  if (isLibraryArquivo && temExportacaoPublica && !temJSDoc && !isInternalArquivo && src.length > 200) {
177
178
  // Arquivo grande o suficiente para justificar documentação
178
179
  problemas.push({
@@ -239,13 +240,11 @@ function detectarProblemasDocumentacaoAST(ast, problemas) {
239
240
  }
240
241
  function agruparPorPrioridade(problemas) {
241
242
  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);
243
+ const prioridade = problema.prioridade ?? 'media';
244
+ if (!(prioridade in acc)) {
245
+ acc[prioridade] = [];
248
246
  }
247
+ acc[prioridade].push(problema);
249
248
  return acc;
250
249
  }, {});
251
250
  }
@@ -257,6 +256,7 @@ function mapearPrioridadeParaNivel(prioridade) {
257
256
  case 'media':
258
257
  return 'aviso';
259
258
  case 'baixa':
259
+ return 'info';
260
260
  case undefined: {
261
261
  throw new Error('Not implemented yet: undefined case');
262
262
  }
@@ -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,42 @@ 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
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime safety for match.index === 0
118
+ if (src[match.index - 1]?.match(/[a-zA-Z]/)) {
119
+ continue;
120
+ }
121
+ ocorrencias.push(warn('React 19 use() hook detected — must be wrapped in <Suspense> boundary', relPath, line, messages.SeverityNiveis.info));
122
+ }
123
+ // Custom hooks naming convention
84
124
  const customHooks = [...src.matchAll(/\buse[A-Z][A-Za-z0-9_]*\s*=\s*\(/g)];
85
125
  customHooks.forEach(m => {
86
126
  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)) {
127
+ if (!ALL_BUILTIN_HOOKS.includes(hookName)) {
88
128
  if (/^use[A-Z]/.test(hookName)) {
89
129
  const line = lineOf(m.index || 0);
90
130
  ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line));
@@ -106,7 +146,7 @@ function parseHooksWithBabel(src, relPath) {
106
146
  if (isJsxLike) {
107
147
  plugins.unshift('jsx');
108
148
  }
109
- const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins });
149
+ const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins: plugins });
110
150
  const out = [];
111
151
  const seen = new Set();
112
152
  const pushOnce = (m) => {
@@ -120,7 +160,12 @@ function parseHooksWithBabel(src, relPath) {
120
160
  const declaredRefs = new Set();
121
161
  const isHookCallee = (callee) => {
122
162
  if (t.isIdentifier(callee)) {
123
- return { name: callee.name };
163
+ const name = callee.name;
164
+ // React 19: use() is a special case (not prefixed with use but still a hook)
165
+ if (name === 'use') {
166
+ return { name };
167
+ }
168
+ return /^use[A-Z0-9_]/.test(name) ? { name } : null;
124
169
  }
125
170
  if (t.isMemberExpression(callee)) {
126
171
  const obj = callee.object;
@@ -135,7 +180,7 @@ function parseHooksWithBabel(src, relPath) {
135
180
  };
136
181
  const isEffectLike = (name) => name === 'useEffect' || name === 'useLayoutEffect' || name === 'useInsertionEffect';
137
182
  const isMemoLike = (name) => name === 'useMemo' || name === 'useCallback';
138
- const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name);
183
+ const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name) || name === 'use';
139
184
  const inConditionalOrLoop = (path) => {
140
185
  return Boolean(path.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression()));
141
186
  };
@@ -207,6 +252,18 @@ function parseHooksWithBabel(src, relPath) {
207
252
  }
208
253
  }
209
254
  }
255
+ // React 19: use() hook should be in Suspense boundary
256
+ if (name === 'use') {
257
+ pushOnce(warn('React 19 use() hook — ensure wrapped in <Suspense> boundary', relPath, locLine));
258
+ }
259
+ // React 19: useOptimistic should have reducer function
260
+ if (name === 'useOptimistic' && path.node.arguments.length < 2) {
261
+ pushOnce(warn('useOptimistic expects initial state and update function (React 19)', relPath, locLine));
262
+ }
263
+ // React 19: useActionState should have form action
264
+ if (name === 'useActionState') {
265
+ pushOnce(warn('useActionState (React 19) — verify form action handler', relPath, locLine));
266
+ }
210
267
  if (isEffectLike(name) && path.node.arguments.length >= 2) {
211
268
  const callback = path.node.arguments[0];
212
269
  const deps = path.node.arguments[1];
@@ -271,7 +328,7 @@ function parseHooksWithBabel(src, relPath) {
271
328
  }
272
329
  }
273
330
  }
274
- if (isAnyHook(name) && !isEffectLike(name) && !isMemoLike(name) && name !== 'useState' && name !== 'useRef' && name !== 'useContext' && name !== 'useReducer' && name !== 'useDebugValue' && name !== 'useId') {
331
+ 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
332
  pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine));
276
333
  }
277
334
  }
@@ -290,7 +347,7 @@ function parseHooksWithBabel(src, relPath) {
290
347
  export const analistaReactHooks = criarAnalista({
291
348
  nome: 'analista-react-hooks',
292
349
  categoria: 'framework',
293
- descricao: 'Heurísticas de Hooks com análise de dependências (sem ESLint).',
350
+ descricao: 'Heurísticas de Hooks (React 18/19) com análise de dependências.',
294
351
  global: false,
295
352
  test: (relPath) => /\.(jsx|tsx|js|ts)$/i.test(relPath),
296
353
  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;
@@ -141,7 +185,7 @@ function parseReactWithBabel(scan, relPath) {
141
185
  const ast = babelParse(scan, {
142
186
  sourceType: 'unambiguous',
143
187
  errorRecovery: true,
144
- plugins
188
+ plugins: plugins
145
189
  });
146
190
  const ocorrencias = [];
147
191
  const seen = new Set();
@@ -19,7 +19,7 @@ export function detectarCorrecoesReact(src, _relPath) {
19
19
  correcoes.push({
20
20
  tipo: 'react-dangerous-html',
21
21
  original: linha.trim(),
22
- sugerido: '// Use: <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />',
22
+ sugerido: '// SEGURANCA: Use DOMPurify.sanitize() antes de dangerouslySetInnerHTML\n// <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />',
23
23
  linha: numeroLinha,
24
24
  confianca: 90
25
25
  });
@@ -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
  }
@@ -66,7 +66,7 @@ function detectWithBabel(src, relPath) {
66
66
  try {
67
67
  const plugins = ['decorators-legacy', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'optionalChaining', 'nullishCoalescingOperator', 'topLevelAwait', 'importAttributes', 'jsx'];
68
68
  plugins.unshift('typescript');
69
- const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins });
69
+ const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins: plugins });
70
70
  const out = [];
71
71
  const seen = new Set();
72
72
  const pushOnce = (o) => {
@@ -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;
@@ -13,8 +13,8 @@ function extrairEntradasDeModulo(mod) {
13
13
  const out = [];
14
14
  const candidates = [];
15
15
  candidates.push(metrica.default);
16
- if (Array.isArray(metrica.analistas)) {
17
- candidates.push(...metrica.analistas);
16
+ if (Array.isArray((metrica).analistas)) {
17
+ candidates.push(...(metrica).analistas);
18
18
  }
19
19
  // Coleta exportações nomeadas que pareçam analista/técnica
20
20
  for (const valor of Object.values(metrica)) {
@@ -32,7 +32,7 @@ function collectBestPractices(src, relPath) {
32
32
  // Excessive function length
33
33
  const funcBlocks = src.matchAll(/(?:^|\n)\s*fn\s+\w+.*?\{/gs);
34
34
  for (const funcBlock of funcBlocks) {
35
- const startIdx = (funcBlock.index ?? 0) + (funcBlock[0].length ?? 0);
35
+ const startIdx = funcBlock.index + funcBlock[0].length;
36
36
  let braceCount = 1;
37
37
  let endIdx = startIdx;
38
38
  for (let i = startIdx; i < src.length && braceCount > 0; i++) {
@@ -47,7 +47,7 @@ function collectBestPractices(src, relPath) {
47
47
  const funcBody = src.slice(startIdx, endIdx);
48
48
  const funcLines = funcBody.split('\n').length;
49
49
  if (funcLines > 80) {
50
- const line = lineOf(funcBlock.index ?? 0);
50
+ const line = lineOf(funcBlock.index);
51
51
  ocorrencias.push(warn(messages.RustMensagens.longFunction, relPath, line));
52
52
  }
53
53
  }
@@ -64,12 +64,51 @@ function collectBestPractices(src, relPath) {
64
64
  }
65
65
  declaredVars.add(varName);
66
66
  }
67
+ // Vec::push in a loop without pre-allocation
68
+ for (const match of scan.matchAll(/for\s+(\w+)\s+in\s+(\w+)\.iter\(\)\s*\{[\s\S]{0,200}results\.push\(/g)) {
69
+ const line = lineOf(match.index);
70
+ ocorrencias.push(warn('Collection built with push() in loop; consider using .map().collect() or Iterator adapters', relPath, line, messages.SeverityNiveis.suggestion));
71
+ }
72
+ // Use of as_ptr_range / as_mut_ptr_range (Rust 1.77+)
73
+ for (const match of scan.matchAll(/\.as_(ptr|mut_ptr)_range\(\)/g)) {
74
+ const line = lineOf(match.index);
75
+ ocorrencias.push(warn(`.${match[1]}_range() used (Rust 1.77+)`, relPath, line, messages.SeverityNiveis.info));
76
+ }
77
+ // Option::unwrap_or_else(|| ...) -> unwrap_or(...) when argument is constant
78
+ for (const match of scan.matchAll(/\.unwrap_or_else\(\s*\|\|\s*(\w+)::(\w+)\s*\)/g)) {
79
+ const line = lineOf(match.index);
80
+ ocorrencias.push(warn(`.unwrap_or_else(|| ...) with constant default; consider .unwrap_or(...) (Rust 1.78+)`, relPath, line, messages.SeverityNiveis.suggestion));
81
+ }
82
+ // use of assert!(expr.is_some()) -> assert!(expr.is_some()) is fine, but check for unwrap after
83
+ for (const match of scan.matchAll(/if\s+(\w+)\.is_some\(\)\s*\{[\s\S]{0,100}(\w+)\.unwrap\(\)/g)) {
84
+ const line = lineOf(match.index);
85
+ ocorrencias.push(warn('Guard with .is_some() followed by .unwrap(); use if let Some(x) = ... instead', relPath, line, messages.SeverityNiveis.suggestion));
86
+ }
87
+ // use of format!("literal {var}") without explicit capture (Rust 1.80+)
88
+ for (const match of scan.matchAll(/format!\(["']([^"']*\{(\w+)\}[^"']*)["'],\s*\2\s*\)/g)) {
89
+ const line = lineOf(match.index);
90
+ const varName = match[2];
91
+ ocorrencias.push(warn(`format!("...{${varName}}...", ${varName}) can be simplified to format!("...{${varName}}...") using implicit capture (Rust 1.80+)`, relPath, line, messages.SeverityNiveis.suggestion));
92
+ }
93
+ // detect assert!(matches!(expr, pattern)) pattern -> suggest assert_matches!
94
+ for (const match of scan.matchAll(/assert!\(matches!\(([^,]+),\s*([^)]+)\)\s*\)/g)) {
95
+ const line = lineOf(match.index);
96
+ ocorrencias.push(warn(`assert!(matches!(${match[1]}, ${match[2]})) — consider assert_matches!(${match[1]}, ${match[2]}) (nightly Rust 1.81+)`, relPath, line, messages.SeverityNiveis.info));
97
+ }
98
+ // detect Vec<_>::new() -> Vec::new() (type inference)
99
+ // detect .into_iter() -> .iter()/iter_mut() when not consuming
100
+ for (const match of scan.matchAll(/\.into_iter\(\)\.(map|filter|for_each)/g)) {
101
+ const line = lineOf(match.index);
102
+ if (!src.slice(0, match.index).includes('.collect')) {
103
+ ocorrencias.push(warn('.into_iter() used but not consuming; consider .iter() for read-only iteration', relPath, line, messages.SeverityNiveis.suggestion));
104
+ }
105
+ }
67
106
  return ocorrencias;
68
107
  }
69
108
  export const analistaRustBoasPraticas = criarAnalista({
70
109
  nome: 'analista-rust-boas-praticas',
71
110
  categoria: 'boas-praticas',
72
- descricao: 'Detecta desvios de boas práticas em código Rust.',
111
+ descricao: 'Detecta desvios de boas práticas Rust (edition 2024, Rust 1.80+).',
73
112
  global: false,
74
113
  test: (relPath) => /\.(rs)$/i.test(relPath),
75
114
  aplicar: (src, relPath) => {
@@ -20,7 +20,7 @@ function collectMemoryIssues(src, relPath) {
20
20
  // Vec without with_capacity
21
21
  for (const match of scan.matchAll(/(\w+)\.push\(/g)) {
22
22
  const line = lineOf(match.index);
23
- const varName = match[1] ?? '';
23
+ const varName = match[1];
24
24
  const declMatch = new RegExp(`let\\s+mut\\s+${varName}\\s*:\\s*Vec|<|=\\s*Vec::new\\(\\)`, 'i');
25
25
  if (declMatch.test(scan.slice(0, match.index))) {
26
26
  ocorrencias.push(warn(messages.RustMensagens.vecWithoutCapacity, relPath, line, messages.SeverityNiveis.suggestion));
@@ -29,7 +29,7 @@ function collectMemoryIssues(src, relPath) {
29
29
  // read_line without trimming
30
30
  for (const match of scan.matchAll(/(\w+)\.read_line\(&mut\s+(\w+)\)/g)) {
31
31
  const line = lineOf(match.index);
32
- const varName = match[2] ?? '';
32
+ const varName = match[2];
33
33
  const snippet = src.slice(match.index, match.index + 200);
34
34
  if (!snippet.includes(`${varName}.trim()`) && !snippet.includes(`${varName}.trim_end()`)) {
35
35
  ocorrencias.push(warn(messages.RustMensagens.noTrimOnInput, relPath, line));