@mocoto/mahoraga 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysts/architects/analyst-structure.js +1 -1
- package/dist/analysts/architects/archetypes-custom.js +8 -13
- package/dist/analysts/architects/detector-archetypes.js +39 -35
- package/dist/analysts/architects/orchestrator-archetypes.js +3 -3
- package/dist/analysts/architects/signals-project-advanced.js +2 -2
- package/dist/analysts/architects/signals-project.js +1 -1
- package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +20 -21
- package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +0 -3
- package/dist/analysts/circleci/analysts/analyst-circleci.js +22 -24
- package/dist/analysts/circleci/detectors/detector-circleci-structure.js +4 -7
- package/dist/analysts/converters/convert-registry.js +1 -1
- package/dist/analysts/converters/index.js +1 -1
- package/dist/analysts/corrections/auto-fix-engine.js +1 -1
- package/dist/analysts/corrections/corrector-structure.js +8 -7
- package/dist/analysts/corrections/map-reversion.js +2 -2
- package/dist/analysts/corrections/reorganize-barrels.js +3 -3
- package/dist/analysts/corrections/rewrite-testes-aliases.js +23 -22
- package/dist/analysts/corrections/type-safety/type-analyzer.js +3 -3
- package/dist/analysts/corrections/type-safety/type-validator.js +2 -2
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +3 -3
- package/dist/analysts/css/analysts/analyst-css.js +27 -28
- package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +8 -15
- package/dist/analysts/detectors/detector-architecture.js +2 -2
- package/dist/analysts/detectors/detector-bugs-ml.js +8 -8
- package/dist/analysts/detectors/detector-code-fragile.js +11 -10
- package/dist/analysts/detectors/detector-dependencies.js +5 -5
- package/dist/analysts/detectors/detector-duplications.js +5 -8
- package/dist/analysts/detectors/detector-monorepo.js +10 -9
- package/dist/analysts/detectors/detector-performance.js +2 -1
- package/dist/analysts/detectors/detector-recommendation-architecture.js +1 -1
- package/dist/analysts/detectors/detector-security.js +1 -1
- package/dist/analysts/detectors/detector-structure.js +3 -3
- 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 +81 -9
- package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +35 -6
- 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 +5 -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 +63 -8
- package/dist/analysts/react/analysts/analyst-react.js +45 -1
- package/dist/analysts/react/corrections/correction-react.js +40 -0
- package/dist/analysts/react/detectors/detector-react-best-practices.js +4 -4
- package/dist/analysts/react/scorers/scorer-react.js +36 -1
- 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 +116 -9
- package/dist/analysts/tailwind/detectors/detector-tailwind-best-practices.js +122 -7
- package/dist/analysts/tailwind/scorers/scorer-tailwind.js +62 -10
- 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 +1 -1
- 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-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 +3 -3
- 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/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 +2 -2
- package/dist/core/config/config.js +6 -8
- 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/executor.js +8 -19
- package/dist/core/execution/inquisitor.js +24 -21
- package/dist/core/execution/results.js +1 -0
- package/dist/core/execution/scanner.js +3 -3
- 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/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/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/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/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 +2 -2
- 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 +6 -1
- package/dist/guardian/result.js +1 -1
- package/dist/guardian/sentinel.js +6 -1
- package/dist/guardian/watcher-hidden.js +5 -1
- package/dist/licenses/fs-utils.js +4 -1
- package/dist/licenses/generate-notices.js +1 -1
- package/dist/licenses/header-options.js +0 -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 +4 -4
- package/dist/reports/report-archetypes.js +10 -10
- package/dist/reports/report-caretaker-health.js +2 -1
- package/dist/sdk/analyzer.js +1 -1
- package/dist/shared/data-processing/fragment-report.js +10 -10
- 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 +6 -9
- 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 +18 -23
- 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 +1 -1
- 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 +0 -1
- package/dist/shared/plugins/core-plugin.js +3 -1
- package/dist/shared/plugins/registry.js +3 -2
- 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 +3 -7
- 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/npm-audit.js +0 -1
- package/dist/vulnerabilities/scanner.js +2 -1
- package/dist/vulnerabilities/vulnerabilities.js +0 -1
- package/package.json +11 -12
|
@@ -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));
|
|
@@ -20,7 +20,6 @@ function collectGeneralRustIssues(src, relPath) {
|
|
|
20
20
|
const ocorrencias = [];
|
|
21
21
|
const lineOf = createLineLookup(src).lineAt;
|
|
22
22
|
const scan = maskRustComments(src);
|
|
23
|
-
// Missing module-level comment
|
|
24
23
|
const firstItem = scan.match(/^\s*(?:pub\s+)?(?:fn|mod|struct|enum|trait|impl)\s+\w+/m);
|
|
25
24
|
if (firstItem) {
|
|
26
25
|
const srcBefore = src.slice(0, firstItem.index ?? 0);
|
|
@@ -29,7 +28,6 @@ function collectGeneralRustIssues(src, relPath) {
|
|
|
29
28
|
ocorrencias.push(warn('Module or crate missing documentation comment (//! or /*! */)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
// Snake case convention for functions
|
|
33
31
|
for (const match of scan.matchAll(/^\s*(?:pub\s+)?fn\s+(\w+)\s*\(/gm)) {
|
|
34
32
|
const fnName = match[1];
|
|
35
33
|
if (/[A-Z]/.test(fnName)) {
|
|
@@ -37,7 +35,6 @@ function collectGeneralRustIssues(src, relPath) {
|
|
|
37
35
|
ocorrencias.push(warn(`Function '${fnName}' should follow snake_case convention`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
|
-
// PascalCase for types
|
|
41
38
|
for (const match of scan.matchAll(/^\s*(?:pub\s+)?(?:struct|enum|trait|type)\s+(\w+)/gm)) {
|
|
42
39
|
const typeName = match[1];
|
|
43
40
|
if (/^[a-z_]/.test(typeName)) {
|
|
@@ -45,7 +42,6 @@ function collectGeneralRustIssues(src, relPath) {
|
|
|
45
42
|
ocorrencias.push(warn(`Type '${typeName}' should follow PascalCase convention`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
|
-
// Deep nesting
|
|
49
45
|
let maxDepth = 0;
|
|
50
46
|
const lines = src.split('\n');
|
|
51
47
|
for (const line of lines) {
|
|
@@ -56,12 +52,63 @@ function collectGeneralRustIssues(src, relPath) {
|
|
|
56
52
|
if (maxDepth > 8) {
|
|
57
53
|
ocorrencias.push(warn(`Deep nesting detected (${maxDepth} levels); consider refactoring`, relPath, 1, messages.SeverityNiveis.suggestion));
|
|
58
54
|
}
|
|
55
|
+
// detect Lazy/OnceCell from once_cell crate -> suggest std::sync::LazyLock (Rust 1.80+)
|
|
56
|
+
for (const match of scan.matchAll(/\b(once_cell::sync::Lazy|once_cell::sync::OnceCell)\b/g)) {
|
|
57
|
+
const line = lineOf(match.index);
|
|
58
|
+
const isLazy = match[1].includes('Lazy');
|
|
59
|
+
ocorrencias.push(warn(isLazy
|
|
60
|
+
? `'once_cell::sync::Lazy' detected; use std::sync::LazyLock (stable since Rust 1.80)`
|
|
61
|
+
: `'once_cell::sync::OnceCell' detected; use std::sync::OnceLock (stable since Rust 1.70)`, relPath, line, messages.SeverityNiveis.suggestion));
|
|
62
|
+
}
|
|
63
|
+
// detect manual offset calculation -> suggest std::mem::offset_of! (Rust 1.77+)
|
|
64
|
+
for (const match of scan.matchAll(/offset_of!\(/g)) {
|
|
65
|
+
const line = lineOf(match.index);
|
|
66
|
+
ocorrencias.push(warn('offset_of! macro usage is correct for Rust 1.77+', relPath, line, messages.SeverityNiveis.info));
|
|
67
|
+
}
|
|
68
|
+
// detect manual transmute-like offset patterns
|
|
69
|
+
for (const match of scan.matchAll(/(\w+) as \*const u8\)\.offset\((\d+)\)/g)) {
|
|
70
|
+
const line = lineOf(match.index);
|
|
71
|
+
ocorrencias.push(warn('Manual pointer offset for field access; consider std::mem::offset_of! (Rust 1.77+)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
72
|
+
}
|
|
73
|
+
// detect assert!(matches!(...)) -> suggest assert_matches! (Rust 1.81+)
|
|
74
|
+
for (const match of scan.matchAll(/assert!\(matches!\(/g)) {
|
|
75
|
+
const line = lineOf(match.index);
|
|
76
|
+
ocorrencias.push(warn('assert!(matches!(...)) pattern; consider assert_matches!(...) (unstable, nightly only)', relPath, line, messages.SeverityNiveis.info));
|
|
77
|
+
}
|
|
78
|
+
// detect if let ... && let ... (let_chains, stable since Rust 1.78)
|
|
79
|
+
for (const match of scan.matchAll(/if\s+let\s+\w+\s*=\s*\w+\s*&&\s*let\s+/g)) {
|
|
80
|
+
const line = lineOf(match.index);
|
|
81
|
+
ocorrencias.push(warn('let_chains pattern detected (stable since Rust 1.78)', relPath, line, messages.SeverityNiveis.info));
|
|
82
|
+
}
|
|
83
|
+
// detect is_some_and / is_ok_and (stable since Rust 1.70)
|
|
84
|
+
for (const match of scan.matchAll(/\.is_some\(\)\s*&&\s*\w+\s*==/g)) {
|
|
85
|
+
const line = lineOf(match.index);
|
|
86
|
+
ocorrencias.push(warn('.is_some() && predicate pattern; consider .is_some_and(|x| ...) (Rust 1.70+)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
87
|
+
}
|
|
88
|
+
for (const match of scan.matchAll(/\.is_ok\(\)\s*&&\s*\w+\s*==/g)) {
|
|
89
|
+
const line = lineOf(match.index);
|
|
90
|
+
ocorrencias.push(warn('.is_ok() && predicate pattern; consider .is_ok_and(|x| ...) (Rust 1.70+)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
91
|
+
}
|
|
92
|
+
// detect Option::inspect / Result::inspect (stable since Rust 1.76)
|
|
93
|
+
for (const match of scan.matchAll(/\.map\(.*\|x\|.*\{[\s\S]{0,100}println!.*\}\)/g)) {
|
|
94
|
+
const line = lineOf(match.index);
|
|
95
|
+
ocorrencias.push(warn('map() with side-effect closure; consider .inspect() (Rust 1.76+)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
96
|
+
}
|
|
97
|
+
// detect string interpolation for format args (Rust 1.80+)
|
|
98
|
+
for (const match of scan.matchAll(/format!\(["']\{[\w:?]*\}["'],\s*(\w+)\)/g)) {
|
|
99
|
+
const line = lineOf(match.index);
|
|
100
|
+
const varName = match[1];
|
|
101
|
+
if (varName !== match[0].match(/\{(\w+)\}/)?.[1]) {
|
|
102
|
+
// suggest captured identifier format
|
|
103
|
+
ocorrencias.push(warn(`format!("{varName}") could be simplified to format!("{varName}") using captured identifiers (Rust 1.80+)`, relPath, line, messages.SeverityNiveis.info));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
59
106
|
return ocorrencias;
|
|
60
107
|
}
|
|
61
108
|
export const analistaRust = criarAnalista({
|
|
62
109
|
nome: 'analista-rust',
|
|
63
110
|
categoria: 'linguagem',
|
|
64
|
-
descricao: 'Analisa código Rust
|
|
111
|
+
descricao: 'Analisa código Rust: convenções, nomenclatura, padrões modernos (edition 2024).',
|
|
65
112
|
global: false,
|
|
66
113
|
test: (relPath) => isRustFile(relPath),
|
|
67
114
|
aplicar: (src, relPath) => {
|
|
@@ -51,12 +51,40 @@ function collectSecurityIssues(src, relPath) {
|
|
|
51
51
|
const line = lineOf(match.index);
|
|
52
52
|
ocorrencias.push(warn(`Unsafe ${match[1]} declaration; document safety invariants with // SAFETY:`, relPath, line, messages.SeverityNiveis.error));
|
|
53
53
|
}
|
|
54
|
+
// pointer::cast instead of `as` pointer cast (Rust 1.72+)
|
|
55
|
+
for (const match of scan.matchAll(/\b(\w+)\s+as\s+\*const\s+\w+/g)) {
|
|
56
|
+
const line = lineOf(match.index);
|
|
57
|
+
if (!match[0].includes('as *const u8')) {
|
|
58
|
+
ocorrencias.push(warn('Using `as` for pointer cast; consider .pointer::cast::<T>() or .cast() (Rust 1.72+)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Command::new with shell injection risk
|
|
62
|
+
for (const match of scan.matchAll(/Command::new\(["'](sh|bash|cmd|powershell)["']\)/g)) {
|
|
63
|
+
const line = lineOf(match.index);
|
|
64
|
+
const argsMatch = src.slice(match.index, match.index + 200).match(/\.arg\(/);
|
|
65
|
+
if (argsMatch) {
|
|
66
|
+
ocorrencias.push(warn('Shell invocation via Command; ensure no injection via user input', relPath, line, messages.SeverityNiveis.error));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// detect .ok()?.unwrap() antipattern
|
|
70
|
+
for (const match of scan.matchAll(/\.ok\(\)\?\.unwrap\(\)/g)) {
|
|
71
|
+
const line = lineOf(match.index);
|
|
72
|
+
ocorrencias.push(warn('.ok()?.unwrap() antipattern; use ? directly on Result instead', relPath, line, messages.SeverityNiveis.warning));
|
|
73
|
+
}
|
|
74
|
+
// detect splitn / split_once with manual index access instead of destructuring (Rust 1.62+ split_once stable)
|
|
75
|
+
for (const match of scan.matchAll(/\.splitn\(2,\s*/g)) {
|
|
76
|
+
const line = lineOf(match.index);
|
|
77
|
+
const afterSplit = src.slice(match.index, match.index + 100);
|
|
78
|
+
if (afterSplit.includes('[0]') || afterSplit.includes('[1]')) {
|
|
79
|
+
ocorrencias.push(warn('splitn(2, ...) with index access; consider .split_once() for cleaner destructuring (Rust 1.62+)', relPath, line, messages.SeverityNiveis.suggestion));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
54
82
|
return ocorrencias;
|
|
55
83
|
}
|
|
56
84
|
export const analistaRustSeguranca = criarAnalista({
|
|
57
85
|
nome: 'analista-rust-seguranca',
|
|
58
86
|
categoria: 'seguranca',
|
|
59
|
-
descricao: 'Detecta problemas de segurança e unsafe em
|
|
87
|
+
descricao: 'Detecta problemas de segurança e unsafe em Rust (edition 2024).',
|
|
60
88
|
global: false,
|
|
61
89
|
test: (relPath) => /\.(rs)$/i.test(relPath),
|
|
62
90
|
aplicar: (src, relPath) => {
|
|
@@ -166,7 +166,7 @@ export function pontuarTodos(arquivos) {
|
|
|
166
166
|
const resultado = scoreArquetipo(def, arquivos);
|
|
167
167
|
resultados.push(resultado);
|
|
168
168
|
if (resultado.candidatoExtra) {
|
|
169
|
-
const extra = ARQUETIPOS
|
|
169
|
+
const extra = ARQUETIPOS?.find((arquivo) => arquivo.nome === resultado.candidatoExtra);
|
|
170
170
|
if (extra) {
|
|
171
171
|
const extraResultado = scoreArquetipo(extra, arquivos);
|
|
172
172
|
if (!resultados.some(x => x.nome === extraResultado.nome)) {
|
|
@@ -189,9 +189,9 @@ export function scoreArquetipoAvancado(def, arquivos, sinaisAvancados) {
|
|
|
189
189
|
// Clona o resultado base para adicionar análises avançadas
|
|
190
190
|
let score = resultadoBase.score;
|
|
191
191
|
let explicacao = resultadoBase.explicacaoSimilaridade ?? '';
|
|
192
|
-
const frameworks = sinaisAvancados.frameworksDetectados
|
|
193
|
-
const deps = sinaisAvancados.dependencias
|
|
194
|
-
const scripts = sinaisAvancados.scripts
|
|
192
|
+
const frameworks = sinaisAvancados.frameworksDetectados;
|
|
193
|
+
const deps = sinaisAvancados.dependencias;
|
|
194
|
+
const scripts = sinaisAvancados.scripts;
|
|
195
195
|
const detalhes = sinaisAvancados.detalhes ?? {};
|
|
196
196
|
// BONUS INTELIGENTE: Considerar sinais avançados para pontuação contextual
|
|
197
197
|
score += calcularBonusContextual(def, sinaisAvancados);
|
|
@@ -602,7 +602,7 @@ export function pontuarTodosAvancado(arquivos, sinaisAvancados) {
|
|
|
602
602
|
const resultado = scoreArquetipoAvancado(def, arquivos, sinaisAvancados);
|
|
603
603
|
resultados.push(resultado);
|
|
604
604
|
if (resultado.candidatoExtra) {
|
|
605
|
-
const extra = ARQUETIPOS
|
|
605
|
+
const extra = ARQUETIPOS?.find((arquivo) => arquivo.nome === resultado.candidatoExtra);
|
|
606
606
|
if (extra) {
|
|
607
607
|
const extraResultado = scoreArquetipoAvancado(extra, arquivos, sinaisAvancados);
|
|
608
608
|
if (!resultados.some(x => x.nome === extraResultado.nome)) {
|
|
@@ -24,7 +24,7 @@ export const analistaShell = criarAnalista({
|
|
|
24
24
|
sugestao: messages.AnalistaShellMensagens.missingShebangSugestao
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
|
-
// 2. Verificar 'set -
|
|
27
|
+
// 2. Verificar 'set -euo pipefail' (Tratamento de Erros + strict mode)
|
|
28
28
|
const srcCompleto = src.toLowerCase();
|
|
29
29
|
if (!srcCompleto.includes('set -e') && !srcCompleto.includes('set -o pipefail')) {
|
|
30
30
|
ocorrencias.push(criarOcorrencia({
|
|
@@ -36,6 +36,84 @@ export const analistaShell = criarAnalista({
|
|
|
36
36
|
sugestao: messages.AnalistaShellMensagens.missingSetESugestao
|
|
37
37
|
}));
|
|
38
38
|
}
|
|
39
|
+
// 2b. Verificar 'set -u'
|
|
40
|
+
if (!srcCompleto.includes('set -u')) {
|
|
41
|
+
ocorrencias.push(criarOcorrencia({
|
|
42
|
+
tipo: 'codigo-fragil',
|
|
43
|
+
nivel: 'info',
|
|
44
|
+
mensagem: messages.AnalistaShellMensagens.missingSetU,
|
|
45
|
+
relPath,
|
|
46
|
+
linha: 1,
|
|
47
|
+
sugestao: messages.AnalistaShellMensagens.missingSetUSugestao
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
// 2c. Verificar IFS=$'\n\t'
|
|
51
|
+
if (!src.includes('IFS=')) {
|
|
52
|
+
ocorrencias.push(criarOcorrencia({
|
|
53
|
+
tipo: 'codigo-fragil',
|
|
54
|
+
nivel: 'info',
|
|
55
|
+
mensagem: messages.AnalistaShellMensagens.missingIso,
|
|
56
|
+
relPath,
|
|
57
|
+
linha: 1,
|
|
58
|
+
sugestao: messages.AnalistaShellMensagens.missingIsoSugestao
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
// 2d. Verificar trap handler
|
|
62
|
+
if (!src.includes('trap ')) {
|
|
63
|
+
ocorrencias.push(criarOcorrencia({
|
|
64
|
+
tipo: 'boa-pratica-ausente',
|
|
65
|
+
nivel: 'info',
|
|
66
|
+
mensagem: messages.AnalistaShellMensagens.missingTrap,
|
|
67
|
+
relPath,
|
|
68
|
+
linha: 1,
|
|
69
|
+
sugestao: messages.AnalistaShellMensagens.missingTrapSugestao
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
// 2e. Verificar main function pattern
|
|
73
|
+
if (!src.includes('main()') && !src.includes('main ()')) {
|
|
74
|
+
ocorrencias.push(criarOcorrencia({
|
|
75
|
+
tipo: 'boa-pratica-ausente',
|
|
76
|
+
nivel: 'info',
|
|
77
|
+
mensagem: messages.AnalistaShellMensagens.missingMainFn,
|
|
78
|
+
relPath,
|
|
79
|
+
linha: linhas.length,
|
|
80
|
+
sugestao: messages.AnalistaShellMensagens.missingMainFnSugestao
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
// 2f. Verificar command -v usage for hardcoded paths
|
|
84
|
+
if (/(\/usr\/bin\/|\/bin\/|\/usr\/local\/bin\/)/.test(src) && !src.includes('command -v')) {
|
|
85
|
+
ocorrencias.push(criarOcorrencia({
|
|
86
|
+
tipo: 'boa-pratica-ausente',
|
|
87
|
+
nivel: 'info',
|
|
88
|
+
mensagem: messages.AnalistaShellMensagens.missingCommandV,
|
|
89
|
+
relPath,
|
|
90
|
+
linha: 1,
|
|
91
|
+
sugestao: messages.AnalistaShellMensagens.missingCommandVSugestao
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
// 2g. Verificar DEBUG mode
|
|
95
|
+
if (!src.includes('${DEBUG:-}') && !src.includes('$DEBUG')) {
|
|
96
|
+
ocorrencias.push(criarOcorrencia({
|
|
97
|
+
tipo: 'boa-pratica-ausente',
|
|
98
|
+
nivel: 'info',
|
|
99
|
+
mensagem: messages.AnalistaShellMensagens.missingDebugMode,
|
|
100
|
+
relPath,
|
|
101
|
+
linha: 1,
|
|
102
|
+
sugestao: messages.AnalistaShellMensagens.missingDebugModeSugestao
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
// 2h. Verificar input validation
|
|
106
|
+
const hasUsageCheck = /\[\[ -z "\$[0-9*@]/.test(src) || /\[ -z "\$[0-9*@]/.test(src) || src.includes('Usage:') || src.includes('usage:');
|
|
107
|
+
if (!hasUsageCheck && /\$[0-9]/.test(src)) {
|
|
108
|
+
ocorrencias.push(criarOcorrencia({
|
|
109
|
+
tipo: 'boa-pratica-ausente',
|
|
110
|
+
nivel: 'info',
|
|
111
|
+
mensagem: messages.AnalistaShellMensagens.missingInputValidation,
|
|
112
|
+
relPath,
|
|
113
|
+
linha: 1,
|
|
114
|
+
sugestao: messages.AnalistaShellMensagens.missingInputValidationSugestao
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
39
117
|
linhas.forEach((linha, index) => {
|
|
40
118
|
const numeroLinha = index + 1;
|
|
41
119
|
const linhaTrim = linha.trim();
|
|
@@ -55,9 +133,7 @@ export const analistaShell = criarAnalista({
|
|
|
55
133
|
}));
|
|
56
134
|
}
|
|
57
135
|
// 4. Variáveis não aspeadas (Fragilidade)
|
|
58
|
-
// Detecta patterns como $VAR sem aspas (ex: echo $USER, mas ignora em loops/condicionais complexas)
|
|
59
136
|
if (/\$(\w+)\b/.test(linha) && !/["'].*\$\w+.*["']/.test(linha) && !/\$\(.*\)/.test(linha) && !/^\s*(for|while|if|case|read)\s+/.test(linhaTrim)) {
|
|
60
|
-
// Heurística simples: se a variável aparece sozinha ou em comando comum
|
|
61
137
|
if (/\b(echo|ls|cd|rm|cp|mv)\s+.*\$\w+/.test(linhaTrim)) {
|
|
62
138
|
ocorrencias.push(criarOcorrencia({
|
|
63
139
|
tipo: 'codigo-fragil',
|
|
@@ -73,7 +149,7 @@ export const analistaShell = criarAnalista({
|
|
|
73
149
|
if (/\brm\s+-rf\s+\/(\s|$|['"`])/.test(linha)) {
|
|
74
150
|
ocorrencias.push(criarOcorrencia({
|
|
75
151
|
tipo: 'vulnerabilidade-seguranca',
|
|
76
|
-
nivel: '
|
|
152
|
+
nivel: 'erro',
|
|
77
153
|
mensagem: messages.AnalistaShellMensagens.rmRfRoot,
|
|
78
154
|
relPath,
|
|
79
155
|
linha: numeroLinha,
|
|
@@ -84,7 +160,7 @@ export const analistaShell = criarAnalista({
|
|
|
84
160
|
if (/(curl|wget).*(|\s)sh(\s|$)/.test(linha)) {
|
|
85
161
|
ocorrencias.push(criarOcorrencia({
|
|
86
162
|
tipo: 'vulnerabilidade-seguranca',
|
|
87
|
-
nivel: '
|
|
163
|
+
nivel: 'erro',
|
|
88
164
|
mensagem: messages.AnalistaShellMensagens.curlPipeSh,
|
|
89
165
|
relPath,
|
|
90
166
|
linha: numeroLinha,
|
|
@@ -25,12 +25,45 @@ export function detectarCorrecoesShell(src, _relPath) {
|
|
|
25
25
|
if (!/set\s+(-e|-o\s+pipefail)/.test(src)) {
|
|
26
26
|
correcoes.push({
|
|
27
27
|
tipo: 'shell-add-set-e',
|
|
28
|
-
original: 'Sem set -
|
|
28
|
+
original: 'Sem set -euo pipefail',
|
|
29
29
|
sugerido: 'set -euo pipefail',
|
|
30
30
|
linha: 2,
|
|
31
31
|
confianca: 90
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
// Add set -u if missing
|
|
35
|
+
if (!/set\s+-u/.test(src)) {
|
|
36
|
+
const linhaSetE = src.match(/set\s+(-e\b)/);
|
|
37
|
+
const sugestao = linhaSetE ? 'set -euo pipefail' : 'set -u';
|
|
38
|
+
const linha = linhaSetE ? 2 : 1;
|
|
39
|
+
correcoes.push({
|
|
40
|
+
tipo: 'shell-add-set-u',
|
|
41
|
+
original: 'Sem set -u',
|
|
42
|
+
sugerido: sugestao,
|
|
43
|
+
linha,
|
|
44
|
+
confianca: 85
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Add IFS if missing
|
|
48
|
+
if (!/IFS=/.test(src)) {
|
|
49
|
+
correcoes.push({
|
|
50
|
+
tipo: 'shell-add-ifs',
|
|
51
|
+
original: 'Sem IFS',
|
|
52
|
+
sugerido: "IFS=$'\\n\\t'",
|
|
53
|
+
linha: 3,
|
|
54
|
+
confianca: 80
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Add trap if missing
|
|
58
|
+
if (!/\btrap\s+/.test(src) && linhas.length > 5) {
|
|
59
|
+
correcoes.push({
|
|
60
|
+
tipo: 'shell-add-trap',
|
|
61
|
+
original: 'Sem trap handler',
|
|
62
|
+
sugerido: 'trap "cleanup" EXIT INT TERM',
|
|
63
|
+
linha: linhas.length + 1,
|
|
64
|
+
confianca: 75
|
|
65
|
+
});
|
|
66
|
+
}
|
|
34
67
|
linhas.forEach((linha, index) => {
|
|
35
68
|
const numeroLinha = index + 1;
|
|
36
69
|
if (/\b(echo|ls|cd|rm|cp|mv)\s+.*\$\w+/.test(linha) && !/["'].*\$\w+.*["']/.test(linha)) {
|
|
@@ -57,6 +57,94 @@ export const detectorShellSecurity = {
|
|
|
57
57
|
sugestao: messages.DetectorShellMensagens.unsafePipefailSugestao
|
|
58
58
|
}));
|
|
59
59
|
}
|
|
60
|
+
// set -u (treat unset variables as error)
|
|
61
|
+
if (!/set\s+-u/.test(src)) {
|
|
62
|
+
ocorrencias.push(criarOcorrencia({
|
|
63
|
+
tipo: 'shell-missing-set-u',
|
|
64
|
+
nivel: 'info',
|
|
65
|
+
mensagem: messages.DetectorShellMensagens.missingSetU,
|
|
66
|
+
relPath,
|
|
67
|
+
linha: 1,
|
|
68
|
+
sugestao: messages.DetectorShellMensagens.missingSetUSugestao
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
// IFS=$'\n\t'
|
|
72
|
+
if (!/IFS=/.test(src)) {
|
|
73
|
+
ocorrencias.push(criarOcorrencia({
|
|
74
|
+
tipo: 'shell-missing-ifs',
|
|
75
|
+
nivel: 'info',
|
|
76
|
+
mensagem: messages.DetectorShellMensagens.missingIso,
|
|
77
|
+
relPath,
|
|
78
|
+
linha: 1,
|
|
79
|
+
sugestao: messages.DetectorShellMensagens.missingIsoSugestao
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
// Trap handler
|
|
83
|
+
if (!/\btrap\s+/.test(src)) {
|
|
84
|
+
ocorrencias.push(criarOcorrencia({
|
|
85
|
+
tipo: 'shell-missing-trap',
|
|
86
|
+
nivel: 'info',
|
|
87
|
+
mensagem: messages.DetectorShellMensagens.missingTrap,
|
|
88
|
+
relPath,
|
|
89
|
+
linha: 1,
|
|
90
|
+
sugestao: messages.DetectorShellMensagens.missingTrapSugestao
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
// Main function pattern
|
|
94
|
+
if (!/main\s*\(\s*\)/.test(src) && !/^main\s*\(/m.test(src)) {
|
|
95
|
+
ocorrencias.push(criarOcorrencia({
|
|
96
|
+
tipo: 'shell-missing-main-fn',
|
|
97
|
+
nivel: 'info',
|
|
98
|
+
mensagem: messages.DetectorShellMensagens.missingMainFn,
|
|
99
|
+
relPath,
|
|
100
|
+
linha: linhas.length,
|
|
101
|
+
sugestao: messages.DetectorShellMensagens.missingMainFnSugestao
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
// Hardcoded paths without command -v
|
|
105
|
+
if (/(\/usr\/bin\/|\/bin\/|\/usr\/local\/bin\/)/.test(src) && !/command\s+-v/.test(src)) {
|
|
106
|
+
ocorrencias.push(criarOcorrencia({
|
|
107
|
+
tipo: 'shell-missing-command-v',
|
|
108
|
+
nivel: 'info',
|
|
109
|
+
mensagem: messages.DetectorShellMensagens.missingCommandV,
|
|
110
|
+
relPath,
|
|
111
|
+
linha: 1,
|
|
112
|
+
sugestao: messages.DetectorShellMensagens.missingCommandVSugestao
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
// DEBUG mode
|
|
116
|
+
if (!/\$\{DEBUG[^}]*\}/.test(src) && !/\$DEBUG\b/.test(src)) {
|
|
117
|
+
ocorrencias.push(criarOcorrencia({
|
|
118
|
+
tipo: 'shell-missing-debug-mode',
|
|
119
|
+
nivel: 'info',
|
|
120
|
+
mensagem: messages.DetectorShellMensagens.missingDebugMode,
|
|
121
|
+
relPath,
|
|
122
|
+
linha: 1,
|
|
123
|
+
sugestao: messages.DetectorShellMensagens.missingDebugModeSugestao
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
// Input validation
|
|
127
|
+
if (/\$[0-9@*]/.test(src) && !/\[\[ -z "\$[0-9@*]/.test(src) && !/\[ -z "\$[0-9@*]/.test(src)) {
|
|
128
|
+
ocorrencias.push(criarOcorrencia({
|
|
129
|
+
tipo: 'shell-missing-input-validation',
|
|
130
|
+
nivel: 'info',
|
|
131
|
+
mensagem: messages.DetectorShellMensagens.missingInputValidation,
|
|
132
|
+
relPath,
|
|
133
|
+
linha: 1,
|
|
134
|
+
sugestao: messages.DetectorShellMensagens.missingInputValidationSugestao
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
// ShellCheck recommendation (only if script is non-trivial)
|
|
138
|
+
if (linhas.length > 10 && !src.includes('shellcheck')) {
|
|
139
|
+
ocorrencias.push(criarOcorrencia({
|
|
140
|
+
tipo: 'shellcheck-recommended',
|
|
141
|
+
nivel: 'info',
|
|
142
|
+
mensagem: messages.DetectorShellMensagens.shellCheckRecommended,
|
|
143
|
+
relPath,
|
|
144
|
+
linha: 1,
|
|
145
|
+
sugestao: messages.DetectorShellMensagens.shellCheckRecommendedSugestao
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
60
148
|
const isSh = /\/bin\/sh(\s|$)/.test(shebang);
|
|
61
149
|
linhas.forEach((linha, index) => {
|
|
62
150
|
const numeroLinha = index + 1;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { splitLines } from '../../../shared/helpers/index.js';
|
|
13
13
|
export function pontuarShell(src, _relPath) {
|
|
14
14
|
let score = 0;
|
|
15
|
-
const maxScore =
|
|
15
|
+
const maxScore = 280;
|
|
16
16
|
const issues = [];
|
|
17
17
|
const strengths = [];
|
|
18
18
|
const linhas = splitLines(src);
|
|
@@ -33,6 +33,30 @@ export function pontuarShell(src, _relPath) {
|
|
|
33
33
|
else {
|
|
34
34
|
issues.push('Sem set -e / pipefail');
|
|
35
35
|
}
|
|
36
|
+
// set -u (10 pontos)
|
|
37
|
+
if (/set\s+-u/.test(srcCompleto)) {
|
|
38
|
+
score += 10;
|
|
39
|
+
strengths.push('Strict mode (set -u)');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
issues.push('Sem set -u');
|
|
43
|
+
}
|
|
44
|
+
// IFS (5 pontos)
|
|
45
|
+
if (/IFS=/.test(srcCompleto)) {
|
|
46
|
+
score += 5;
|
|
47
|
+
strengths.push('IFS configurado');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
issues.push('Sem IFS');
|
|
51
|
+
}
|
|
52
|
+
// trap handler (10 pontos)
|
|
53
|
+
if (/\btrap\s+/.test(srcCompleto)) {
|
|
54
|
+
score += 10;
|
|
55
|
+
strengths.push('Cleanup handler presente');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
issues.push('Sem trap handler');
|
|
59
|
+
}
|
|
36
60
|
// No eval (15 pontos)
|
|
37
61
|
if (!/\beval\s+/.test(srcCompleto)) {
|
|
38
62
|
score += 15;
|
|
@@ -218,6 +242,39 @@ export function pontuarShell(src, _relPath) {
|
|
|
218
242
|
else {
|
|
219
243
|
issues.push('sudo detectado');
|
|
220
244
|
}
|
|
245
|
+
// ── Modern Bash (2026) ──────────────────────────────────────────
|
|
246
|
+
// main function pattern (5 pontos)
|
|
247
|
+
if (/main\s*\(\s*\)/.test(srcCompleto)) {
|
|
248
|
+
score += 5;
|
|
249
|
+
strengths.push('Função main presente');
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
issues.push('Sem padrão de função main');
|
|
253
|
+
}
|
|
254
|
+
// command -v usage (5 pontos)
|
|
255
|
+
if (hardPaths === 0 || /command\s+-v/.test(srcCompleto)) {
|
|
256
|
+
score += 5;
|
|
257
|
+
strengths.push('Uso de command -v');
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
issues.push('Caminhos fixos sem command -v');
|
|
261
|
+
}
|
|
262
|
+
// DEBUG mode (5 pontos)
|
|
263
|
+
if (/\$\{DEBUG[^}]*\}/.test(srcCompleto) || /\$DEBUG\b/.test(srcCompleto)) {
|
|
264
|
+
score += 5;
|
|
265
|
+
strengths.push('Modo debug presente');
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
issues.push('Sem modo debug');
|
|
269
|
+
}
|
|
270
|
+
// Input validation (5 pontos)
|
|
271
|
+
if (/\[\[ -z "\$[0-9@*]/.test(srcCompleto) || /\[ -z "\$[0-9@*]/.test(srcCompleto)) {
|
|
272
|
+
score += 5;
|
|
273
|
+
strengths.push('Validação de input');
|
|
274
|
+
}
|
|
275
|
+
else if (/\$[0-9]/.test(srcCompleto)) {
|
|
276
|
+
issues.push('Sem validação de argumentos');
|
|
277
|
+
}
|
|
221
278
|
return {
|
|
222
279
|
score,
|
|
223
280
|
maxScore,
|
|
@@ -55,12 +55,10 @@ export const OperarioEstrutura = {
|
|
|
55
55
|
} : {})
|
|
56
56
|
}, sinaisAvancados // Passar sinais avançados para inteligência contextual
|
|
57
57
|
);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
63
|
-
}
|
|
58
|
+
return {
|
|
59
|
+
plano: planoAlt,
|
|
60
|
+
origem: 'estrategista'
|
|
61
|
+
};
|
|
64
62
|
}
|
|
65
63
|
catch (erro) {
|
|
66
64
|
log.aviso(MENSAGENS_ARQUETIPOS_HANDLER.falhaEstrategista);
|
|
@@ -109,7 +107,7 @@ export const OperarioEstrutura = {
|
|
|
109
107
|
return mapa;
|
|
110
108
|
}
|
|
111
109
|
for (const occ of ocorrencias) {
|
|
112
|
-
const rel = occ.relPath
|
|
110
|
+
const rel = occ.relPath;
|
|
113
111
|
mapa.push({
|
|
114
112
|
arquivo: rel,
|
|
115
113
|
ideal: null,
|
|
@@ -29,7 +29,7 @@ export const analistaSugestoesContextuais = {
|
|
|
29
29
|
// Obter package.json se disponível
|
|
30
30
|
let packageJson;
|
|
31
31
|
try {
|
|
32
|
-
const pkgArquivo = contexto.arquivos
|
|
32
|
+
const pkgArquivo = contexto.arquivos?.find(arquivo => arquivo.relPath === 'package.json');
|
|
33
33
|
if (pkgArquivo?.content) {
|
|
34
34
|
packageJson = JSON.parse(pkgArquivo.content);
|
|
35
35
|
}
|
|
@@ -112,9 +112,12 @@ export const analistaSugestoesContextuais = {
|
|
|
112
112
|
case 'estrutura':
|
|
113
113
|
mensagem = messages.SugestoesContextuaisMensagens.evidenciaEstrutura(evidencia.valor, melhorResultado.tecnologia);
|
|
114
114
|
break;
|
|
115
|
+
case 'export':
|
|
116
|
+
case 'config':
|
|
117
|
+
case 'script':
|
|
118
|
+
continue;
|
|
115
119
|
default:
|
|
116
120
|
continue;
|
|
117
|
-
// Pular outros tipos menos relevantes
|
|
118
121
|
}
|
|
119
122
|
ocorrencias.push({
|
|
120
123
|
tipo: 'evidencia_contextual',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
import { messages } from '../../../core/messages/index.js';
|
|
2
3
|
import { otimizarSvgLikeSvgo, shouldSugerirOtimizacaoSvg } from '../../../shared/formatters/index.js';
|
|
3
4
|
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
|