@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.
- package/README.md +0 -34
- package/dist/analysts/architects/analyst-structure.js +20 -20
- package/dist/analysts/architects/archetypes-custom.js +16 -19
- package/dist/analysts/architects/detector-archetypes.js +43 -36
- package/dist/analysts/architects/orchestrator-archetypes.js +3 -3
- package/dist/analysts/architects/signals-project-advanced.js +26 -28
- package/dist/analysts/architects/signals-project.js +2 -2
- package/dist/analysts/architects/strategist-structure.js +47 -66
- package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +41 -35
- package/dist/analysts/azure-pipelines/corrections/correction-azure.js +22 -19
- package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +21 -18
- package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +25 -20
- package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +39 -40
- package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
- package/dist/analysts/circleci/analysts/analyst-circleci.js +33 -27
- package/dist/analysts/circleci/corrections/correction-circleci.js +22 -20
- package/dist/analysts/circleci/detectors/detector-circleci-performance.js +23 -19
- package/dist/analysts/circleci/detectors/detector-circleci-security.js +78 -82
- package/dist/analysts/circleci/detectors/detector-circleci-structure.js +22 -22
- package/dist/analysts/converters/convert-registry.js +1 -1
- package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
- package/dist/analysts/converters/index.js +1 -1
- package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
- package/dist/analysts/corrections/analyst-scoring.js +24 -21
- package/dist/analysts/corrections/auto-fix-engine.js +1 -1
- package/dist/analysts/corrections/corrector-structure.js +43 -42
- package/dist/analysts/corrections/fix-alias-imports.js +10 -6
- package/dist/analysts/corrections/fix-md-fences.js +23 -23
- package/dist/analysts/corrections/map-reversion.js +56 -53
- package/dist/analysts/corrections/pruning.js +19 -16
- package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +57 -61
- package/dist/analysts/corrections/quick-fixes/fix-unknown-to-specific-type.js +30 -30
- package/dist/analysts/corrections/reorganize-barrels.js +13 -13
- package/dist/analysts/corrections/rewrite-testes-aliases.js +23 -22
- package/dist/analysts/corrections/scoring-shared.js +19 -18
- package/dist/analysts/corrections/type-safety/context-analyzer.js +15 -10
- package/dist/analysts/corrections/type-safety/type-analyzer.js +3 -3
- package/dist/analysts/corrections/type-safety/type-validator.js +4 -3
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +39 -42
- package/dist/analysts/css/analysts/analyst-css.js +42 -42
- package/dist/analysts/css/corrections/correction-css.js +19 -11
- package/dist/analysts/css/detectors/detector-css-best-practices.js +129 -118
- package/dist/analysts/css/scorers/scorer-css.js +23 -18
- package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +24 -17
- package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +31 -16
- package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +28 -30
- package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +79 -54
- package/dist/analysts/css-in-js/shared.js +7 -0
- package/dist/analysts/detectors/detector-architecture.js +91 -85
- package/dist/analysts/detectors/detector-bugs-ml.js +29 -27
- package/dist/analysts/detectors/detector-code-fragile.js +41 -36
- package/dist/analysts/detectors/detector-dependencies.js +5 -5
- package/dist/analysts/detectors/detector-duplications.js +26 -26
- package/dist/analysts/detectors/detector-monorepo.js +51 -45
- package/dist/analysts/detectors/detector-performance.js +24 -17
- package/dist/analysts/detectors/detector-recommendation-architecture.js +40 -36
- package/dist/analysts/detectors/detector-security.js +43 -42
- package/dist/analysts/detectors/detector-structure.js +31 -28
- package/dist/analysts/github-actions/analysts/analyst-github-actions.js +5 -8
- package/dist/analysts/github-actions/detectors/detector-workflow-accessibility.js +1 -9
- package/dist/analysts/github-actions/detectors/detector-workflow-structure.js +1 -4
- package/dist/analysts/github-actions/detectors/detector-workflow-trigger-unsafe.js +6 -5
- package/dist/analysts/gitlab-ci/analysts/analyst-gitlab-ci.js +2 -2
- package/dist/analysts/gitlab-ci/detectors/detector-gitlab-ci-structure.js +0 -3
- package/dist/analysts/go/analysts/analyst-best-practices.js +2 -2
- package/dist/analysts/go/analysts/analyst-concurrency.js +1 -1
- package/dist/analysts/go/analysts/analyst-security.js +1 -1
- package/dist/analysts/go/detectors/detector-conventions.js +1 -1
- package/dist/analysts/html/analysts/analyst-html.js +10 -12
- package/dist/analysts/html/detectors/detector-html.js +3 -3
- package/dist/analysts/js-ts/analysts/analyst-commands-cli.js +22 -22
- package/dist/analysts/js-ts/analysts/analyst-functions-long.js +5 -5
- package/dist/analysts/js-ts/analysts/analyst-patterns-usage.js +6 -2
- package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +84 -9
- package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +33 -10
- package/dist/analysts/js-ts/detectors/detector-context-smart.js +6 -6
- package/dist/analysts/js-ts/detectors/detector-leaks-memory.js +1 -1
- package/dist/analysts/js-ts/detectors/detector-types-unsafe.js +33 -2
- package/dist/analysts/php/analysts/analyst-php.js +3 -3
- package/dist/analysts/php/detectors/detector-php-best-practices.js +5 -5
- package/dist/analysts/php/detectors/detector-php-security.js +5 -5
- package/dist/analysts/php/scorers/scorer-php.js +2 -2
- package/dist/analysts/plugins/detector-documentation.js +6 -6
- package/dist/analysts/plugins/detector-markdown.js +7 -7
- package/dist/analysts/python/analysts/analyst-python.js +2 -5
- package/dist/analysts/react/analysts/analyst-react-hooks.js +66 -9
- package/dist/analysts/react/analysts/analyst-react.js +46 -2
- package/dist/analysts/react/corrections/correction-react.js +41 -1
- package/dist/analysts/react/detectors/detector-react-best-practices.js +5 -5
- package/dist/analysts/react/scorers/scorer-react.js +36 -1
- package/dist/analysts/registry/autodiscovery.js +2 -2
- package/dist/analysts/rust/analysts/analyst-best-practices.js +42 -3
- package/dist/analysts/rust/analysts/analyst-memory.js +2 -2
- package/dist/analysts/rust/analysts/analyst-rust.js +52 -5
- package/dist/analysts/rust/analysts/analyst-security.js +29 -1
- package/dist/analysts/scorers/scorer.js +5 -5
- package/dist/analysts/shell/analysts/analyst-shell.js +81 -5
- package/dist/analysts/shell/corrections/correction-shell.js +34 -1
- package/dist/analysts/shell/detectors/detector-shell-security.js +88 -0
- package/dist/analysts/shell/scorers/scorer-shell.js +58 -1
- package/dist/analysts/strategists/operator-structure.js +5 -7
- package/dist/analysts/strategists/suggestions-contextuais.js +5 -2
- package/dist/analysts/svg/analysts/analyst-svg.js +1 -0
- package/dist/analysts/tailwind/analysts/analyst-tailwind.js +79 -55
- package/dist/analysts/tailwind/corrections/correction-tailwind.js +108 -9
- package/dist/analysts/tailwind/detectors/detector-tailwind-best-practices.js +122 -7
- package/dist/analysts/tailwind/scorers/scorer-tailwind.js +62 -11
- package/dist/analysts/xml/analysts/analyst-xml.js +2 -2
- package/dist/app/index.js +1 -0
- package/dist/bin/cli-bootstrap.js +6 -4
- package/dist/bin/index.js +6 -4
- package/dist/caretakers/scoring.js +1 -0
- package/dist/cli/commands/command-analysts.js +4 -3
- package/dist/cli/commands/command-azure.js +1 -1
- package/dist/cli/commands/command-circleci.js +1 -1
- package/dist/cli/commands/command-compliance.js +1 -1
- package/dist/cli/commands/command-diagnose.js +10 -13
- package/dist/cli/commands/command-fix-types.js +9 -32
- package/dist/cli/commands/command-formatters.js +2 -2
- package/dist/cli/commands/command-github-actions.js +1 -1
- package/dist/cli/commands/command-gitlab-ci.js +1 -1
- package/dist/cli/commands/command-guardian.js +1 -1
- package/dist/cli/commands/command-imports.js +6 -6
- package/dist/cli/commands/command-jenkins.js +1 -1
- package/dist/cli/commands/command-licenses.js +1 -1
- package/dist/cli/commands/command-marketplace.js +7 -7
- package/dist/cli/commands/command-names.js +3 -8
- package/dist/cli/commands/command-perf.js +7 -9
- package/dist/cli/commands/command-plugins.js +4 -4
- package/dist/cli/diagnostic/auto-fix.js +2 -2
- package/dist/cli/diagnostic/exporters/json-exporter.js +1 -1
- package/dist/cli/diagnostic/exporters/sharded-exporter.js +0 -3
- package/dist/cli/diagnostic/exporters/svg-optimization-exporter.js +1 -1
- package/dist/cli/diagnostic/filters.js +9 -9
- package/dist/cli/diagnostic/handlers/archetype-handler.js +3 -6
- package/dist/cli/diagnostic/handlers/auto-fix-handler.js +4 -4
- package/dist/cli/diagnostic/handlers/guardian-handler.js +1 -1
- package/dist/cli/diagnostic/processing-diagnostic.js +31 -35
- package/dist/cli/diagnostic/processing-helpers.js +2 -2
- package/dist/cli/handlers/fix-types-exporter.js +1 -4
- package/dist/cli/helpers/display-frame.js +5 -5
- package/dist/core/config/auto/auto-fix-config.js +0 -6
- package/dist/core/config/chalk-safe.js +6 -6
- package/dist/core/config/config.js +11 -13
- package/dist/core/config/conventions.js +1 -1
- package/dist/core/config/security.js +2 -2
- package/dist/core/execution/analysis-cache.js +5 -8
- package/dist/core/execution/ast-cache.js +1 -3
- package/dist/core/execution/executor.js +8 -21
- package/dist/core/execution/inquisitor.js +24 -21
- package/dist/core/execution/results.js +1 -0
- package/dist/core/execution/scanner.js +4 -4
- package/dist/core/messages/en/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/en/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/en/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/en/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/en/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/en/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/en/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/en/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/en/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/en/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/en/core/plugin-messages.js +31 -3
- package/dist/core/messages/en/log/log.js +4 -4
- package/dist/core/messages/en/ui/filter-config.js +1 -5
- package/dist/core/messages/i18n-system.js +3 -3
- package/dist/core/messages/index.js +3 -3
- package/dist/core/messages/ja/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/ja/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/ja/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/ja/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/ja/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/ja/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/ja/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/ja/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/ja/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/ja/core/plugin-messages.js +31 -3
- package/dist/core/messages/ja/log/log.js +4 -4
- package/dist/core/messages/ja/ui/filter-config.js +1 -1
- package/dist/core/messages/pt/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/pt/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/pt/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/pt/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/pt/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/pt/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/pt/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/pt/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/pt/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/pt/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/pt/core/plugin-messages.js +31 -3
- package/dist/core/messages/pt/log/log-engine.js +6 -6
- package/dist/core/messages/pt/log/log.js +3 -3
- package/dist/core/messages/pt/ui/filter-config.js +1 -1
- package/dist/core/messages/shared/icons.js +3 -3
- package/dist/core/messages/zh/analysts/analyst-github-actions-messages.js +1 -1
- package/dist/core/messages/zh/analysts/analyst-patterns-usage-messages.js +1 -0
- package/dist/core/messages/zh/analysts/analyst-shell-messages.js +15 -0
- package/dist/core/messages/zh/analysts/detector-constructions-syntactic-messages.js +6 -0
- package/dist/core/messages/zh/analysts/detector-js-ts-messages.js +27 -0
- package/dist/core/messages/zh/analysts/detector-react-messages.js +8 -0
- package/dist/core/messages/zh/analysts/detector-shell-messages.js +17 -0
- package/dist/core/messages/zh/analysts/detector-tailwind-messages.js +3 -3
- package/dist/core/messages/zh/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
- package/dist/core/messages/zh/core/plugin-messages.js +31 -3
- package/dist/core/messages/zh/log/log.js +3 -3
- package/dist/core/messages/zh/ui/filter-config.js +1 -5
- package/dist/core/parsing/filters.js +3 -3
- package/dist/core/parsing/langs/json.js +1 -1
- package/dist/core/parsing/langs/python.js +1 -1
- package/dist/core/parsing/langs/typescript.js +1 -1
- package/dist/core/parsing/parser.js +3 -4
- package/dist/core/parsing/utils.js +1 -0
- package/dist/core/registry/file-registry.js +3 -2
- package/dist/core/registry/paths.js +3 -3
- package/dist/core/reporting/default-reporter.js +2 -2
- package/dist/core/schema/version.js +6 -6
- package/dist/core/workers/worker-executor.js +3 -3
- package/dist/core/workers/worker-pool.js +0 -0
- package/dist/guardian/baseline.js +1 -1
- package/dist/guardian/gpg.js +3 -1
- package/dist/guardian/integrity.js +2 -7
- package/dist/guardian/records.js +7 -1
- package/dist/guardian/result.js +1 -1
- package/dist/guardian/sentinel.js +7 -1
- package/dist/guardian/watcher-hidden.js +6 -1
- package/dist/licenses/fs-utils.js +5 -1
- package/dist/licenses/generate-notices.js +1 -1
- package/dist/licenses/index.js +1 -0
- package/dist/licenses/normalizer.js +10 -9
- package/dist/node.loader.js +224 -4
- package/dist/reports/analise-async-patterns.js +1 -1
- package/dist/reports/filter-smart.js +2 -5
- package/dist/reports/generator-report.js +7 -7
- package/dist/reports/report-archetypes.js +10 -10
- package/dist/reports/report-caretaker-health.js +2 -1
- package/dist/reports/report-patterns-usage.js +1 -1
- package/dist/sdk/analyzer.js +1 -1
- package/dist/shared/data-processing/fragment-report.js +11 -11
- package/dist/shared/data-processing/json.js +1 -1
- package/dist/shared/data-processing/occurrences.js +3 -2
- package/dist/shared/formatters/code-min-formatter.js +1 -1
- package/dist/shared/formatters/engines/prettier.js +1 -1
- package/dist/shared/formatters/engines/stylelint.js +43 -40
- package/dist/shared/formatters/formatter.js +8 -8
- package/dist/shared/formatters/formatters/code.js +3 -3
- package/dist/shared/formatters/formatters/css.js +2 -10
- package/dist/shared/formatters/formatters/dotfiles.js +1 -1
- package/dist/shared/formatters/formatters/html.js +1 -5
- package/dist/shared/formatters/formatters/json.js +0 -3
- package/dist/shared/formatters/formatters/markdown.js +19 -24
- package/dist/shared/formatters/formatters/properties.js +2 -2
- package/dist/shared/formatters/formatters/shell.js +1 -4
- package/dist/shared/formatters/formatters/sql.js +0 -3
- package/dist/shared/formatters/formatters/xml.js +5 -7
- package/dist/shared/formatters/formatters/yaml.js +2 -7
- package/dist/shared/formatters/index.js +1 -1
- package/dist/shared/helpers/framework-detector.js +6 -2
- package/dist/shared/helpers/json.js +1 -1
- package/dist/shared/helpers/magic-constants-whitelist.js +1 -1
- package/dist/shared/helpers/masking.js +17 -17
- package/dist/shared/helpers/reader-report.js +0 -12
- package/dist/shared/helpers/structural-hash.js +3 -2
- package/dist/shared/helpers/structure.js +1 -1
- package/dist/shared/marketplace/registry.js +1 -1
- package/dist/shared/marketplace/remote.js +4 -4
- package/dist/shared/persistence/persistence.js +13 -6
- package/dist/shared/plugins/core-plugin.js +5 -1
- package/dist/shared/plugins/registry.js +3 -2
- package/dist/types/analysts/tailwind-correction.js +2 -0
- package/dist/types/caretakers/imports-barrel.js +1 -0
- package/dist/types/caretakers/index.js +4 -0
- package/dist/types/caretakers/map-reversion.js +1 -0
- package/dist/types/caretakers/pruning.js +1 -0
- package/dist/types/caretakers/scoring.js +1 -0
- package/dist/types/common/analysts.js +1 -1
- package/dist/types/core/config/filters.js +1 -0
- package/dist/types/core/config/index.js +2 -0
- package/dist/types/core/execution/ambiente.js +1 -0
- package/dist/types/core/execution/linguagens.js +1 -0
- package/dist/types/core/execution/parse-errors.js +1 -0
- package/dist/types/core/execution/results.js +1 -0
- package/dist/types/core/execution/structure-json.js +1 -0
- package/dist/types/core/index.js +1 -0
- package/dist/types/core/messages/index.js +1 -0
- package/dist/types/core/messages/log.js +1 -0
- package/dist/types/core/parsing/babel-narrow.js +1 -0
- package/dist/types/core/parsing/index.js +1 -0
- package/dist/types/core/runtime.js +1 -0
- package/dist/types/guardian/index.js +2 -0
- package/dist/types/guardian/integrity.js +7 -0
- package/dist/types/guardian/snapshot.js +1 -0
- package/dist/types/index.js +4 -4
- package/dist/types/licenses/index.js +2 -0
- package/dist/types/processing/display.js +16 -13
- package/dist/types/processing/filters.js +8 -11
- package/dist/types/reports/compliance.js +1 -0
- package/dist/types/reports/streaming.js +1 -0
- package/dist/types/scripts/index.js +1 -0
- package/dist/types/scripts/migrar-aliases.js +1 -0
- package/dist/types/sdk/index.js +1 -0
- package/dist/types/shared/index.js +2 -0
- package/dist/types/vulnerabilities/index.js +2 -0
- package/dist/vulnerabilities/scanner.js +3 -2
- 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(
|
|
116
|
+
out.push(metrica[0].slice(1, -1));
|
|
103
117
|
}
|
|
104
118
|
for (const metrica of text.matchAll(/'([^'\\\n]|\\.)*'/g)) {
|
|
105
|
-
out.push(
|
|
119
|
+
out.push(metrica[0].slice(1, -1));
|
|
106
120
|
}
|
|
107
121
|
for (const metrica of text.matchAll(/`([^`]*)`/g)) {
|
|
108
|
-
out.push(sanitizeTemplateLiteralText(
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
158
|
+
const idx = metrica.index;
|
|
151
159
|
if (idx < 0) {
|
|
152
160
|
continue;
|
|
153
161
|
}
|
|
154
|
-
const braceIdx = idx +
|
|
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
|
|
179
|
+
const idx = metrica.index;
|
|
173
180
|
if (idx < 0) {
|
|
174
181
|
continue;
|
|
175
182
|
}
|
|
176
|
-
const braceIdx = idx +
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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) && !
|
|
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
|
|
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
|
|
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
|
|
322
|
+
sugestao: 'Defina tokens personalizados via @theme no CSS ao invés de valores arbitrários'
|
|
208
323
|
}));
|
|
209
324
|
}
|
|
210
325
|
return ocorrencias;
|