@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
@@ -3,8 +3,28 @@ import { messages } from '../../../core/messages/index.js';
3
3
  import { createLineLookup } from '../../../shared/helpers/index.js';
4
4
  import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
5
5
  const disableEnv = process.env.SUKUNA_DISABLE_PLUGIN_TAILWIND === '1';
6
+ const V3_TO_V4_CLASS_RENAME = {
7
+ 'shadow-sm': 'shadow-xs',
8
+ 'drop-shadow-sm': 'drop-shadow-xs',
9
+ 'blur-sm': 'blur-xs',
10
+ 'backdrop-blur-sm': 'backdrop-blur-xs',
11
+ 'rounded-sm': 'rounded-xs',
12
+ 'outline-none': 'outline-hidden',
13
+ 'flex-shrink': 'shrink',
14
+ 'flex-grow': 'grow',
15
+ 'overflow-ellipsis': 'text-ellipsis',
16
+ 'decoration-slice': 'box-decoration-slice',
17
+ 'decoration-clone': 'box-decoration-clone',
18
+ };
19
+ const V3_TO_V4_SUFFIX_RENAME = {
20
+ 'shadow': 'shadow-sm',
21
+ 'drop-shadow': 'drop-shadow-sm',
22
+ 'blur': 'blur-sm',
23
+ 'backdrop-blur': 'backdrop-blur-sm',
24
+ 'rounded': 'rounded-sm',
25
+ 'ring': 'ring-3',
26
+ };
6
27
  function extractBalancedBraces(src, braceStartIndex, maxLen = 8000) {
7
- // braceStartIndex deve apontar para '{'
8
28
  if (braceStartIndex < 0 || braceStartIndex >= src.length) {
9
29
  return null;
10
30
  }
@@ -26,7 +46,6 @@ function extractBalancedBraces(src, braceStartIndex, maxLen = 8000) {
26
46
  continue;
27
47
  }
28
48
  if (ch === '\\') {
29
- // escape dentro de strings
30
49
  if (inSingle || inDouble || inBacktick) {
31
50
  escaped = true;
32
51
  }
@@ -48,8 +67,6 @@ function extractBalancedBraces(src, braceStartIndex, maxLen = 8000) {
48
67
  continue;
49
68
  }
50
69
  if (inBacktick) {
51
- // Nota: ignoramos completamente conteúdo de template literal
52
- // para não quebrar em '}' de interpolações `${...}`.
53
70
  if (ch === '`') {
54
71
  inBacktick = false;
55
72
  }
@@ -85,27 +102,24 @@ function extractBalancedBraces(src, braceStartIndex, maxLen = 8000) {
85
102
  return null;
86
103
  }
87
104
  function sanitizeTemplateLiteralText(text) {
88
- // Remove interpolações `${...}` para manter apenas a parte estática.
89
105
  return text.replace(/\$\{[\s\S]*?\}/g, ' ');
90
106
  }
91
107
  function normalizeClassText(text) {
92
108
  return (text || '').replace(/\s+/g, ' ').trim();
93
109
  }
94
110
  function isTernaryExpressionText(text) {
95
- // Heurística: se há um '?' e ':' no trecho do atributo, trate como branches exclusivas.
96
- // Isso reduz falsos positivos de conflito entre classes mutuamente exclusivas.
97
111
  return /\?/.test(text) && /:/.test(text);
98
112
  }
99
113
  function extractStringLiterals(text) {
100
114
  const out = [];
101
115
  for (const metrica of text.matchAll(/"([^"\\\n]|\\.)*"/g)) {
102
- out.push((metrica[0] ?? '').slice(1, -1));
116
+ out.push(metrica[0].slice(1, -1));
103
117
  }
104
118
  for (const metrica of text.matchAll(/'([^'\\\n]|\\.)*'/g)) {
105
- out.push((metrica[0] ?? '').slice(1, -1));
119
+ out.push(metrica[0].slice(1, -1));
106
120
  }
107
121
  for (const metrica of text.matchAll(/`([^`]*)`/g)) {
108
- out.push(sanitizeTemplateLiteralText((metrica[1] ?? '')));
122
+ out.push(sanitizeTemplateLiteralText(metrica[1]));
109
123
  }
110
124
  return out;
111
125
  }
@@ -128,30 +142,24 @@ function extractClassBlocks(src) {
128
142
  line
129
143
  });
130
144
  };
131
- // HTML/JSX: class="..." / className="..." (string literal)
132
145
  for (const metrica of src.matchAll(/\bclass(Name)?\s*=\s*(["'])([^"']*)\2/gi)) {
133
- pushBlock(metrica[3] ?? '', lineOf(metrica.index));
146
+ pushBlock(metrica[3], lineOf(metrica.index));
134
147
  }
135
- // JSX: className={"..."} / className={'...'}
136
148
  for (const metrica of src.matchAll(/\bclass(Name)?\s*=\s*\{\s*(["'])([^"']*)\2\s*\}/gi)) {
137
- pushBlock(metrica[3] ?? '', lineOf(metrica.index));
149
+ pushBlock(metrica[3], lineOf(metrica.index));
138
150
  }
139
- // JSX: className={`...${...}...`} (template literal)
140
151
  for (const metrica of src.matchAll(/\bclass(Name)?\s*=\s*\{\s*`([^`]*)`\s*\}/gi)) {
141
- pushBlock(sanitizeTemplateLiteralText(metrica[2] ?? ''), lineOf(metrica.index));
152
+ pushBlock(sanitizeTemplateLiteralText(metrica[2]), lineOf(metrica.index));
142
153
  }
143
- // JS: const x = `...` (casos raros, mas evita perder HTML em strings)
144
154
  for (const metrica of src.matchAll(/\bclass(Name)?\s*=\s*`([^`]*)`/gi)) {
145
- pushBlock(sanitizeTemplateLiteralText(metrica[2] ?? ''), lineOf(metrica.index));
155
+ pushBlock(sanitizeTemplateLiteralText(metrica[2]), lineOf(metrica.index));
146
156
  }
147
- // JSX: className={...} (expressões comuns: ternário, arrays/join, concat, cn(...), etc)
148
- // Mais robusto que regex simples: captura expressão com chaves balanceadas.
149
157
  for (const metrica of src.matchAll(/\bclass(Name)?\s*=\s*\{/gi)) {
150
- const idx = metrica.index ?? -1;
158
+ const idx = metrica.index;
151
159
  if (idx < 0) {
152
160
  continue;
153
161
  }
154
- const braceIdx = idx + (metrica[0].length ?? 0) - 1;
162
+ const braceIdx = idx + metrica[0].length - 1;
155
163
  const expr = extractBalancedBraces(src, braceIdx);
156
164
  if (!expr) {
157
165
  continue;
@@ -167,13 +175,12 @@ function extractClassBlocks(src) {
167
175
  }
168
176
  pushBlock(literals.join(' '), line);
169
177
  }
170
- // Astro: class:list={...}
171
178
  for (const metrica of src.matchAll(/\bclass:list\s*=\s*\{/gi)) {
172
- const idx = metrica.index ?? -1;
179
+ const idx = metrica.index;
173
180
  if (idx < 0) {
174
181
  continue;
175
182
  }
176
- const braceIdx = idx + (metrica[0].length ?? 0) - 1;
183
+ const braceIdx = idx + metrica[0].length - 1;
177
184
  const expr = extractBalancedBraces(src, braceIdx);
178
185
  if (!expr) {
179
186
  continue;
@@ -189,10 +196,8 @@ function extractClassBlocks(src) {
189
196
  }
190
197
  pushBlock(literals.join(' '), line);
191
198
  }
192
- // JSX/TS: helpers comuns (clsx/cn/twMerge/classNames/classnames)
193
- // Heurística simples (sem parser): extrai literais de string dentro da chamada.
194
199
  for (const metrica of src.matchAll(/\b(?:clsx|cn|twMerge|classNames|classnames)\s*\(([^)]*)\)/g)) {
195
- const args = metrica[1] ?? '';
200
+ const args = metrica[1];
196
201
  const literals = extractStringLiterals(args).map(string => string.trim()).filter(Boolean);
197
202
  if (!literals.length) {
198
203
  continue;
@@ -206,7 +211,7 @@ function hasTailwindTokens(src) {
206
211
  if (!blocks.length) {
207
212
  return false;
208
213
  }
209
- const tokenRe = /\b(?:bg-|text-|m[trblxy]?-|p[trblxy]?-|flex|grid|gap-|space-|rounded|shadow|justify-|items-|w-|h-|min-w-|max-w-|min-h-|max-h-|inset-|top-|left-|right-|bottom-|z-|overflow-|cursor-|select-|border|ring|opacity-|font-|leading-|tracking-|transition|duration-|ease-|animate-|underline|no-underline|sr-only|not-sr-only|container)\S*/i;
214
+ const tokenRe = /\b(?:bg-|text-|m[trblxy]?-|p[trblxy]?-|flex|grid|gap-|space-|rounded|shadow|justify-|items-|w-|h-|min-w-|max-w-|min-h-|max-h-|inset-|top-|left-|right-|bottom-|z-|overflow-|cursor-|select-|border|ring|opacity-|font-|leading-|tracking-|transition|duration-|ease-|animate-|underline|no-underline|sr-only|not-sr-only|container|field-sizing-|text-shadow-|inset-shadow-|zoom-|perspective-|outline-|drop-shadow-|backdrop-blur-)\S*/i;
210
215
  return blocks.some(base => tokenRe.test(base.text));
211
216
  }
212
217
  function extractClasses(src) {
@@ -234,14 +239,10 @@ function splitVariants(token) {
234
239
  };
235
240
  }
236
241
  function propertyKey(token) {
237
- // Respeita variantes (sm:, md:, dark:, hover:, etc.): conflitos só contam
238
- // dentro do mesmo "contexto" de variantes.
239
242
  const { variants, base } = splitVariants(token);
240
- // ícones/fontawesome ou tokens livres não são tratados como conflito
241
243
  if (/^(fa-|icon-)/.test(base)) {
242
244
  return null;
243
245
  }
244
- // padding/margin: não trate px + py como conflito (são eixos diferentes)
245
246
  const padMatch = /^(p[trblxy]?)(?:-|\[)/.exec(base);
246
247
  if (padMatch) {
247
248
  return `${variants}|padding:${padMatch[1]}`;
@@ -257,35 +258,27 @@ function propertyKey(token) {
257
258
  if (/^space-[xy]/.test(base)) {
258
259
  return `${variants}|space`;
259
260
  }
260
- // display
261
261
  if (/^(block|inline-block|inline|flex|grid|hidden)$/.test(base)) {
262
262
  return `${variants}|display`;
263
263
  }
264
- // flex direction
265
264
  if (/^flex-(row|col)(-reverse)?$/.test(base)) {
266
265
  return `${variants}|flex-direction`;
267
266
  }
268
- // align/justify
269
267
  if (/^justify-/.test(base)) {
270
268
  return `${variants}|justify`;
271
269
  }
272
270
  if (/^items-/.test(base)) {
273
271
  return `${variants}|items`;
274
272
  }
275
- // border radius
276
273
  if (/^rounded(?:-|\b|\[)/.test(base)) {
277
274
  return `${variants}|rounded`;
278
275
  }
279
- // shadow: distinguir tamanho vs cor para permitir combinações legítimas
280
276
  if (/^shadow(?:-|\b|\[)/.test(base)) {
281
- // shadow, shadow-sm, shadow-md, shadow-lg, shadow-xl, shadow-2xl, shadow-inner, shadow-none
282
277
  if (/^shadow(?:-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/.test(base)) {
283
278
  return `${variants}|shadow-size`;
284
279
  }
285
- // shadow-[custom] ou shadow-color-shade (ex: shadow-black/20, shadow-gray-500)
286
280
  return `${variants}|shadow-color`;
287
281
  }
288
- // background + text (separa tamanho vs cor para reduzir ruído)
289
282
  if (/^bg-(?!opacity-)/.test(base)) {
290
283
  return `${variants}|bg-color`;
291
284
  }
@@ -319,8 +312,23 @@ function propertyKey(token) {
319
312
  }, {
320
313
  re: /^(top|left|right|bottom)(?:-|\[)/,
321
314
  key: 'position'
315
+ }, {
316
+ re: /^field-sizing(?:-|\[)/,
317
+ key: 'field-sizing'
318
+ }, {
319
+ re: /^text-shadow(?:-|\[)/,
320
+ key: 'text-shadow'
321
+ }, {
322
+ re: /^inset-shadow(?:-|\[)/,
323
+ key: 'inset-shadow'
324
+ }, {
325
+ re: /^zoom(?:-|\[)/,
326
+ key: 'zoom'
327
+ }, {
328
+ re: /^perspective(?:-|\[)/,
329
+ key: 'perspective'
322
330
  }];
323
- const found = groups.find(grupo => grupo.re.test(base));
331
+ const found = groups?.find(grupo => grupo.re.test(base));
324
332
  return found ? `${variants}|${found.key}` : null;
325
333
  }
326
334
  function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
@@ -335,8 +343,6 @@ function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
335
343
  }
336
344
  function collectTailwindIssues(src, relPath) {
337
345
  const ocorrencias = [];
338
- // Importante: conflitos devem ser detectados por bloco de classes
339
- // (cada atributo class/className) e não no arquivo inteiro.
340
346
  const blocks = extractClassBlocks(src);
341
347
  for (const block of blocks) {
342
348
  const tokens = (block.text || '').split(/\s+/).filter(Boolean);
@@ -346,28 +352,38 @@ function collectTailwindIssues(src, relPath) {
346
352
  const line = block.line;
347
353
  const seen = {};
348
354
  for (const token of tokens) {
355
+ const v4Rename = V3_TO_V4_CLASS_RENAME[token];
356
+ if (v4Rename) {
357
+ ocorrencias.push(warn(`Classe obsoleta do v3: "${token}" → use "${v4Rename}"`, relPath, line));
358
+ continue;
359
+ }
360
+ const baseClass = token.split(':').pop() ?? token;
361
+ const v4Suffix = V3_TO_V4_SUFFIX_RENAME[baseClass];
362
+ if (v4Suffix && baseClass === token) {
363
+ ocorrencias.push(warn(`Classe renomeada no v4: "${token}" → use "${v4Suffix}"`, relPath, line));
364
+ continue;
365
+ }
366
+ if (token.startsWith('!')) {
367
+ ocorrencias.push(warn(`!important como prefixo (v3) em "${token}" → mova ! para o final (ex: "${token.slice(1)}!")`, relPath, line));
368
+ }
349
369
  const key = propertyKey(token);
350
370
  if (!key) {
351
371
  continue;
352
372
  }
353
- if (!seen[key]) {
373
+ if (!(key in seen)) {
354
374
  seen[key] = [];
355
375
  }
356
376
  seen[key].push(token);
357
377
  }
358
378
  for (const [key, list] of Object.entries(seen)) {
359
379
  const uniqTokens = [...new Set(list)];
360
- // Detect duplicated exact tokens within same block (redundant classes)
361
380
  const repeats = list.filter((textoTitulo, indice) => list.indexOf(textoTitulo) !== indice);
362
381
  for (const raiz of [...new Set(repeats)]) {
363
382
  ocorrencias.push(warn(messages.TailwindMensagens.repeatedClass(raiz), relPath, line));
364
383
  }
365
384
  if (uniqTokens.length > 1) {
366
- // Mensagem: mostra a chave sem o prefixo de variantes para não poluir,
367
- // mas mantém as classes originais nos exemplos.
368
385
  const normalizedChave = key.includes('|') ? key.split('|').slice(1).join('|') : key;
369
386
  ocorrencias.push(warn(messages.TailwindMensagens.conflictingClasses(normalizedChave, uniqTokens.slice(0, 4)), relPath, line));
370
- // Detect variant conflicts for same property across multiple variants
371
387
  const propSuffix = normalizedChave;
372
388
  const variantsSet = new Set();
373
389
  for (const chave of Object.keys(seen)) {
@@ -383,13 +399,12 @@ function collectTailwindIssues(src, relPath) {
383
399
  }
384
400
  }
385
401
  }
386
- // Detect variant conflicts across properties even when each variant has unique token
387
402
  const propVariants = {};
388
403
  for (const chave of Object.keys(seen)) {
389
404
  const parts = chave.split('|');
390
405
  const varPart = parts.length > 1 ? parts[0] : '';
391
406
  const propPart = parts.length > 1 ? parts.slice(1).join('|') : chave;
392
- if (!propVariants[propPart]) {
407
+ if (!(propPart in propVariants)) {
393
408
  propVariants[propPart] = new Set();
394
409
  }
395
410
  propVariants[propPart].add(varPart || 'base');
@@ -400,7 +415,6 @@ function collectTailwindIssues(src, relPath) {
400
415
  }
401
416
  }
402
417
  }
403
- // Arbitrary values: sinalizar para revisão
404
418
  const safeArbitrary = /(var\()|(rgb\()|(hsl\()|(calc\()|url\(|linear-gradient|conic-gradient|radial-gradient|\d+(px|rem|em|%|vh|vw|ms|s|deg|fr)?\]|\d+\/\d+|\[--|^\d+\/\d+|\d+px|\d+rem|\d+em|\d+%|\d+deg|\d+fr/i;
405
419
  const allTokens = extractClasses(src);
406
420
  allTokens.filter(({ token }) => /\[.*\]/.test(token)).forEach(({ token, line }) => {
@@ -409,22 +423,32 @@ function collectTailwindIssues(src, relPath) {
409
423
  ocorrencias.push(warn(messages.TailwindMensagens.dangerousArbitraryValue(token), relPath, line));
410
424
  return;
411
425
  }
412
- // Evita ruído: ignore vars, cores, gradientes, calc, unidades comuns, etc.
413
426
  if (safeArbitrary.test(token)) {
414
427
  return;
415
428
  }
416
429
  ocorrencias.push(warn(messages.TailwindMensagens.arbitraryValue(token), relPath, line));
417
430
  });
418
- // Important usage detection (e.g., 'text-red-500!')
419
431
  allTokens.filter(({ token }) => /!$/.test(token) || /!\]/.test(token)).forEach(({ token, line }) => {
420
432
  ocorrencias.push(warn(messages.TailwindMensagens.importantUsage(token), relPath, line, messages.SeverityNiveis.suggestion));
421
433
  });
434
+ if (/@tailwind\s+(base|components|utilities)/.test(src)) {
435
+ ocorrencias.push(warn('Diretiva @tailwind detectada (v3) - use @import "tailwindcss" (v4)', relPath, 1, messages.SeverityNiveis.warning));
436
+ }
437
+ if (/@layer\s+utilities/.test(src)) {
438
+ ocorrencias.push(warn('@layer utilities detectado (v3) - use @utility (v4)', relPath, 1, messages.SeverityNiveis.suggestion));
439
+ }
440
+ if (/\btheme\(/.test(src)) {
441
+ ocorrencias.push(warn('Função theme() detectada (deprecated v3) - prefira CSS variables (var(--color-*))', relPath, 1, messages.SeverityNiveis.suggestion));
442
+ }
443
+ if (/\[--[^\]]+\]/.test(src)) {
444
+ ocorrencias.push(warn('Variável em colchetes (v3) - use parênteses (ex: bg-(--brand-color) ao invés de bg-[--brand-color])', relPath, undefined, messages.SeverityNiveis.suggestion));
445
+ }
422
446
  return ocorrencias;
423
447
  }
424
448
  export const analistaTailwind = criarAnalista({
425
449
  nome: 'analista-tailwind',
426
450
  categoria: 'estilo',
427
- descricao: 'Heurísticas leves de Tailwind.',
451
+ descricao: 'Heurísticas de Tailwind CSS v4.',
428
452
  global: false,
429
453
  test: (relPath) => /\.(jsx|tsx|js|ts|html|astro)$/i.test(relPath),
430
454
  aplicar: (src, relPath) => {
@@ -1,15 +1,37 @@
1
1
  // SPDX-License-Identifier: MIT
2
- /**
3
- * [CHAVE-INGLESA] Correções automáticas para Tailwind CSS
4
- *
5
- * Fornece correções sugeridas para problemas comuns em Tailwind:
6
- * - Remover classes duplicadas
7
- * - Consolidar classes conflitantes
8
- * - Sugerir config para arbitrary values
9
- */
10
2
  import { messages } from '../../../core/messages/index.js';
11
3
  import { splitLines } from '../../../shared/helpers/index.js';
12
4
  import { criarOcorrencia } from '../../../types/index.js';
5
+ const V3_TO_V4_CLASSES = {
6
+ 'shadow-sm': 'shadow-xs',
7
+ 'drop-shadow-sm': 'drop-shadow-xs',
8
+ 'blur-sm': 'blur-xs',
9
+ 'backdrop-blur-sm': 'backdrop-blur-xs',
10
+ 'rounded-sm': 'rounded-xs',
11
+ 'outline-none': 'outline-hidden',
12
+ 'flex-shrink': 'shrink',
13
+ 'flex-grow': 'grow',
14
+ 'overflow-ellipsis': 'text-ellipsis',
15
+ 'decoration-slice': 'box-decoration-slice',
16
+ 'decoration-clone': 'box-decoration-clone',
17
+ };
18
+ const V3_TO_V4_SUFFIX_CLASSES = {
19
+ 'shadow': 'shadow-sm',
20
+ 'drop-shadow': 'drop-shadow-sm',
21
+ 'blur': 'blur-sm',
22
+ 'backdrop-blur': 'backdrop-blur-sm',
23
+ 'rounded': 'rounded-sm',
24
+ 'ring': 'ring-3',
25
+ };
26
+ function detectarClasseV3Deprecated(classe) {
27
+ return V3_TO_V4_CLASSES[classe] ?? null;
28
+ }
29
+ function detectarClasseSufixoV3(classe) {
30
+ const base = classe.split(':').pop() ?? classe;
31
+ const variante = classe.endsWith(base) ? '' : classe.slice(0, -base.length);
32
+ const sub = V3_TO_V4_SUFFIX_CLASSES[base];
33
+ return sub ? `${variante}${sub}` : null;
34
+ }
13
35
  export function detectarCorrecoesTailwind(src, _relPath) {
14
36
  const correcoes = [];
15
37
  const linhas = splitLines(src);
@@ -28,6 +50,83 @@ export function detectarCorrecoesTailwind(src, _relPath) {
28
50
  confianca: 95
29
51
  });
30
52
  }
53
+ for (const cls of unique) {
54
+ const v4Class = detectarClasseV3Deprecated(cls);
55
+ if (v4Class) {
56
+ correcoes.push({
57
+ tipo: 'tailwind-v3-to-v4-rename',
58
+ original: cls,
59
+ sugerido: v4Class,
60
+ linha: numeroLinha,
61
+ confianca: 98
62
+ });
63
+ }
64
+ const v4Suffix = detectarClasseSufixoV3(cls);
65
+ if (v4Suffix) {
66
+ correcoes.push({
67
+ tipo: 'tailwind-v3-to-v4-rename',
68
+ original: cls,
69
+ sugerido: v4Suffix,
70
+ linha: numeroLinha,
71
+ confianca: 95
72
+ });
73
+ }
74
+ if (cls.startsWith('!')) {
75
+ const cleanCls = cls.replace(/^!/, '');
76
+ if (cleanCls) {
77
+ correcoes.push({
78
+ tipo: 'tailwind-important-prefix-to-suffix',
79
+ original: cls,
80
+ sugerido: `${cleanCls}!`,
81
+ linha: numeroLinha,
82
+ confianca: 97
83
+ });
84
+ }
85
+ }
86
+ }
87
+ }
88
+ const directClassMatch = linha.match(/\bclass(Name)?\s*=\s*\{/);
89
+ if (directClassMatch) {
90
+ const importantPrefixMatch = linha.match(/["'`]\s*!([\w-]+)/);
91
+ if (importantPrefixMatch) {
92
+ correcoes.push({
93
+ tipo: 'tailwind-important-prefix-to-suffix',
94
+ original: `"!${importantPrefixMatch[1]}"`,
95
+ sugerido: `"${importantPrefixMatch[1]}!"`,
96
+ linha: numeroLinha,
97
+ confianca: 90
98
+ });
99
+ }
100
+ }
101
+ const v3VariableArbitrary = linha.match(/\[--([^\]]+)\]/);
102
+ if (v3VariableArbitrary) {
103
+ correcoes.push({
104
+ tipo: 'tailwind-arbitrary-var-syntax',
105
+ original: `[--${v3VariableArbitrary[1]}]`,
106
+ sugerido: `(--${v3VariableArbitrary[1]})`,
107
+ linha: numeroLinha,
108
+ confianca: 98
109
+ });
110
+ }
111
+ const tailwindDirective = linha.match(/@tailwind\s+(base|components|utilities)/);
112
+ if (tailwindDirective) {
113
+ correcoes.push({
114
+ tipo: 'tailwind-v3-to-v4-import',
115
+ original: linha.trim(),
116
+ sugerido: `@import "tailwindcss"`,
117
+ linha: numeroLinha,
118
+ confianca: 98
119
+ });
120
+ }
121
+ const layerUtilities = linha.match(/@layer\s+utilities\s*\{/);
122
+ if (layerUtilities) {
123
+ correcoes.push({
124
+ tipo: 'tailwind-layer-to-utility',
125
+ original: linha.trim(),
126
+ sugerido: `Use @utility nome-da-utilidade { ... } ao invés de @layer utilities`,
127
+ linha: numeroLinha,
128
+ confianca: 85
129
+ });
31
130
  }
32
131
  });
