@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
|
@@ -10,7 +10,7 @@ function detectSecurityIssues(src, relPath) {
|
|
|
10
10
|
// Loose comparison (== vs ===) in security-sensitive contexts
|
|
11
11
|
for (const match of scan.matchAll(/(?:if|elseif|while)\s*\([^)]*==[^=][^)]*\)/gi)) {
|
|
12
12
|
const line = lineOf(match.index);
|
|
13
|
-
const snippet = scan.slice(Math.max(0,
|
|
13
|
+
const snippet = scan.slice(Math.max(0, match.index - 40), match.index + 120);
|
|
14
14
|
if (/\$_(?:GET|POST|REQUEST|COOKIE|SERVER)/.test(snippet) || /password|senha|token|auth|login/i.test(snippet)) {
|
|
15
15
|
ocorrencias.push({
|
|
16
16
|
relPath,
|
|
@@ -37,7 +37,7 @@ function detectSecurityIssues(src, relPath) {
|
|
|
37
37
|
// XSS: echo/print with user input without htmlspecialchars
|
|
38
38
|
for (const match of scan.matchAll(/(?:echo|print)\s+.*\$(?:_GET|_POST|_REQUEST|_COOKIE|_SERVER)/gi)) {
|
|
39
39
|
const line = lineOf(match.index);
|
|
40
|
-
const before = scan.slice(Math.max(0,
|
|
40
|
+
const before = scan.slice(Math.max(0, match.index - 120), match.index);
|
|
41
41
|
if (!before.includes('htmlspecialchars') && !before.includes('htmlentities') && !before.includes('strip_tags') && !before.includes('filter_var') && !before.includes('esc_html') && !before.includes('e(')) {
|
|
42
42
|
ocorrencias.push({
|
|
43
43
|
relPath,
|
|
@@ -51,7 +51,7 @@ function detectSecurityIssues(src, relPath) {
|
|
|
51
51
|
}
|
|
52
52
|
// CSRF protection missing
|
|
53
53
|
for (const match of scan.matchAll(/<form[^>]*>/gi)) {
|
|
54
|
-
const formPos = match.index
|
|
54
|
+
const formPos = match.index;
|
|
55
55
|
const formTag = match[0];
|
|
56
56
|
const formEnd = scan.indexOf('</form>', formPos);
|
|
57
57
|
const formBlock = scan.slice(formPos, formEnd > 0 ? formEnd + 7 : formPos + 500);
|
|
@@ -138,8 +138,8 @@ function detectSecurityIssues(src, relPath) {
|
|
|
138
138
|
if (/(?:md5|sha1)\s*\(\s*\$.*pass/i.test(scan)) {
|
|
139
139
|
for (const match of scan.matchAll(/(?:md5|sha1)\s*\(/gi)) {
|
|
140
140
|
const line = lineOf(match.index);
|
|
141
|
-
const before = scan.slice(Math.max(0,
|
|
142
|
-
if (/pass/i.test(before) || /pass/i.test(scan.slice(match.index
|
|
141
|
+
const before = scan.slice(Math.max(0, match.index - 60), match.index);
|
|
142
|
+
if (/pass/i.test(before) || /pass/i.test(scan.slice(match.index, match.index + 80))) {
|
|
143
143
|
ocorrencias.push({
|
|
144
144
|
relPath,
|
|
145
145
|
mensagem: messages.PhpMensagens.weakPasswordHash,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import { splitLines } from '../../../shared/helpers/index.js';
|
|
3
3
|
import { detectorPhpBestPractices, detectorPhpSecurity } from '../detectors/index.js';
|
|
4
|
-
export
|
|
4
|
+
export function pontuarPhp(src, relPath) {
|
|
5
5
|
let score = 0;
|
|
6
6
|
const maxScore = 100;
|
|
7
7
|
const issues = [];
|
|
@@ -38,7 +38,7 @@ export async function pontuarPhp(src, relPath) {
|
|
|
38
38
|
let hasComplex = false;
|
|
39
39
|
let longFns = 0;
|
|
40
40
|
for (const fn of fnMatches) {
|
|
41
|
-
const bodyStart =
|
|
41
|
+
const bodyStart = fn.index + fn[0].length;
|
|
42
42
|
let braceCount = 1;
|
|
43
43
|
let bodyEnd = bodyStart;
|
|
44
44
|
for (let i = bodyStart; i < src.length && braceCount > 0; i++) {
|
|
@@ -173,6 +173,7 @@ function detectarPadroesDocumentacao(src, problemas, relPath) {
|
|
|
173
173
|
// 2. Tem exportações públicas E
|
|
174
174
|
// 3. Não tem nenhum JSDoc E
|
|
175
175
|
// 4. Não é arquivo de teste/config/setup
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- vars mutated inside forEach closure; TS can't track
|
|
176
177
|
if (isLibraryArquivo && temExportacaoPublica && !temJSDoc && !isInternalArquivo && src.length > 200) {
|
|
177
178
|
// Arquivo grande o suficiente para justificar documentação
|
|
178
179
|
problemas.push({
|
|
@@ -239,13 +240,11 @@ function detectarProblemasDocumentacaoAST(ast, problemas) {
|
|
|
239
240
|
}
|
|
240
241
|
function agruparPorPrioridade(problemas) {
|
|
241
242
|
return problemas.reduce((acc, problema) => {
|
|
242
|
-
const prioridade = problema.prioridade;
|
|
243
|
-
if (prioridade) {
|
|
244
|
-
|
|
245
|
-
acc[prioridade] = [];
|
|
246
|
-
}
|
|
247
|
-
acc[prioridade].push(problema);
|
|
243
|
+
const prioridade = problema.prioridade ?? 'media';
|
|
244
|
+
if (!(prioridade in acc)) {
|
|
245
|
+
acc[prioridade] = [];
|
|
248
246
|
}
|
|
247
|
+
acc[prioridade].push(problema);
|
|
249
248
|
return acc;
|
|
250
249
|
}, {});
|
|
251
250
|
}
|
|
@@ -257,6 +256,7 @@ function mapearPrioridadeParaNivel(prioridade) {
|
|
|
257
256
|
case 'media':
|
|
258
257
|
return 'aviso';
|
|
259
258
|
case 'baixa':
|
|
259
|
+
return 'info';
|
|
260
260
|
case undefined: {
|
|
261
261
|
throw new Error('Not implemented yet: undefined case');
|
|
262
262
|
}
|
|
@@ -100,9 +100,9 @@ function mergeWhitelist(base, override, mode) {
|
|
|
100
100
|
const overrideConfig = override ?? {};
|
|
101
101
|
if (mode === 'replace') {
|
|
102
102
|
return {
|
|
103
|
-
paths:
|
|
104
|
-
patterns:
|
|
105
|
-
dirs:
|
|
103
|
+
paths: overrideConfig.paths ?? [],
|
|
104
|
+
patterns: overrideConfig.patterns ?? [],
|
|
105
|
+
dirs: overrideConfig.dirs ?? []
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
const uniq = (arr) => Array.from(new Set(arr.map(String)));
|
|
@@ -110,9 +110,9 @@ function mergeWhitelist(base, override, mode) {
|
|
|
110
110
|
const overridePatterns = Array.isArray(overrideConfig.patterns) ? overrideConfig.patterns : [];
|
|
111
111
|
const overrideDirs = Array.isArray(overrideConfig.dirs) ? overrideConfig.dirs : [];
|
|
112
112
|
return {
|
|
113
|
-
paths: uniq([...
|
|
114
|
-
patterns: uniq([...
|
|
115
|
-
dirs: uniq([...
|
|
113
|
+
paths: uniq([...base.paths, ...overridePaths]),
|
|
114
|
+
patterns: uniq([...base.patterns, ...overridePatterns]),
|
|
115
|
+
dirs: uniq([...base.dirs, ...overrideDirs])
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
@@ -263,7 +263,7 @@ export const detectorMarkdown = {
|
|
|
263
263
|
test: (relPath) => {
|
|
264
264
|
return relPath.toLowerCase().endsWith('.md');
|
|
265
265
|
},
|
|
266
|
-
aplicar:
|
|
266
|
+
aplicar: (src, relPath, _ast, fullCaminho) => {
|
|
267
267
|
if (!fullCaminho) {
|
|
268
268
|
log.aviso(getMessages().LicensasMensagens.detectorMarkdownPath.replace('{arquivo}', relPath));
|
|
269
269
|
return [];
|
|
@@ -67,7 +67,7 @@ export function collectPythonIssues(src, relPath) {
|
|
|
67
67
|
ocorrencias.push(warn(messages.PythonMensagens.pickleUsage, relPath, line, messages.SeverityNiveis.error));
|
|
68
68
|
}
|
|
69
69
|
for (const match of scan.matchAll(/\byaml\.load\s*\([\s\S]*?\)/gi)) {
|
|
70
|
-
const text = match[0]
|
|
70
|
+
const text = match[0];
|
|
71
71
|
const hasSafeLoader = /\bLoader\s*=\s*yaml\.(?:SafeLoader|CSafeLoader)\b/.test(text);
|
|
72
72
|
const hasFullLoader = /\bLoader\s*=\s*yaml\.(?:FullLoader|CFullLoader)\b/.test(text);
|
|
73
73
|
const hasExplicitLoader = /\bLoader\s*=/.test(text);
|
|
@@ -85,7 +85,7 @@ export function collectPythonIssues(src, relPath) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
for (const match of scanNoComentarios.matchAll(/\b(?:execute|executemany|executescript)\s*\(\s*f(['"])(?:SELECT|INSERT|UPDATE|DELETE)[\s\S]*?\1/gi)) {
|
|
88
|
-
const text = match[0]
|
|
88
|
+
const text = match[0];
|
|
89
89
|
if (!/\{[^}]+\}/.test(text)) {
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
@@ -133,9 +133,6 @@ export function collectPythonIssues(src, relPath) {
|
|
|
133
133
|
}
|
|
134
134
|
// except blocks without logging the exception
|
|
135
135
|
for (const match of src.matchAll(/except\s+(\w+)\s+as\s+(\w+)\s*:((?:[ \t]+.*\n?)*?)(?=\n\S|\n\s*(?:except|else|finally|def|class)|$)/gm)) {
|
|
136
|
-
if (match[3] === undefined) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
136
|
const excVar = match[2];
|
|
140
137
|
const block = match[3];
|
|
141
138
|
if (!block || block.trim() === '' || block.trim() === 'pass') {
|
|
@@ -5,8 +5,19 @@ import { messages } from '../../../core/messages/index.js';
|
|
|
5
5
|
import { createLineLookup } from '../../../shared/helpers/index.js';
|
|
6
6
|
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
|
|
7
7
|
const disableEnv = process.env.SUKUNA_DISABLE_PLUGIN_REACT_HOOKS === '1';
|
|
8
|
+
const REACT_18_HOOKS = [
|
|
9
|
+
'useState', 'useEffect', 'useMemo', 'useCallback', 'useRef',
|
|
10
|
+
'useContext', 'useReducer', 'useLayoutEffect', 'useImperativeHandle',
|
|
11
|
+
'useDebugValue', 'useDeferredValue', 'useTransition',
|
|
12
|
+
'useSyncExternalStore', 'useInsertionEffect', 'useId',
|
|
13
|
+
];
|
|
14
|
+
// React 19+ built-in hooks
|
|
15
|
+
const REACT_19_HOOKS = [
|
|
16
|
+
'use', 'useOptimistic', 'useActionState', 'useFormStatus',
|
|
17
|
+
];
|
|
18
|
+
const ALL_BUILTIN_HOOKS = [...REACT_18_HOOKS, ...REACT_19_HOOKS];
|
|
8
19
|
function hasHooksUsage(src) {
|
|
9
|
-
return
|
|
20
|
+
return new RegExp(`\\b(?:${ALL_BUILTIN_HOOKS.join('|')})\\b`).test(src);
|
|
10
21
|
}
|
|
11
22
|
function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
|
|
12
23
|
return criarOcorrencia({
|
|
@@ -43,9 +54,10 @@ function extractHookCallContent(src, startIndex) {
|
|
|
43
54
|
function collectHookIssues(src, relPath) {
|
|
44
55
|
const ocorrencias = [];
|
|
45
56
|
const lineOf = createLineLookup(src).lineAt;
|
|
57
|
+
// Check useEffect/useLayoutEffect for deps
|
|
46
58
|
const effectMatches = [...src.matchAll(/use(Layout)?Effect\s*\(/g)];
|
|
47
59
|
effectMatches.forEach(m => {
|
|
48
|
-
const hookInicio = m.index
|
|
60
|
+
const hookInicio = m.index;
|
|
49
61
|
const fullCall = extractHookCallContent(src, hookInicio + m[0].length - 1);
|
|
50
62
|
const hasDepsArg = /,\s*(\[[\s\S]*?\]|\w+)\s*\)$/.test(fullCall);
|
|
51
63
|
const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
|
|
@@ -65,9 +77,10 @@ function collectHookIssues(src, relPath) {
|
|
|
65
77
|
}
|
|
66
78
|
}
|
|
67
79
|
});
|
|
80
|
+
// useMemo/useCallback deps check
|
|
68
81
|
const memoMatches = [...src.matchAll(/use(Memo|Callback)\s*\(/g)];
|
|
69
82
|
memoMatches.forEach(m => {
|
|
70
|
-
const hookInicio = m.index
|
|
83
|
+
const hookInicio = m.index;
|
|
71
84
|
const fullCall = extractHookCallContent(src, hookInicio + m[0].length - 1);
|
|
72
85
|
const hasDepsArg = /,\s*(\[[\s\S]*?\]|\w+)\s*\)$/.test(fullCall);
|
|
73
86
|
const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
|
|
@@ -76,15 +89,42 @@ function collectHookIssues(src, relPath) {
|
|
|
76
89
|
ocorrencias.push(warn(messages.ReactHooksMensagens.memoCallbackNoDeps, relPath, line));
|
|
77
90
|
}
|
|
78
91
|
});
|
|
92
|
+
// Conditional hooks
|
|
79
93
|
const conditionalHooks = [...src.matchAll(/(if|for|while)\s*\([^)]*\)\s*\{[\s\S]{0,160}?use[A-Z][A-Za-z0-9_]*/g)];
|
|
80
94
|
conditionalHooks.forEach(m => {
|
|
81
95
|
const line = lineOf(m.index || 0);
|
|
82
96
|
ocorrencias.push(warn(messages.ReactHooksMensagens.hookInConditional, relPath, line));
|
|
83
97
|
});
|
|
98
|
+
// React 19: useOptimistic detected
|
|
99
|
+
for (const match of src.matchAll(/\buseOptimistic\s*\(/g)) {
|
|
100
|
+
const line = lineOf(match.index);
|
|
101
|
+
ocorrencias.push(warn('useOptimistic (React 19) detected', relPath, line, messages.SeverityNiveis.info));
|
|
102
|
+
}
|
|
103
|
+
// React 19: useActionState detected
|
|
104
|
+
for (const match of src.matchAll(/\buseActionState\s*\(/g)) {
|
|
105
|
+
const line = lineOf(match.index);
|
|
106
|
+
ocorrencias.push(warn('useActionState (React 19) detected — verify form action integration', relPath, line, messages.SeverityNiveis.info));
|
|
107
|
+
}
|
|
108
|
+
// React 19: useFormStatus detected
|
|
109
|
+
for (const match of src.matchAll(/\buseFormStatus\s*\(/g)) {
|
|
110
|
+
const line = lineOf(match.index);
|
|
111
|
+
ocorrencias.push(warn('useFormStatus (React 19) detected — access pending form state', relPath, line, messages.SeverityNiveis.info));
|
|
112
|
+
}
|
|
113
|
+
// React 19: use() hook — suspends, must be in Suspense boundary
|
|
114
|
+
for (const match of src.matchAll(/\buse\s*\(/g)) {
|
|
115
|
+
const line = lineOf(match.index);
|
|
116
|
+
// Avoid matching "useState", "useEffect", etc.
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime safety for match.index === 0
|
|
118
|
+
if (src[match.index - 1]?.match(/[a-zA-Z]/)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
ocorrencias.push(warn('React 19 use() hook detected — must be wrapped in <Suspense> boundary', relPath, line, messages.SeverityNiveis.info));
|
|
122
|
+
}
|
|
123
|
+
// Custom hooks naming convention
|
|
84
124
|
const customHooks = [...src.matchAll(/\buse[A-Z][A-Za-z0-9_]*\s*=\s*\(/g)];
|
|
85
125
|
customHooks.forEach(m => {
|
|
86
126
|
const hookName = m[0].replace(/\s*=\s*\(/, '');
|
|
87
|
-
if (
|
|
127
|
+
if (!ALL_BUILTIN_HOOKS.includes(hookName)) {
|
|
88
128
|
if (/^use[A-Z]/.test(hookName)) {
|
|
89
129
|
const line = lineOf(m.index || 0);
|
|
90
130
|
ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line));
|
|
@@ -106,7 +146,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
106
146
|
if (isJsxLike) {
|
|
107
147
|
plugins.unshift('jsx');
|
|
108
148
|
}
|
|
109
|
-
const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins });
|
|
149
|
+
const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins: plugins });
|
|
110
150
|
const out = [];
|
|
111
151
|
const seen = new Set();
|
|
112
152
|
const pushOnce = (m) => {
|
|
@@ -120,7 +160,12 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
120
160
|
const declaredRefs = new Set();
|
|
121
161
|
const isHookCallee = (callee) => {
|
|
122
162
|
if (t.isIdentifier(callee)) {
|
|
123
|
-
|
|
163
|
+
const name = callee.name;
|
|
164
|
+
// React 19: use() is a special case (not prefixed with use but still a hook)
|
|
165
|
+
if (name === 'use') {
|
|
166
|
+
return { name };
|
|
167
|
+
}
|
|
168
|
+
return /^use[A-Z0-9_]/.test(name) ? { name } : null;
|
|
124
169
|
}
|
|
125
170
|
if (t.isMemberExpression(callee)) {
|
|
126
171
|
const obj = callee.object;
|
|
@@ -135,7 +180,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
135
180
|
};
|
|
136
181
|
const isEffectLike = (name) => name === 'useEffect' || name === 'useLayoutEffect' || name === 'useInsertionEffect';
|
|
137
182
|
const isMemoLike = (name) => name === 'useMemo' || name === 'useCallback';
|
|
138
|
-
const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name);
|
|
183
|
+
const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name) || name === 'use';
|
|
139
184
|
const inConditionalOrLoop = (path) => {
|
|
140
185
|
return Boolean(path.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression()));
|
|
141
186
|
};
|
|
@@ -207,6 +252,18 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
207
252
|
}
|
|
208
253
|
}
|
|
209
254
|
}
|
|
255
|
+
// React 19: use() hook should be in Suspense boundary
|
|
256
|
+
if (name === 'use') {
|
|
257
|
+
pushOnce(warn('React 19 use() hook — ensure wrapped in <Suspense> boundary', relPath, locLine));
|
|
258
|
+
}
|
|
259
|
+
// React 19: useOptimistic should have reducer function
|
|
260
|
+
if (name === 'useOptimistic' && path.node.arguments.length < 2) {
|
|
261
|
+
pushOnce(warn('useOptimistic expects initial state and update function (React 19)', relPath, locLine));
|
|
262
|
+
}
|
|
263
|
+
// React 19: useActionState should have form action
|
|
264
|
+
if (name === 'useActionState') {
|
|
265
|
+
pushOnce(warn('useActionState (React 19) — verify form action handler', relPath, locLine));
|
|
266
|
+
}
|
|
210
267
|
if (isEffectLike(name) && path.node.arguments.length >= 2) {
|
|
211
268
|
const callback = path.node.arguments[0];
|
|
212
269
|
const deps = path.node.arguments[1];
|
|
@@ -271,7 +328,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
271
328
|
}
|
|
272
329
|
}
|
|
273
330
|
}
|
|
274
|
-
if (isAnyHook(name) && !isEffectLike(name) && !isMemoLike(name) && name !== 'useState' && name !== 'useRef' && name !== 'useContext' && name !== 'useReducer' && name !== 'useDebugValue' && name !== 'useId') {
|
|
331
|
+
if (isAnyHook(name) && !isEffectLike(name) && !isMemoLike(name) && name !== 'useState' && name !== 'useRef' && name !== 'useContext' && name !== 'useReducer' && name !== 'useDebugValue' && name !== 'useId' && name !== 'useOptimistic' && name !== 'useActionState' && name !== 'useFormStatus' && name !== 'use') {
|
|
275
332
|
pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine));
|
|
276
333
|
}
|
|
277
334
|
}
|
|
@@ -290,7 +347,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
290
347
|
export const analistaReactHooks = criarAnalista({
|
|
291
348
|
nome: 'analista-react-hooks',
|
|
292
349
|
categoria: 'framework',
|
|
293
|
-
descricao: 'Heurísticas de Hooks com análise de dependências
|
|
350
|
+
descricao: 'Heurísticas de Hooks (React 18/19) com análise de dependências.',
|
|
294
351
|
global: false,
|
|
295
352
|
test: (relPath) => /\.(jsx|tsx|js|ts)$/i.test(relPath),
|
|
296
353
|
aplicar: (src, relPath) => {
|
|
@@ -79,6 +79,50 @@ function collectReactIssues(src, relPath) {
|
|
|
79
79
|
ocorrencias.push(warn(messages.ReactMensagens.locationHrefRedirect, relPath, line));
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
// React 19: <form action={...}>
|
|
83
|
+
for (const match of src.matchAll(/<form[^>]*>/gi)) {
|
|
84
|
+
const hasAction = /\saction\s*=\s*\{/.test(match[0]);
|
|
85
|
+
if (hasAction) {
|
|
86
|
+
const line = lineOf(match.index);
|
|
87
|
+
ocorrencias.push(warn('React 19 form action detected', relPath, line, messages.SeverityNiveis.info));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// React 19: use() hook (called at top level in render)
|
|
91
|
+
for (const match of src.matchAll(/\bconst\s+\w+\s*=\s*use\s*\(/g)) {
|
|
92
|
+
const line = lineOf(match.index);
|
|
93
|
+
if (!/use[A-Z]/.test(match[0])) {
|
|
94
|
+
ocorrencias.push(warn('React 19 use() hook detected — ensure wrapped in Suspense boundary', relPath, line, messages.SeverityNiveis.info));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 'use client' / 'use server' directives
|
|
98
|
+
if (/^['"]use (client|server)['"];/m.test(src)) {
|
|
99
|
+
ocorrencias.push(warn('Server Component directive detected', relPath, 1, messages.SeverityNiveis.info));
|
|
100
|
+
}
|
|
101
|
+
if (!/^['"]use client['"];/m.test(src) && /useState|useEffect|useContext|onClick/.test(src)) {
|
|
102
|
+
// This file uses client-side features without 'use client'
|
|
103
|
+
const hasClientFeatures = /useState|useEffect|useContext|onClick/.test(src);
|
|
104
|
+
if (hasClientFeatures) {
|
|
105
|
+
const firstLine = src.split('\n')[0]?.trim() ?? '';
|
|
106
|
+
if (firstLine.startsWith('import')) {
|
|
107
|
+
ocorrencias.push(warn('File uses client-side features (hooks/events) but missing \'use client\' directive (React 19 Server Components)', relPath, 1, messages.SeverityNiveis.suggestion));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// React 19: forwardRef deprecation
|
|
112
|
+
for (const match of src.matchAll(/\bforwardRef\s*\(/g)) {
|
|
113
|
+
const line = lineOf(match.index);
|
|
114
|
+
ocorrencias.push(warn('forwardRef is deprecated in React 19 — pass ref as a regular prop instead', relPath, line, messages.SeverityNiveis.suggestion));
|
|
115
|
+
}
|
|
116
|
+
// React 19: React.FC / React.FunctionComponent / React.VoidFunctionComponent
|
|
117
|
+
for (const match of src.matchAll(/\bReact\.(FC|FunctionComponent|VoidFunctionComponent)\b/g)) {
|
|
118
|
+
const line = lineOf(match.index);
|
|
119
|
+
ocorrencias.push(warn(`React.${match[1]} is deprecated in React 19 — type props directly: (props: Props) => JSX.Element`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
120
|
+
}
|
|
121
|
+
// React 19: defaultProps
|
|
122
|
+
for (const match of src.matchAll(/\.defaultProps\s*=\s*\{/g)) {
|
|
123
|
+
const line = lineOf(match.index);
|
|
124
|
+
ocorrencias.push(warn('defaultProps is deprecated in React 19 — use default parameter values instead', relPath, line, messages.SeverityNiveis.suggestion));
|
|
125
|
+
}
|
|
82
126
|
return ocorrencias;
|
|
83
127
|
}
|
|
84
128
|
function normalizeStringValue(valor) {
|
|
@@ -92,7 +136,7 @@ function attrsToFlatList(attrs) {
|
|
|
92
136
|
return [];
|
|
93
137
|
}
|
|
94
138
|
return attrs.filter(isBabelNode).filter((arquivo) => textoTitulo.isJSXAttribute(arquivo)).filter(arquivo => textoTitulo.isJSXIdentifier(arquivo.name)).map(arquivo => {
|
|
95
|
-
const name =
|
|
139
|
+
const name = arquivo.name.name;
|
|
96
140
|
let value;
|
|
97
141
|
if (!arquivo.value) {
|
|
98
142
|
value = null;
|
|
@@ -141,7 +185,7 @@ function parseReactWithBabel(scan, relPath) {
|
|
|
141
185
|
const ast = babelParse(scan, {
|
|
142
186
|
sourceType: 'unambiguous',
|
|
143
187
|
errorRecovery: true,
|
|
144
|
-
plugins
|
|
188
|
+
plugins: plugins
|
|
145
189
|
});
|
|
146
190
|
const ocorrencias = [];
|
|
147
191
|
const seen = new Set();
|
|
@@ -19,7 +19,7 @@ export function detectarCorrecoesReact(src, _relPath) {
|
|
|
19
19
|
correcoes.push({
|
|
20
20
|
tipo: 'react-dangerous-html',
|
|
21
21
|
original: linha.trim(),
|
|
22
|
-
sugerido: '//
|
|
22
|
+
sugerido: '// SEGURANCA: Use DOMPurify.sanitize() antes de dangerouslySetInnerHTML\n// <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />',
|
|
23
23
|
linha: numeroLinha,
|
|
24
24
|
confianca: 90
|
|
25
25
|
});
|
|
@@ -87,6 +87,46 @@ export function detectarCorrecoesReact(src, _relPath) {
|
|
|
87
87
|
confianca: 50
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
+
// React 19: forwardRef replacement
|
|
91
|
+
if (/\bforwardRef\s*\(/.test(linha)) {
|
|
92
|
+
correcoes.push({
|
|
93
|
+
tipo: 'react-forwardref-to-prop',
|
|
94
|
+
original: linha.trim(),
|
|
95
|
+
sugerido: '// React 19: passe ref como prop em vez de forwardRef\n// Ex: function MeuComponente({ ref, ...props }) { ... }',
|
|
96
|
+
linha: numeroLinha,
|
|
97
|
+
confianca: 85
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// React 19: React.FC replacement
|
|
101
|
+
if (/\bReact\.(FC|FunctionComponent)\s*</.test(linha)) {
|
|
102
|
+
correcoes.push({
|
|
103
|
+
tipo: 'react-fc-to-direct-type',
|
|
104
|
+
original: linha.trim(),
|
|
105
|
+
sugerido: '// React 19: tipagem direta de props\n// Ex: function MeuComponente({ prop1, prop2 }: Props)',
|
|
106
|
+
linha: numeroLinha,
|
|
107
|
+
confianca: 85
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// React 19: defaultProps replacement
|
|
111
|
+
if (/\.defaultProps\s*=/.test(linha)) {
|
|
112
|
+
correcoes.push({
|
|
113
|
+
tipo: 'react-default-props-to-params',
|
|
114
|
+
original: linha.trim(),
|
|
115
|
+
sugerido: '// React 19: use default parameter values\n// Ex: function MeuComponente({ prop = valor })',
|
|
116
|
+
linha: numeroLinha,
|
|
117
|
+
confianca: 90
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// React 19: 'use client' directive missing
|
|
121
|
+
if (/^\s*import\s+/.test(linha) && /useState|useEffect|onClick/.test(linha)) {
|
|
122
|
+
correcoes.push({
|
|
123
|
+
tipo: 'react-use-client-missing',
|
|
124
|
+
original: linha.trim(),
|
|
125
|
+
sugerido: `'use client';\n\n${linha.trim()}`,
|
|
126
|
+
linha: numeroLinha,
|
|
127
|
+
confianca: 70
|
|
128
|
+
});
|
|
129
|
+
}
|
|
90
130
|
});
|
|
91
131
|
return correcoes;
|
|
92
132
|
}
|
|
@@ -66,7 +66,7 @@ function detectWithBabel(src, relPath) {
|
|
|
66
66
|
try {
|
|
67
67
|
const plugins = ['decorators-legacy', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'optionalChaining', 'nullishCoalescingOperator', 'topLevelAwait', 'importAttributes', 'jsx'];
|
|
68
68
|
plugins.unshift('typescript');
|
|
69
|
-
const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins });
|
|
69
|
+
const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins: plugins });
|
|
70
70
|
const out = [];
|
|
71
71
|
const seen = new Set();
|
|
72
72
|
const pushOnce = (o) => {
|
|
@@ -78,7 +78,7 @@ function detectWithBabel(src, relPath) {
|
|
|
78
78
|
out.push(o);
|
|
79
79
|
};
|
|
80
80
|
const _lineOf = createLineLookup(src).lineAt;
|
|
81
|
-
const isHookName = (name) => /^use[A-Z0-9_]/.test(name)
|
|
81
|
+
const isHookName = (name) => /^use[A-Z0-9_]/.test(name);
|
|
82
82
|
const declaredVars = new Set();
|
|
83
83
|
const visitor = {
|
|
84
84
|
VariableDeclarator(path) {
|
|
@@ -134,7 +134,7 @@ function detectWithBabel(src, relPath) {
|
|
|
134
134
|
}
|
|
135
135
|
if (name === 'useEffect' || name === 'useLayoutEffect' || name === 'useInsertionEffect') {
|
|
136
136
|
const bodyArg = node.arguments[0];
|
|
137
|
-
if (
|
|
137
|
+
if ((t.isFunctionExpression(bodyArg) || t.isArrowFunctionExpression(bodyArg)) && t.isBlockStatement(bodyArg.body)) {
|
|
138
138
|
const bodyStmts = bodyArg.body.body;
|
|
139
139
|
const hasCleanup = bodyStmts.some(stmt => t.isReturnStatement(stmt) && stmt.argument && (t.isFunctionExpression(stmt.argument) || t.isArrowFunctionExpression(stmt.argument)));
|
|
140
140
|
const hasSubscriptions = /\b(subscribe|addEventListener|on\()/.test(src.slice(bodyArg.start ?? 0, bodyArg.end ?? 0));
|
|
@@ -174,7 +174,7 @@ function detectWithBabel(src, relPath) {
|
|
|
174
174
|
}
|
|
175
175
|
return false;
|
|
176
176
|
});
|
|
177
|
-
if (stateCalls.length > 1
|
|
177
|
+
if (stateCalls.length > 1) {
|
|
178
178
|
pushOnce(warn('react-infinite-loop', D.infiniteLoopPattern, relPath, locLine, D.infiniteLoopPatternSugestao, 'aviso'));
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -273,7 +273,7 @@ function detectWithBabel(src, relPath) {
|
|
|
273
273
|
}
|
|
274
274
|
const fragmentParent = path.findParent(p => p.isJSXFragment());
|
|
275
275
|
if (fragmentParent?.isJSXFragment()) {
|
|
276
|
-
const children = fragmentParent.node.children.filter(c => !t.isJSXText(c) ||
|
|
276
|
+
const children = fragmentParent.node.children.filter(c => !t.isJSXText(c) || c.value.trim() !== '');
|
|
277
277
|
if (children.length <= 1) {
|
|
278
278
|
pushOnce(warn('react-unnecessary-fragment', D.unnecessaryFragment, relPath, locLine, undefined, 'info'));
|
|
279
279
|
}
|
|
@@ -6,8 +6,43 @@ export function pontuarReact(src, _relPath) {
|
|
|
6
6
|
const issues = [];
|
|
7
7
|
const strengths = [];
|
|
8
8
|
const srcCompleto = src;
|
|
9
|
+
// React 19 patterns: forwardRef deprecation, React.FC, Server Components
|
|
10
|
+
const hasForwardRef = /\bforwardRef\s*\(/.test(srcCompleto);
|
|
11
|
+
if (hasForwardRef) {
|
|
12
|
+
issues.push('forwardRef é deprecated no React 19 — passe ref como prop direta');
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
score += 3;
|
|
16
|
+
strengths.push('Sem forwardRef (estilo React 19)');
|
|
17
|
+
}
|
|
18
|
+
const hasReactFC = /\bReact\.(FC|FunctionComponent)\b/.test(srcCompleto);
|
|
19
|
+
if (hasReactFC) {
|
|
20
|
+
issues.push('React.FC/FunctionComponent é deprecated no React 19');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
score += 2;
|
|
24
|
+
strengths.push('Sem React.FC (tipagem direta de props)');
|
|
25
|
+
}
|
|
26
|
+
const hasUseClient = /^['"]use client['"];/m.test(srcCompleto);
|
|
27
|
+
const hasServerFeatures = /useState|useEffect|useContext|onClick/.test(srcCompleto);
|
|
28
|
+
if (hasServerFeatures && !hasUseClient) {
|
|
29
|
+
const firstLine = srcCompleto.split('\n')[0]?.trim() ?? '';
|
|
30
|
+
if (firstLine.startsWith('import')) {
|
|
31
|
+
issues.push('Componente usa hooks/eventos sem diretiva \'use client\' (React 19)');
|
|
32
|
+
score = Math.max(0, score - 3);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (hasUseClient) {
|
|
36
|
+
score += 3;
|
|
37
|
+
strengths.push('Diretiva \'use client\' presente (React 19)');
|
|
38
|
+
}
|
|
39
|
+
const hasFormAction = /<form[^>]*\saction\s*=/.test(srcCompleto);
|
|
40
|
+
if (hasFormAction) {
|
|
41
|
+
strengths.push('Uso de form action (React 19)');
|
|
42
|
+
score += 2;
|
|
43
|
+
}
|
|
9
44
|
// Hooks usage & compliance (20 pontos)
|
|
10
|
-
const usaHooks = /use(State|Effect|Memo|Callback|Reducer|Ref|Context|LayoutEffect|ImperativeHandle|Transition|DeferredValue|SyncExternalStore|InsertionEffect|Id)/.test(srcCompleto);
|
|
45
|
+
const usaHooks = /use(State|Effect|Memo|Callback|Reducer|Ref|Context|LayoutEffect|ImperativeHandle|Transition|DeferredValue|SyncExternalStore|InsertionEffect|Id|Optimistic|ActionState|FormStatus)|\buse\s*\(/.test(srcCompleto);
|
|
11
46
|
let hookPontos = 0;
|
|
12
47
|
if (usaHooks) {
|
|
13
48
|
hookPontos = 10;
|
|
@@ -13,8 +13,8 @@ function extrairEntradasDeModulo(mod) {
|
|
|
13
13
|
const out = [];
|
|
14
14
|
const candidates = [];
|
|
15
15
|
candidates.push(metrica.default);
|
|
16
|
-
if (Array.isArray(metrica.analistas)) {
|
|
17
|
-
candidates.push(...metrica.analistas);
|
|
16
|
+
if (Array.isArray((metrica).analistas)) {
|
|
17
|
+
candidates.push(...(metrica).analistas);
|
|
18
18
|
}
|
|
19
19
|
// Coleta exportações nomeadas que pareçam analista/técnica
|
|
20
20
|
for (const valor of Object.values(metrica)) {
|
|
@@ -32,7 +32,7 @@ function collectBestPractices(src, relPath) {
|
|
|
32
32
|
// Excessive function length
|
|
33
33
|
const funcBlocks = src.matchAll(/(?:^|\n)\s*fn\s+\w+.*?\{/gs);
|
|
34
34
|
for (const funcBlock of funcBlocks) {
|
|
35
|
-
const startIdx =
|
|
35
|
+
const startIdx = funcBlock.index + funcBlock[0].length;
|
|
36
36
|
let braceCount = 1;
|
|
37
37
|
let endIdx = startIdx;
|
|
38
38
|
for (let i = startIdx; i < src.length && braceCount > 0; i++) {
|
|
@@ -47,7 +47,7 @@ function collectBestPractices(src, relPath) {
|
|
|
47
47
|
const funcBody = src.slice(startIdx, endIdx);
|
|
48
48
|
const funcLines = funcBody.split('\n').length;
|
|
49
49
|
if (funcLines > 80) {
|
|
50
|
-
const line = lineOf(funcBlock.index
|
|
50
|
+
const line = lineOf(funcBlock.index);
|
|
51
51
|
ocorrencias.push(warn(messages.RustMensagens.longFunction, relPath, line));
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -64,12 +64,51 @@ function collectBestPractices(src, relPath) {
|
|
|
64
64
|
}
|
|
65
65
|
declaredVars.add(varName);
|
|
66
66
|
}
|
|
67
|
+
// Vec::push in a loop without pre-allocation
|
|
68
|
+
for (const match of scan.matchAll(/for\s+(\w+)\s+in\s+(\w+)\.iter\(\)\s*\{[\s\S]{0,200}results\.push\(/g)) {
|
|
69
|
+
const line = lineOf(match.index);
|
|
70
|
+
ocorrencias.push(warn('Collection built with push() in loop; consider using .map().collect() or Iterator adapters', relPath, line, messages.SeverityNiveis.suggestion));
|
|
71
|
+
}
|
|
72
|
+
// Use of as_ptr_range / as_mut_ptr_range (Rust 1.77+)
|
|
73
|
+
for (const match of scan.matchAll(/\.as_(ptr|mut_ptr)_range\(\)/g)) {
|
|
74
|
+
const line = lineOf(match.index);
|
|
75
|
+
ocorrencias.push(warn(`.${match[1]}_range() used (Rust 1.77+)`, relPath, line, messages.SeverityNiveis.info));
|
|
76
|
+
}
|
|
77
|
+
// Option::unwrap_or_else(|| ...) -> unwrap_or(...) when argument is constant
|
|
78
|
+
for (const match of scan.matchAll(/\.unwrap_or_else\(\s*\|\|\s*(\w+)::(\w+)\s*\)/g)) {
|
|
79
|
+
const line = lineOf(match.index);
|
|
80
|
+
ocorrencias.push(warn(`.unwrap_or_else(|| ...) with constant default; consider .unwrap_or(...) (Rust 1.78+)`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
81
|
+
}
|
|
82
|
+
// use of assert!(expr.is_some()) -> assert!(expr.is_some()) is fine, but check for unwrap after
|
|
83
|
+
for (const match of scan.matchAll(/if\s+(\w+)\.is_some\(\)\s*\{[\s\S]{0,100}(\w+)\.unwrap\(\)/g)) {
|
|
84
|
+
const line = lineOf(match.index);
|
|
85
|
+
ocorrencias.push(warn('Guard with .is_some() followed by .unwrap(); use if let Some(x) = ... instead', relPath, line, messages.SeverityNiveis.suggestion));
|
|
86
|
+
}
|
|
87
|
+
// use of format!("literal {var}") without explicit capture (Rust 1.80+)
|
|
88
|
+
for (const match of scan.matchAll(/format!\(["']([^"']*\{(\w+)\}[^"']*)["'],\s*\2\s*\)/g)) {
|
|
89
|
+
const line = lineOf(match.index);
|
|
90
|
+
const varName = match[2];
|
|
91
|
+
ocorrencias.push(warn(`format!("...{${varName}}...", ${varName}) can be simplified to format!("...{${varName}}...") using implicit capture (Rust 1.80+)`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
92
|
+
}
|
|
93
|
+
// detect assert!(matches!(expr, pattern)) pattern -> suggest assert_matches!
|
|
94
|
+
for (const match of scan.matchAll(/assert!\(matches!\(([^,]+),\s*([^)]+)\)\s*\)/g)) {
|
|
95
|
+
const line = lineOf(match.index);
|
|
96
|
+
ocorrencias.push(warn(`assert!(matches!(${match[1]}, ${match[2]})) — consider assert_matches!(${match[1]}, ${match[2]}) (nightly Rust 1.81+)`, relPath, line, messages.SeverityNiveis.info));
|
|
97
|
+
}
|
|
98
|
+
// detect Vec<_>::new() -> Vec::new() (type inference)
|
|
99
|
+
// detect .into_iter() -> .iter()/iter_mut() when not consuming
|
|
100
|
+
for (const match of scan.matchAll(/\.into_iter\(\)\.(map|filter|for_each)/g)) {
|
|
101
|
+
const line = lineOf(match.index);
|
|
102
|
+
if (!src.slice(0, match.index).includes('.collect')) {
|
|
103
|
+
ocorrencias.push(warn('.into_iter() used but not consuming; consider .iter() for read-only iteration', relPath, line, messages.SeverityNiveis.suggestion));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
67
106
|
return ocorrencias;
|
|
68
107
|
}
|
|
69
108
|
export const analistaRustBoasPraticas = criarAnalista({
|
|
70
109
|
nome: 'analista-rust-boas-praticas',
|
|
71
110
|
categoria: 'boas-praticas',
|
|
72
|
-
descricao: 'Detecta desvios de boas práticas
|
|
111
|
+
descricao: 'Detecta desvios de boas práticas Rust (edition 2024, Rust 1.80+).',
|
|
73
112
|
global: false,
|
|
74
113
|
test: (relPath) => /\.(rs)$/i.test(relPath),
|
|
75
114
|
aplicar: (src, relPath) => {
|
|
@@ -20,7 +20,7 @@ function collectMemoryIssues(src, relPath) {
|
|
|
20
20
|
// Vec without with_capacity
|
|
21
21
|
for (const match of scan.matchAll(/(\w+)\.push\(/g)) {
|
|
22
22
|
const line = lineOf(match.index);
|
|
23
|
-
const varName = match[1]
|
|
23
|
+
const varName = match[1];
|
|
24
24
|
const declMatch = new RegExp(`let\\s+mut\\s+${varName}\\s*:\\s*Vec|<|=\\s*Vec::new\\(\\)`, 'i');
|
|
25
25
|
if (declMatch.test(scan.slice(0, match.index))) {
|
|
26
26
|
ocorrencias.push(warn(messages.RustMensagens.vecWithoutCapacity, relPath, line, messages.SeverityNiveis.suggestion));
|
|
@@ -29,7 +29,7 @@ function collectMemoryIssues(src, relPath) {
|
|
|
29
29
|
// read_line without trimming
|
|
30
30
|
for (const match of scan.matchAll(/(\w+)\.read_line\(&mut\s+(\w+)\)/g)) {
|
|
31
31
|
const line = lineOf(match.index);
|
|
32
|
-
const varName = match[2]
|
|
32
|
+
const varName = match[2];
|
|
33
33
|
const snippet = src.slice(match.index, match.index + 200);
|
|
34
34
|
if (!snippet.includes(`${varName}.trim()`) && !snippet.includes(`${varName}.trim_end()`)) {
|
|
35
35
|
ocorrencias.push(warn(messages.RustMensagens.noTrimOnInput, relPath, line));
|