33
132
  return correcoes;
@@ -35,7 +134,7 @@ export function detectarCorrecoesTailwind(src, _relPath) {
35
134
  export const correctionTailwind = {
36
135
  nome: 'correction-tailwind',
37
136
  categoria: 'estilo',
38
- descricao: 'Correções automáticas para problemas Tailwind',
137
+ descricao: 'Correções automáticas para Tailwind CSS v4',
39
138
  test: (relPath) => /\.(jsx|tsx|js|ts|html|astro)$/i.test(relPath),
40
139
  aplicar: (src, relPath) => {
41
140
  if (!src) {
@@ -1,6 +1,27 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { splitLines } from '../../../shared/helpers/index.js';
3
3
  import { criarOcorrencia } from '../../../types/index.js';
4
+ const V3_DEPRECATED_UTILITIES_MAP = {
5
+ 'shadow-sm': 'shadow-xs',
6
+ 'drop-shadow-sm': 'drop-shadow-xs',
7
+ 'blur-sm': 'blur-xs',
8
+ 'backdrop-blur-sm': 'backdrop-blur-xs',
9
+ 'rounded-sm': 'rounded-xs',
10
+ 'outline-none': 'outline-hidden',
11
+ 'flex-shrink': 'shrink',
12
+ 'flex-grow': 'grow',
13
+ 'overflow-ellipsis': 'text-ellipsis',
14
+ 'decoration-slice': 'box-decoration-slice',
15
+ 'decoration-clone': 'box-decoration-clone',
16
+ };
17
+ const V3_SUFFIX_UTILITIES_MAP = {
18
+ 'shadow': 'shadow-sm',
19
+ 'drop-shadow': 'drop-shadow-sm',
20
+ 'blur': 'blur-sm',
21
+ 'backdrop-blur': 'backdrop-blur-sm',
22
+ 'rounded': 'rounded-sm',
23
+ 'ring': 'ring-3',
24
+ };
4
25
  const TAILWIND_UTILITIES = new Set([
5
26
  'bg-', 'text-', 'm-', 'mt-', 'mr-', 'mb-', 'ml-', 'mx-', 'my-',
6
27
  'p-', 'pt-', 'pr-', 'pb-', 'pl-', 'px-', 'py-',
@@ -11,13 +32,16 @@ const TAILWIND_UTILITIES = new Set([
11
32
  'transition', 'duration-', 'ease-', 'animate-',
12
33
  'z-', 'overflow-', 'cursor-', 'select-', 'blur-',
13
34
  'top-', 'left-', 'right-', 'bottom-', 'inset-',
35
+ 'field-sizing-', 'text-shadow-', 'inset-shadow-', 'zoom-', 'perspective-',
36
+ 'outline-', 'drop-shadow-', 'backdrop-blur-',
14
37
  ]);
15
38
  const TAILWIND_COLORS = [
16
39
  'slate', 'gray', 'zinc', 'neutral', 'stone', 'red', 'orange', 'amber',
17
40
  'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue',
18
41
  'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose',
42
+ 'mauve', 'olive', 'mist', 'taupe',
19
43
  ];
20
- function _isTailwindColor(value) {
44
+ function isTailwindColor(value) {
21
45
  return TAILWIND_COLORS.some(c => value.includes(c));
22
46
  }
23
47
  function getPropertyKey(token) {
@@ -45,12 +69,27 @@ function getPropertyKey(token) {
45
69
  if (/^opacity/.test(token)) {
46
70
  return 'opacity';
47
71
  }
72
+ if (/^field-sizing/.test(token)) {
73
+ return 'field-sizing';
74
+ }
75
+ if (/^text-shadow/.test(token)) {
76
+ return 'text-shadow';
77
+ }
78
+ if (/^inset-shadow/.test(token)) {
79
+ return 'inset-shadow';
80
+ }
81
+ if (/^zoom/.test(token)) {
82
+ return 'zoom';
83
+ }
84
+ if (/^perspective/.test(token)) {
85
+ return 'perspective';
86
+ }
48
87
  return null;
49
88
  }
50
89
  export const detectorTailwindBestPractices = {
51
90
  nome: 'detector-tailwind-best-practices',
52
91
  categoria: 'estilo',
53
- descricao: 'Detecta más práticas em Tailwind CSS',
92
+ descricao: 'Detecta más práticas em Tailwind CSS v4',
54
93
  test: (relPath) => /\.(jsx|tsx|js|ts|html|astro)$/i.test(relPath),
55
94
  aplicar: (src, relPath) => {
56
95
  if (!src) {
@@ -62,10 +101,86 @@ export const detectorTailwindBestPractices = {
62
101
  let inlineStyleCount = 0;
63
102
  linhas.forEach((linha, index) => {
64
103
  const numeroLinha = index + 1;
65
- if (/\bclass(Name)?\s*=\s*["'][^"']*["']/.test(linha)) {
104
+ if (/@tailwind\s+(base|components|utilities)/.test(linha)) {
105
+ ocorrencias.push(criarOcorrencia({
106
+ tipo: 'tailwind-v3-directive',
107
+ nivel: 'aviso',
108
+ mensagem: 'Diretiva @tailwind detectada (v3) - use @import "tailwindcss"',
109
+ relPath,
110
+ linha: numeroLinha,
111
+ sugestao: 'Substitua @tailwind base/components/utilities por @import "tailwindcss"'
112
+ }));
113
+ }
114
+ if (/@layer\s+utilities\s*\{/.test(linha)) {
115
+ ocorrencias.push(criarOcorrencia({
116
+ tipo: 'tailwind-v3-layer-utilities',
117
+ nivel: 'info',
118
+ mensagem: '@layer utilities detectado (v3) - use @utility (v4)',
119
+ relPath,
120
+ linha: numeroLinha,
121
+ sugestao: 'Use @utility nome-da-utilidade { ... } ao invés de @layer utilities { ... }'
122
+ }));
123
+ }
124
+ if (/\btheme\(/.test(linha)) {
125
+ ocorrencias.push(criarOcorrencia({
126
+ tipo: 'tailwind-theme-function',
127
+ nivel: 'info',
128
+ mensagem: 'theme() detectado (deprecated v3) - use CSS variables',
129
+ relPath,
130
+ linha: numeroLinha,
131
+ sugestao: 'Substitua theme(colors.red.500) por var(--color-red-500)'
132
+ }));
133
+ }
134
+ if (/\bclass(Name)?\s*=\s*["'][^"']*![\w-]/.test(linha)) {
135
+ ocorrencias.push(criarOcorrencia({
136
+ tipo: 'tailwind-important-prefix',
137
+ nivel: 'info',
138
+ mensagem: '!important como prefixo (v3) - mova ! para o final da classe',
139
+ relPath,
140
+ linha: numeroLinha,
141
+ sugestao: 'Use "flex!" ao invés de "!flex"'
142
+ }));
143
+ }
144
+ if (/\bclass(Name)?\s*=\s*["'][^"']*\[--[^\]]+\]/.test(linha)) {
145
+ ocorrencias.push(criarOcorrencia({
146
+ tipo: 'tailwind-arbitrary-var-syntax',
147
+ nivel: 'info',
148
+ mensagem: 'Variável em colchetes (v3) - use parênteses (v4)',
149
+ relPath,
150
+ linha: numeroLinha,
151
+ sugestao: 'Use bg-(--brand-color) ao invés de bg-[--brand-color]'
152
+ }));
153
+ }
154
+ if (/\bclass(Name)?\s*=\s*["']([^"']*)["']/.test(linha)) {
66
155
  const classMatch = linha.match(/\bclass(Name)?\s*=\s*["']([^"']*)["']/);
67
156
  if (classMatch) {
68
157
  const classes = classMatch[2].split(/\s+/).filter(Boolean);
158
+ for (const cls of classes) {
159
+ const v4Equivalent = V3_DEPRECATED_UTILITIES_MAP[cls];
160
+ if (v4Equivalent) {
161
+ ocorrencias.push(criarOcorrencia({
162
+ tipo: 'tailwind-v3-deprecated-utility',
163
+ nivel: 'aviso',
164
+ mensagem: `Classe obsoleta do v3: "${cls}" → use "${v4Equivalent}"`,
165
+ relPath,
166
+ linha: numeroLinha,
167
+ sugestao: `Substitua ${cls} por ${v4Equivalent}`
168
+ }));
169
+ }
170
+ const baseClass = cls.split(':').pop() ?? cls;
171
+ if (baseClass && V3_SUFFIX_UTILITIES_MAP[baseClass]) {
172
+ const variante = cls.endsWith(baseClass) ? '' : cls.slice(0, -baseClass.length);
173
+ const v4Suffix = V3_SUFFIX_UTILITIES_MAP[baseClass];
174
+ ocorrencias.push(criarOcorrencia({
175
+ tipo: 'tailwind-v3-deprecated-utility',
176
+ nivel: 'aviso',
177
+ mensagem: `Classe renomeada no v4: "${cls}" → use "${variante}${v4Suffix}"`,
178
+ relPath,
179
+ linha: numeroLinha,
180
+ sugestao: `Substitua ${cls} por ${variante}${v4Suffix} (ex: shadow → shadow-sm)`
181
+ }));
182
+ }
183
+ }
69
184
  const seen = new Map();
70
185
  classes.forEach(token => {
71
186
  const base = token.split(':').pop() ?? token;
@@ -139,7 +254,7 @@ export const detectorTailwindBestPractices = {
139
254
  }
140
255
  const hardcodedValues = classes.filter(c => {
141
256
  const val = c.split('-').pop() ?? '';
142
- return /^\d+$/.test(val) && !TAILWIND_COLORS.some(col => c.includes(col)) && val.length > 2;
257
+ return /^\d+$/.test(val) && !isTailwindColor(c) && val.length > 2;
143
258
  });
144
259
  if (hardcodedValues.length > 0 && !hardcodedValues.some(v => /^[0-9]+\/[0-9]+$/.test(v))) {
145
260
  ocorrencias.push(criarOcorrencia({
@@ -148,7 +263,7 @@ export const detectorTailwindBestPractices = {
148
263
  mensagem: 'Valor hardcoded detectado - use tokens de design do Tailwind',
149
264
  relPath,
150
265
  linha: numeroLinha,
151
- sugestao: 'Extraia cores, espaçamentos e tamanhos para o tailwind.config.js'
266
+ sugestao: 'Extraia cores, espaçamentos e tamanhos para @theme no CSS (v4)'
152
267
  }));
153
268
  }
154
269
  }
@@ -166,7 +281,7 @@ export const detectorTailwindBestPractices = {
166
281
  mensagem: `Valor arbitrário detectado: ${val}`,
167
282
  relPath,
168
283
  linha: numeroLinha,
169
- sugestao: 'Considere adicionar ao tailwind.config.js'
284
+ sugestao: 'Considere adicionar ao @theme no CSS (v4)'
170
285
  }));
171
286
  }
172
287
  }
@@ -204,7 +319,7 @@ export const detectorTailwindBestPractices = {
204
319
  mensagem: 'Uso excessivo de valores arbitrários - pode indicar falta de tokens de design',
205
320
  relPath,
206
321
  linha: 1,
207
- sugestao: 'Defina tokens personalizados em tailwind.config.js ao invés de valores arbitrários'
322
+ sugestao: 'Defina tokens personalizados via @theme no CSS ao invés de valores arbitrários'
208
323
  }));
209
324
  }
210
325
  return ocorrencias;