@mocoto/mahoraga 0.14.2 → 0.14.4
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 +2 -32
- package/dist/analysts/architects/analyst-structure.js +19 -19
- package/dist/analysts/architects/archetypes-custom.js +8 -6
- package/dist/analysts/architects/detector-archetypes.js +30 -27
- package/dist/analysts/architects/signals-project-advanced.js +24 -26
- package/dist/analysts/architects/signals-project.js +1 -1
- package/dist/analysts/architects/strategist-structure.js +47 -66
- package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +21 -14
- 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 -37
- package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
- package/dist/analysts/circleci/analysts/analyst-circleci.js +23 -15
- 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 +20 -17
- package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
- package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
- package/dist/analysts/corrections/analyst-scoring.js +24 -21
- package/dist/analysts/corrections/corrector-structure.js +35 -35
- 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 +54 -51
- 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 +10 -10
- 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-validator.js +2 -1
- package/dist/analysts/corrections/type-safety/usage-analyzer.js +36 -39
- package/dist/analysts/css/analysts/analyst-css.js +16 -15
- 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 +20 -15
- 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 +89 -83
- package/dist/analysts/detectors/detector-bugs-ml.js +21 -19
- package/dist/analysts/detectors/detector-code-fragile.js +30 -26
- package/dist/analysts/detectors/detector-duplications.js +21 -18
- package/dist/analysts/detectors/detector-monorepo.js +41 -36
- package/dist/analysts/detectors/detector-performance.js +22 -16
- package/dist/analysts/detectors/detector-recommendation-architecture.js +39 -35
- package/dist/analysts/detectors/detector-security.js +43 -42
- package/dist/analysts/detectors/detector-structure.js +29 -26
- package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +4 -1
- package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +10 -16
- package/dist/analysts/plugins/detector-documentation.js +1 -0
- package/dist/analysts/react/analysts/analyst-react-hooks.js +4 -2
- package/dist/analysts/react/analysts/analyst-react.js +1 -1
- package/dist/analysts/react/corrections/correction-react.js +2 -2
- package/dist/analysts/react/detectors/detector-react-best-practices.js +1 -1
- package/dist/analysts/registry/autodiscovery.js +2 -2
- package/dist/analysts/tailwind/corrections/correction-tailwind.js +0 -8
- package/dist/analysts/tailwind/scorers/scorer-tailwind.js +1 -2
- package/dist/cli/commands/command-analysts.js +3 -2
- package/dist/cli/commands/command-formatters.js +6 -5
- package/dist/cli/commands/command-github-actions.js +1 -1
- package/dist/cli/commands/command-guardian.js +1 -1
- package/dist/cli/commands/command-marketplace.js +4 -4
- package/dist/cli/diagnostic/filters.js +9 -9
- package/dist/core/config/chalk-safe.js +4 -4
- package/dist/core/config/config.js +5 -5
- package/dist/core/execution/analysis-cache.js +1 -1
- package/dist/core/execution/ast-cache.js +1 -3
- package/dist/core/execution/executor.js +2 -4
- package/dist/core/execution/scanner.js +1 -1
- 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/ja/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
- 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/zh/analysts/report-circleci-messages.js +7 -7
- package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
- package/dist/core/parsing/parser.js +2 -3
- package/dist/core/parsing/utils.js +1 -0
- package/dist/core/workers/worker-pool.js +0 -0
- package/dist/guardian/records.js +1 -0
- package/dist/guardian/sentinel.js +1 -0
- package/dist/guardian/watcher-hidden.js +1 -0
- package/dist/licenses/fs-utils.js +2 -1
- package/dist/licenses/header-options.js +1 -0
- package/dist/licenses/index.js +1 -1
- package/dist/reports/generator-report.js +3 -3
- package/dist/reports/report-patterns-usage.js +1 -1
- package/dist/shared/data-processing/fragment-report.js +1 -1
- package/dist/shared/formatters/engines/stylelint.js +40 -34
- package/dist/shared/formatters/formatters/markdown.js +1 -1
- package/dist/shared/helpers/framework-detector.js +5 -1
- package/dist/shared/persistence/persistence.js +13 -5
- package/dist/shared/plugins/core-plugin.js +3 -1
- package/dist/types/analysts/tailwind-correction.js +2 -0
- package/dist/types/index.js +3 -3
- package/dist/types/processing/filters.js +5 -4
- package/dist/vulnerabilities/npm-audit.js +1 -0
- package/dist/vulnerabilities/scanner.js +1 -1
- package/dist/vulnerabilities/vulnerabilities.js +1 -0
- package/package.json +5 -10
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
import { parseDocument } from 'yaml';
|
|
3
3
|
import { messages } from '../../../core/messages/index.js';
|
|
4
4
|
import { criarOcorrencia } from '../../../types/index.js';
|
|
5
|
+
function processarJobsAzure(jobs, relPath, ocorrencias) {
|
|
6
|
+
const jobNames = jobs.map(j => j.job ?? '').filter(Boolean);
|
|
7
|
+
for (const job of jobs) {
|
|
8
|
+
if (job.dependsOn && !jobNames.includes(job.dependsOn)) {
|
|
9
|
+
ocorrencias.push(criarOcorrencia({
|
|
10
|
+
tipo: 'azure-job-orfao',
|
|
11
|
+
nivel: 'erro',
|
|
12
|
+
mensagem: `Job '${job.job ?? job.displayName}' depende de '${job.dependsOn}' que não existe`,
|
|
13
|
+
relPath, linha: 1,
|
|
14
|
+
sugestao: 'Verifique se o nome do job no dependsOn está correto',
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (jobs.length > 3 && jobs.every(j => !j.dependsOn || jobNames.indexOf(j.dependsOn) === jobNames.indexOf(j.job ?? '') - 1)) {
|
|
19
|
+
const hasParallel = jobs.some(j => !j.dependsOn && j !== jobs[0]);
|
|
20
|
+
if (!hasParallel) {
|
|
21
|
+
ocorrencias.push(criarOcorrencia({
|
|
22
|
+
tipo: 'azure-sem-paralelismo',
|
|
23
|
+
nivel: 'info',
|
|
24
|
+
mensagem: 'Pipeline com jobs estritamente sequenciais',
|
|
25
|
+
relPath, linha: 1,
|
|
26
|
+
sugestao: 'Considere paralelizar jobs independentes',
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const job of jobs) {
|
|
31
|
+
if (job.job && !job.displayName) {
|
|
32
|
+
ocorrencias.push(criarOcorrencia({
|
|
33
|
+
tipo: 'azure-missing-job-displayname',
|
|
34
|
+
nivel: 'info',
|
|
35
|
+
mensagem: messages.RelatorioAzureMensagens.missingJobDisplayName,
|
|
36
|
+
relPath,
|
|
37
|
+
linha: 1,
|
|
38
|
+
sugestao: messages.RelatorioAzureMensagens.missingJobDisplayNameSugestao,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
5
43
|
export const detectorAzureEstrutura = {
|
|
6
44
|
nome: 'detector-azure-estrutura',
|
|
7
45
|
categoria: 'workflows',
|
|
@@ -17,43 +55,7 @@ export const detectorAzureEstrutura = {
|
|
|
17
55
|
const pipeline = doc.toJS();
|
|
18
56
|
const jobs = pipeline.jobs;
|
|
19
57
|
if (jobs && Array.isArray(jobs)) {
|
|
20
|
-
|
|
21
|
-
for (const job of jobs) {
|
|
22
|
-
if (job.dependsOn && !jobNames.includes(job.dependsOn)) {
|
|
23
|
-
ocorrencias.push(criarOcorrencia({
|
|
24
|
-
tipo: 'azure-job-orfao',
|
|
25
|
-
nivel: 'erro',
|
|
26
|
-
mensagem: `Job '${job.job ?? job.displayName}' depende de '${job.dependsOn}' que não existe`,
|
|
27
|
-
relPath, linha: 1,
|
|
28
|
-
sugestao: 'Verifique se o nome do job no dependsOn está correto',
|
|
29
|
-
}));
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (jobs.length > 3 && jobs.every(j => !j.dependsOn || jobNames.indexOf(j.dependsOn) === jobNames.indexOf(j.job ?? '') - 1)) {
|
|
33
|
-
const hasParallel = jobs.some(j => !j.dependsOn && j !== jobs[0]);
|
|
34
|
-
if (!hasParallel) {
|
|
35
|
-
ocorrencias.push(criarOcorrencia({
|
|
36
|
-
tipo: 'azure-sem-paralelismo',
|
|
37
|
-
nivel: 'info',
|
|
38
|
-
mensagem: 'Pipeline com jobs estritamente sequenciais',
|
|
39
|
-
relPath, linha: 1,
|
|
40
|
-
sugestao: 'Considere paralelizar jobs independentes',
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Display name conventions
|
|
45
|
-
for (const job of jobs) {
|
|
46
|
-
if (job.job && !job.displayName) {
|
|
47
|
-
ocorrencias.push(criarOcorrencia({
|
|
48
|
-
tipo: 'azure-missing-job-displayname',
|
|
49
|
-
nivel: 'info',
|
|
50
|
-
mensagem: messages.RelatorioAzureMensagens.missingJobDisplayName,
|
|
51
|
-
relPath,
|
|
52
|
-
linha: 1,
|
|
53
|
-
sugestao: messages.RelatorioAzureMensagens.missingJobDisplayNameSugestao,
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
58
|
+
processarJobsAzure(jobs, relPath, ocorrencias);
|
|
57
59
|
}
|
|
58
60
|
const container = pipeline;
|
|
59
61
|
const containers = container.resources?.containers;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import { messages } from '../../../core/messages/index.js';
|
|
3
3
|
import { splitLines } from '../../../shared/helpers/index.js';
|
|
4
|
+
function detectarVulnerableTask(srcCompleto) {
|
|
5
|
+
return Object.keys({
|
|
6
|
+
'Npm@1': 'Npm@1',
|
|
7
|
+
'NuGetCommand@2': 'NuGetCommand@2',
|
|
8
|
+
'DotNetCoreCLI@1': 'DotNetCoreCLI@1',
|
|
9
|
+
'VSBuild@1': 'VSBuild@1',
|
|
10
|
+
'VsTest@1': 'VsTest@1',
|
|
11
|
+
}).some(task => new RegExp(`task:\\s*${task.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(srcCompleto));
|
|
12
|
+
}
|
|
4
13
|
export function pontuarAzure(src, _relPath) {
|
|
5
14
|
let score = 0;
|
|
6
15
|
const maxScore = 100;
|
|
@@ -16,13 +25,7 @@ export function pontuarAzure(src, _relPath) {
|
|
|
16
25
|
else {
|
|
17
26
|
issues.push(messages.RelatorioAzureMensagens.vulnerableTask);
|
|
18
27
|
}
|
|
19
|
-
const isVulnerableTask =
|
|
20
|
-
'Npm@1': 'Npm@1',
|
|
21
|
-
'NuGetCommand@2': 'NuGetCommand@2',
|
|
22
|
-
'DotNetCoreCLI@1': 'DotNetCoreCLI@1',
|
|
23
|
-
'VSBuild@1': 'VSBuild@1',
|
|
24
|
-
'VsTest@1': 'VsTest@1',
|
|
25
|
-
}).some(task => new RegExp(`task:\\s*${task.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(srcCompleto));
|
|
28
|
+
const isVulnerableTask = detectarVulnerableTask(srcCompleto);
|
|
26
29
|
if (!isVulnerableTask) {
|
|
27
30
|
score += 5;
|
|
28
31
|
strengths.push('Tasks com versões seguras');
|
|
@@ -3,6 +3,23 @@ import { parseDocument } from 'yaml';
|
|
|
3
3
|
import { detectorCircleCIPerformance } from '../detectors/detector-circleci-performance.js';
|
|
4
4
|
import { detectorCircleCISecurity, obterDetectoresCircleCI } from '../detectors/detector-circleci-security.js';
|
|
5
5
|
import { detectorCircleCIEstrutura } from '../detectors/detector-circleci-structure.js';
|
|
6
|
+
function processarJobsCircleCI(jobs, relPath, ocorrencias) {
|
|
7
|
+
if (!jobs) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
for (const [jobName, job] of Object.entries(jobs)) {
|
|
11
|
+
if (job.steps && job.steps.length > 20) {
|
|
12
|
+
ocorrencias.push({
|
|
13
|
+
tipo: 'CIRCLECI_JOB_TOO_MANY_STEPS',
|
|
14
|
+
mensagem: `Job '${jobName}' possui muitos steps (${job.steps.length})`,
|
|
15
|
+
relPath,
|
|
16
|
+
linha: 1,
|
|
17
|
+
nivel: 'info',
|
|
18
|
+
sugestao: 'Agrupe steps repetitivos em "commands" para maior legibilidade'
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
6
23
|
export const analistaCircleCI = {
|
|
7
24
|
nome: 'circleci',
|
|
8
25
|
categoria: 'workflows',
|
|
@@ -14,7 +31,11 @@ export const analistaCircleCI = {
|
|
|
14
31
|
const securityResults = await Promise.resolve(detectorCircleCISecurity.aplicar(conteudo, relPath, null, _fc, contexto));
|
|
15
32
|
const performanceResults = await Promise.resolve(detectorCircleCIPerformance.aplicar(conteudo, relPath, null, _fc, contexto));
|
|
16
33
|
const structureResults = await Promise.resolve(detectorCircleCIEstrutura.aplicar(conteudo, relPath, null, _fc, contexto));
|
|
17
|
-
|
|
34
|
+
for (const r of [securityResults, performanceResults, structureResults]) {
|
|
35
|
+
if (r) {
|
|
36
|
+
ocorrencias.push(...(Array.isArray(r) ? r : [r]));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
// Run registered plugins
|
|
19
40
|
const caminhos = contexto?.arquivos.map(arquivo => arquivo.relPath) ?? [];
|
|
20
41
|
const plugResults = await Promise.all(obterDetectoresCircleCI().map(d => Promise.resolve(d.testar({ content: conteudo }, { conteudo, caminhos })).catch(() => [])));
|
|
@@ -43,20 +64,7 @@ export const analistaCircleCI = {
|
|
|
43
64
|
sugestao: 'Atualize para a versão 2.1 para usar Orbs, Commands e outras melhorias modernas'
|
|
44
65
|
});
|
|
45
66
|
}
|
|
46
|
-
|
|
47
|
-
for (const [jobName, job] of Object.entries(configData.jobs)) {
|
|
48
|
-
if (job.steps && job.steps.length > 20) {
|
|
49
|
-
ocorrencias.push({
|
|
50
|
-
tipo: 'CIRCLECI_JOB_TOO_MANY_STEPS',
|
|
51
|
-
mensagem: `Job '${jobName}' possui muitos steps (${job.steps.length})`,
|
|
52
|
-
relPath,
|
|
53
|
-
linha: 1,
|
|
54
|
-
nivel: 'info',
|
|
55
|
-
sugestao: 'Agrupe steps repetitivos em "commands" para maior legibilidade'
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
67
|
+
processarJobsCircleCI(configData.jobs, relPath, ocorrencias);
|
|
60
68
|
}
|
|
61
69
|
catch {
|
|
62
70
|
// Falha silenciosa no parse
|
|
@@ -2,30 +2,32 @@
|
|
|
2
2
|
import { messages } from '../../../core/messages/index.js';
|
|
3
3
|
import { splitLines } from '../../../shared/helpers/index.js';
|
|
4
4
|
import { criarOcorrencia } from '../../../types/index.js';
|
|
5
|
+
function processarLinhaCircleCI(linha, numeroLinha, correcoes) {
|
|
6
|
+
if (/\bimage:\s*[^\s@]+:latest\b/.test(linha)) {
|
|
7
|
+
correcoes.push({
|
|
8
|
+
tipo: 'circleci-fix-latest-image',
|
|
9
|
+
original: linha,
|
|
10
|
+
sugerido: linha.replace(/:latest\b/, ':3.18'),
|
|
11
|
+
linha: numeroLinha,
|
|
12
|
+
confianca: 90
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
|
|
16
|
+
const replaced = linha.replace(/['"][^'"]+['"]/, '"$VAR_NAME"');
|
|
17
|
+
correcoes.push({
|
|
18
|
+
tipo: 'circleci-use-context',
|
|
19
|
+
original: linha,
|
|
20
|
+
sugerido: replaced,
|
|
21
|
+
linha: numeroLinha,
|
|
22
|
+
confianca: 90
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
5
26
|
export function detectarCorrecoesCircleCI(src, _relPath) {
|
|
6
27
|
const correcoes = [];
|
|
7
28
|
const linhas = splitLines(src);
|
|
8
29
|
linhas.forEach((linha, index) => {
|
|
9
|
-
|
|
10
|
-
if (/\bimage:\s*[^\s@]+:latest\b/.test(linha)) {
|
|
11
|
-
correcoes.push({
|
|
12
|
-
tipo: 'circleci-fix-latest-image',
|
|
13
|
-
original: linha,
|
|
14
|
-
sugerido: linha.replace(/:latest\b/, ':3.18'),
|
|
15
|
-
linha: numeroLinha,
|
|
16
|
-
confianca: 90
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
|
|
20
|
-
const replaced = linha.replace(/['"][^'"]+['"]/, '"$VAR_NAME"');
|
|
21
|
-
correcoes.push({
|
|
22
|
-
tipo: 'circleci-use-context',
|
|
23
|
-
original: linha,
|
|
24
|
-
sugerido: replaced,
|
|
25
|
-
linha: numeroLinha,
|
|
26
|
-
confianca: 90
|
|
27
|
-
});
|
|
28
|
-
}
|
|
30
|
+
processarLinhaCircleCI(linha, index + 1, correcoes);
|
|
29
31
|
});
|
|
30
32
|
if (!/save_cache:/.test(src) && !/restore_cache:/.test(src)) {
|
|
31
33
|
const firstJobLine = linhas.findIndex(l => /steps:/.test(l));
|
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
import { messages } from '../../../core/messages/index.js';
|
|
3
3
|
import { criarOcorrencia } from '../../../types/index.js';
|
|
4
4
|
const MSG = messages.RelatorioCircleciMensagens;
|
|
5
|
+
function analisarJobsSequenciais(src, relPath, ocorrencias) {
|
|
6
|
+
const jobSections = src.split(/^jobs:/m);
|
|
7
|
+
if (jobSections.length <= 1) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
const jobsBlock = jobSections[1].split(/^workflows:|^orbs:|^commands:|^executors:/m)[0];
|
|
11
|
+
const jobNames = jobsBlock.match(/^\s{2}\w[^:]+:/gm);
|
|
12
|
+
const jobCountEstimate = jobNames ? jobNames.length : 0;
|
|
13
|
+
if (jobCountEstimate > 2) {
|
|
14
|
+
const needsCount = (jobsBlock.match(/needs:/g) ?? []).length;
|
|
15
|
+
if (needsCount >= jobCountEstimate - 1) {
|
|
16
|
+
ocorrencias.push(criarOcorrencia({
|
|
17
|
+
tipo: 'circleci-sequential-test-jobs',
|
|
18
|
+
nivel: 'info',
|
|
19
|
+
mensagem: typeof MSG.sequentialTestJobs === 'function' ? (MSG.sequentialTestJobs)() : MSG.sequentialTestJobs,
|
|
20
|
+
relPath, linha: 1,
|
|
21
|
+
sugestao: typeof MSG.sequentialTestJobsSugestao === 'function' ? (MSG.sequentialTestJobsSugestao)() : MSG.sequentialTestJobsSugestao
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return jobCountEstimate;
|
|
26
|
+
}
|
|
5
27
|
export const detectorCircleCIPerformance = {
|
|
6
28
|
nome: 'detector-circleci-performance',
|
|
7
29
|
categoria: 'performance',
|
|
@@ -73,25 +95,7 @@ export const detectorCircleCIPerformance = {
|
|
|
73
95
|
}));
|
|
74
96
|
}
|
|
75
97
|
// 6. Sequential test jobs analysis
|
|
76
|
-
|
|
77
|
-
const jobSections = src.split(/^jobs:/m);
|
|
78
|
-
if (jobSections.length > 1) {
|
|
79
|
-
const jobsBlock = jobSections[1].split(/^workflows:|^orbs:|^commands:|^executors:/m)[0];
|
|
80
|
-
const jobNames = jobsBlock.match(/^\s{2}\w[^:]+:/gm);
|
|
81
|
-
jobCountEstimate = jobNames ? jobNames.length : 0;
|
|
82
|
-
if (jobCountEstimate > 2) {
|
|
83
|
-
const needsCount = (jobsBlock.match(/needs:/g) ?? []).length;
|
|
84
|
-
if (needsCount >= jobCountEstimate - 1) {
|
|
85
|
-
ocorrencias.push(criarOcorrencia({
|
|
86
|
-
tipo: 'circleci-sequential-test-jobs',
|
|
87
|
-
nivel: 'info',
|
|
88
|
-
mensagem: typeof MSG.sequentialTestJobs === 'function' ? (MSG.sequentialTestJobs)() : MSG.sequentialTestJobs,
|
|
89
|
-
relPath, linha: 1,
|
|
90
|
-
sugestao: typeof MSG.sequentialTestJobsSugestao === 'function' ? (MSG.sequentialTestJobsSugestao)() : MSG.sequentialTestJobsSugestao
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
98
|
+
const jobCountEstimate = analisarJobsSequenciais(src, relPath, ocorrencias);
|
|
95
99
|
// 7. Single executor type for all jobs
|
|
96
100
|
const executorTypes = new Set();
|
|
97
101
|
const machineMatch = src.match(/machine:/g);
|
|
@@ -13,6 +13,83 @@ function _isProblemaWorkflowArray(val) {
|
|
|
13
13
|
return Array.isArray(val);
|
|
14
14
|
}
|
|
15
15
|
const MSG = messages.RelatorioCircleciMensagens;
|
|
16
|
+
function processarLinhaSegurancaCircleCI(linha, index, linhas, relPath, ocorrencias) {
|
|
17
|
+
const numeroLinha = index + 1;
|
|
18
|
+
if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
|
|
19
|
+
ocorrencias.push(criarOcorrencia({
|
|
20
|
+
tipo: 'circleci-hardcoded-secret',
|
|
21
|
+
nivel: 'erro',
|
|
22
|
+
mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
|
|
23
|
+
relPath,
|
|
24
|
+
linha: numeroLinha,
|
|
25
|
+
sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
if (/\bimage:\s*[^\s@]+:latest\b/.test(linha) || (/\bimage:\s*[^\s@:]+$/.test(linha.trim()) && linha.includes('image:'))) {
|
|
29
|
+
ocorrencias.push(criarOcorrencia({
|
|
30
|
+
tipo: 'circleci-image-latest',
|
|
31
|
+
nivel: 'aviso',
|
|
32
|
+
mensagem: 'Uso de imagem :latest ou sem tag específica',
|
|
33
|
+
relPath,
|
|
34
|
+
linha: numeroLinha,
|
|
35
|
+
sugestao: 'Use um digest (SHA) ou uma versão fixa para garantir segurança e reprodutibilidade'
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
if (/resource_class\s*:\s*(medium|small)\s*$/.test(linha)) {
|
|
39
|
+
ocorrencias.push(criarOcorrencia({
|
|
40
|
+
tipo: 'circleci-default-resource-class',
|
|
41
|
+
nivel: 'info',
|
|
42
|
+
mensagem: typeof MSG.missingResourceClass === 'function' ? (MSG.missingResourceClass)() : MSG.missingResourceClass,
|
|
43
|
+
relPath,
|
|
44
|
+
linha: numeroLinha,
|
|
45
|
+
sugestao: typeof MSG.missingResourceClassSugestao === 'function' ? (MSG.missingResourceClassSugestao)() : MSG.missingResourceClassSugestao
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
if (/context\s*:/.test(linha)) {
|
|
49
|
+
const nextLines = linhas.slice(index + 1, index + 5);
|
|
50
|
+
const hasBranchFilter = nextLines.some(l => /branches\s*:/.test(l));
|
|
51
|
+
if (!hasBranchFilter) {
|
|
52
|
+
ocorrencias.push(criarOcorrencia({
|
|
53
|
+
tipo: 'circleci-context-no-branch-restriction',
|
|
54
|
+
nivel: 'aviso',
|
|
55
|
+
mensagem: typeof MSG.unsafeContextUsage === 'function' ? (MSG.unsafeContextUsage)() : MSG.unsafeContextUsage,
|
|
56
|
+
relPath,
|
|
57
|
+
linha: numeroLinha,
|
|
58
|
+
sugestao: typeof MSG.unsafeContextUsageSugestao === 'function' ? (MSG.unsafeContextUsageSugestao)() : MSG.unsafeContextUsageSugestao
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (/fingerprint\s*:\s*['"][^'"]+['"]/.test(linha) || /\bkey\s*:\s*['"][A-Za-z0-9+/]{20,}['"]/.test(linha)) {
|
|
63
|
+
ocorrencias.push(criarOcorrencia({
|
|
64
|
+
tipo: 'circleci-hardcoded-fingerprint',
|
|
65
|
+
nivel: 'erro',
|
|
66
|
+
mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
|
|
67
|
+
relPath,
|
|
68
|
+
linha: numeroLinha,
|
|
69
|
+
sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
if (/checkout\s*:/.test(linha) && /path\s*:/.test(linha)) {
|
|
73
|
+
ocorrencias.push(criarOcorrencia({
|
|
74
|
+
tipo: 'circleci-custom-checkout-path',
|
|
75
|
+
nivel: 'info',
|
|
76
|
+
mensagem: typeof MSG.dangerousCheckoutPath === 'function' ? (MSG.dangerousCheckoutPath)() : MSG.dangerousCheckoutPath,
|
|
77
|
+
relPath,
|
|
78
|
+
linha: numeroLinha,
|
|
79
|
+
sugestao: typeof MSG.dangerousCheckoutPathSugestao === 'function' ? (MSG.dangerousCheckoutPathSugestao)() : MSG.dangerousCheckoutPathSugestao
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
if (/\bsudo\s+/.test(linha)) {
|
|
83
|
+
ocorrencias.push(criarOcorrencia({
|
|
84
|
+
tipo: 'circleci-sudo-usage',
|
|
85
|
+
nivel: 'aviso',
|
|
86
|
+
mensagem: typeof MSG.sudoWithoutPasswordless === 'function' ? (MSG.sudoWithoutPasswordless)() : MSG.sudoWithoutPasswordless,
|
|
87
|
+
relPath,
|
|
88
|
+
linha: numeroLinha,
|
|
89
|
+
sugestao: typeof MSG.sudoWithoutPasswordlessSugestao === 'function' ? (MSG.sudoWithoutPasswordlessSugestao)() : MSG.sudoWithoutPasswordlessSugestao
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
16
93
|
export const detectorCircleCISecurity = {
|
|
17
94
|
nome: 'detector-circleci-security',
|
|
18
95
|
categoria: 'security',
|
|
@@ -25,88 +102,7 @@ export const detectorCircleCISecurity = {
|
|
|
25
102
|
const ocorrencias = [];
|
|
26
103
|
const linhas = splitLines(src);
|
|
27
104
|
linhas.forEach((linha, index) => {
|
|
28
|
-
|
|
29
|
-
// 1. Secrets hardcoded em environment
|
|
30
|
-
if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
|
|
31
|
-
ocorrencias.push(criarOcorrencia({
|
|
32
|
-
tipo: 'circleci-hardcoded-secret',
|
|
33
|
-
nivel: 'erro',
|
|
34
|
-
mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
|
|
35
|
-
relPath,
|
|
36
|
-
linha: numeroLinha,
|
|
37
|
-
sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
// 2. Uso de imagem :latest
|
|
41
|
-
if (/\bimage:\s*[^\s@]+:latest\b/.test(linha) || (/\bimage:\s*[^\s@:]+$/.test(linha.trim()) && linha.includes('image:'))) {
|
|
42
|
-
ocorrencias.push(criarOcorrencia({
|
|
43
|
-
tipo: 'circleci-image-latest',
|
|
44
|
-
nivel: 'aviso',
|
|
45
|
-
mensagem: 'Uso de imagem :latest ou sem tag específica',
|
|
46
|
-
relPath,
|
|
47
|
-
linha: numeroLinha,
|
|
48
|
-
sugestao: 'Use um digest (SHA) ou uma versão fixa para garantir segurança e reprodutibilidade'
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
// 3. Resource class analysis
|
|
52
|
-
if (/resource_class\s*:\s*(medium|small)\s*$/.test(linha)) {
|
|
53
|
-
ocorrencias.push(criarOcorrencia({
|
|
54
|
-
tipo: 'circleci-default-resource-class',
|
|
55
|
-
nivel: 'info',
|
|
56
|
-
mensagem: typeof MSG.missingResourceClass === 'function' ? (MSG.missingResourceClass)() : MSG.missingResourceClass,
|
|
57
|
-
relPath,
|
|
58
|
-
linha: numeroLinha,
|
|
59
|
-
sugestao: typeof MSG.missingResourceClassSugestao === 'function' ? (MSG.missingResourceClassSugestao)() : MSG.missingResourceClassSugestao
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
62
|
-
// 4. Context usage without branch restriction
|
|
63
|
-
if (/context\s*:/.test(linha)) {
|
|
64
|
-
const nextLines = linhas.slice(index + 1, index + 5);
|
|
65
|
-
const hasBranchFilter = nextLines.some(l => /branches\s*:/.test(l));
|
|
66
|
-
if (!hasBranchFilter) {
|
|
67
|
-
ocorrencias.push(criarOcorrencia({
|
|
68
|
-
tipo: 'circleci-context-no-branch-restriction',
|
|
69
|
-
nivel: 'aviso',
|
|
70
|
-
mensagem: typeof MSG.unsafeContextUsage === 'function' ? (MSG.unsafeContextUsage)() : MSG.unsafeContextUsage,
|
|
71
|
-
relPath,
|
|
72
|
-
linha: numeroLinha,
|
|
73
|
-
sugestao: typeof MSG.unsafeContextUsageSugestao === 'function' ? (MSG.unsafeContextUsageSugestao)() : MSG.unsafeContextUsageSugestao
|
|
74
|
-
}));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// 5. Hardcoded key/fingerprint
|
|
78
|
-
if (/fingerprint\s*:\s*['"][^'"]+['"]/.test(linha) || /\bkey\s*:\s*['"][A-Za-z0-9+/]{20,}['"]/.test(linha)) {
|
|
79
|
-
ocorrencias.push(criarOcorrencia({
|
|
80
|
-
tipo: 'circleci-hardcoded-fingerprint',
|
|
81
|
-
nivel: 'erro',
|
|
82
|
-
mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
|
|
83
|
-
relPath,
|
|
84
|
-
linha: numeroLinha,
|
|
85
|
-
sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
|
|
86
|
-
}));
|
|
87
|
-
}
|
|
88
|
-
// 6. Dangerous checkout path
|
|
89
|
-
if (/checkout\s*:/.test(linha) && /path\s*:/.test(linha)) {
|
|
90
|
-
ocorrencias.push(criarOcorrencia({
|
|
91
|
-
tipo: 'circleci-custom-checkout-path',
|
|
92
|
-
nivel: 'info',
|
|
93
|
-
mensagem: typeof MSG.dangerousCheckoutPath === 'function' ? (MSG.dangerousCheckoutPath)() : MSG.dangerousCheckoutPath,
|
|
94
|
-
relPath,
|
|
95
|
-
linha: numeroLinha,
|
|
96
|
-
sugestao: typeof MSG.dangerousCheckoutPathSugestao === 'function' ? (MSG.dangerousCheckoutPathSugestao)() : MSG.dangerousCheckoutPathSugestao
|
|
97
|
-
}));
|
|
98
|
-
}
|
|
99
|
-
// 7. Sudo usage
|
|
100
|
-
if (/\bsudo\s+/.test(linha)) {
|
|
101
|
-
ocorrencias.push(criarOcorrencia({
|
|
102
|
-
tipo: 'circleci-sudo-usage',
|
|
103
|
-
nivel: 'aviso',
|
|
104
|
-
mensagem: typeof MSG.sudoWithoutPasswordless === 'function' ? (MSG.sudoWithoutPasswordless)() : MSG.sudoWithoutPasswordless,
|
|
105
|
-
relPath,
|
|
106
|
-
linha: numeroLinha,
|
|
107
|
-
sugestao: typeof MSG.sudoWithoutPasswordlessSugestao === 'function' ? (MSG.sudoWithoutPasswordlessSugestao)() : MSG.sudoWithoutPasswordlessSugestao
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
105
|
+
processarLinhaSegurancaCircleCI(linha, index, linhas, relPath, ocorrencias);
|
|
110
106
|
});
|
|
111
107
|
return ocorrencias;
|
|
112
108
|
}
|
|
@@ -127,23 +127,7 @@ export const detectorCircleCIEstrutura = {
|
|
|
127
127
|
}
|
|
128
128
|
// Orphaned job detection
|
|
129
129
|
if (jobs.jobs && workflows) {
|
|
130
|
-
const
|
|
131
|
-
const usedJobs = new Set();
|
|
132
|
-
for (const wf of wfValues) {
|
|
133
|
-
if (typeof wf === 'object') {
|
|
134
|
-
const wfJobs = wf.jobs;
|
|
135
|
-
if (Array.isArray(wfJobs)) {
|
|
136
|
-
for (const wfJob of wfJobs) {
|
|
137
|
-
if (typeof wfJob === 'string') {
|
|
138
|
-
usedJobs.add(wfJob);
|
|
139
|
-
}
|
|
140
|
-
else if (typeof wfJob === 'object') {
|
|
141
|
-
Object.keys(wfJob).forEach(k => usedJobs.add(k));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
130
|
+
const usedJobs = collectUsedJobsFromWorkflows(Object.values(workflows));
|
|
147
131
|
for (const jn of Object.keys(jobs.jobs)) {
|
|
148
132
|
if (!usedJobs.has(jn)) {
|
|
149
133
|
ocorrencias.push(criarOcorrencia({
|
|
@@ -217,3 +201,22 @@ export const detectorCircleCIEstrutura = {
|
|
|
217
201
|
return ocorrencias;
|
|
218
202
|
}
|
|
219
203
|
};
|
|
204
|
+
function collectUsedJobsFromWorkflows(wfValues) {
|
|
205
|
+
const usedJobs = new Set();
|
|
206
|
+
for (const wf of wfValues) {
|
|
207
|
+
if (typeof wf === 'object' && wf !== null) {
|
|
208
|
+
const wfJobs = wf.jobs;
|
|
209
|
+
if (Array.isArray(wfJobs)) {
|
|
210
|
+
for (const wfJob of wfJobs) {
|
|
211
|
+
if (typeof wfJob === 'string') {
|
|
212
|
+
usedJobs.add(wfJob);
|
|
213
|
+
}
|
|
214
|
+
else if (typeof wfJob === 'object' && wfJob !== null) {
|
|
215
|
+
Object.keys(wfJob).forEach(k => usedJobs.add(k));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return usedJobs;
|
|
222
|
+
}
|
|
@@ -10,6 +10,7 @@ export class ConversorGhaToGitlab extends ConversorBase {
|
|
|
10
10
|
de = 'github-actions';
|
|
11
11
|
para = 'gitlab-ci';
|
|
12
12
|
async converter(conteudo) {
|
|
13
|
+
await Promise.resolve();
|
|
13
14
|
const avisos = [];
|
|
14
15
|
const erros = [];
|
|
15
16
|
try {
|
|
@@ -49,21 +50,7 @@ export class ConversorGhaToGitlab extends ConversorBase {
|
|
|
49
50
|
if (job.steps && Array.isArray(job.steps)) {
|
|
50
51
|
const steps = job.steps;
|
|
51
52
|
for (const step of steps) {
|
|
52
|
-
|
|
53
|
-
// Limpar comandos de run (remover multi-line pipe se necessário)
|
|
54
|
-
const cmd = step.run.trim();
|
|
55
|
-
gitlabJob.script.push(cmd);
|
|
56
|
-
}
|
|
57
|
-
else if (step.uses) {
|
|
58
|
-
// Tratamento especial para checkout
|
|
59
|
-
if (step.uses.includes('actions/checkout')) {
|
|
60
|
-
gitlabJob.script.push('# Checkout automático pelo GitLab Runner');
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
avisos.push(`Job '${jobId}': Action '${step.uses}' detectada. Considere usar um componente equivalente do GitLab ou um script customizado.`);
|
|
64
|
-
gitlabJob.script.push(`# TODO: Implementar lógica da action: ${step.uses}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
53
|
+
processWorkflowStep(step, jobId, gitlabJob, avisos);
|
|
67
54
|
}
|
|
68
55
|
}
|
|
69
56
|
// Mapear dependências (needs)
|
|
@@ -85,3 +72,18 @@ export class ConversorGhaToGitlab extends ConversorBase {
|
|
|
85
72
|
}
|
|
86
73
|
}
|
|
87
74
|
}
|
|
75
|
+
function processWorkflowStep(step, jobId, gitlabJob, avisos) {
|
|
76
|
+
if (step.run) {
|
|
77
|
+
const cmd = step.run.trim();
|
|
78
|
+
gitlabJob.script.push(cmd);
|
|
79
|
+
}
|
|
80
|
+
else if (step.uses) {
|
|
81
|
+
if (step.uses.includes('actions/checkout')) {
|
|
82
|
+
gitlabJob.script.push('# Checkout automático pelo GitLab Runner');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
avisos.push(`Job '${jobId}': Action '${step.uses}' detectada. Considere usar um componente equivalente do GitLab ou um script customizado.`);
|
|
86
|
+
gitlabJob.script.push(`# TODO: Implementar lógica da action: ${step.uses}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -24,39 +24,36 @@ export const analistaQuickFixes = {
|
|
|
24
24
|
const uniqueFixes = quickFixes.filter((fix, index, arr) => arr.findIndex(arquivo => arquivo.id === fix.id) === index);
|
|
25
25
|
for (const fixResultado of uniqueFixes) {
|
|
26
26
|
for (const match of fixResultado.matches) {
|
|
27
|
-
|
|
28
|
-
const beforeMatch = src.substring(0, match.index ?? 0);
|
|
29
|
-
const linha = beforeMatch.split('\n').length;
|
|
30
|
-
// Gerar preview da correção
|
|
31
|
-
const previewCorrecao = fixResultado.fix(match, src);
|
|
32
|
-
const originalLine = src.split('\n')[linha - 1] || '';
|
|
33
|
-
const fixedLine = previewCorrecao.split('\n')[linha - 1] || '';
|
|
34
|
-
// Criar sugestão com preview mais detalhado
|
|
35
|
-
const sugestao = [fixResultado.description, '', `[CHAVE-INGLESA] Correção sugerida:`, `[ERRO] Antes: ${originalLine.trim()}`, `[OK] Depois: ${fixedLine.trim()}`, '', `Confiança: ${fixResultado.confidence}%`, `Categoria: ${fixResultado.category}`, `ID do Fix: ${fixResultado.id}`].join('\n');
|
|
36
|
-
// Mapear categoria para nível
|
|
37
|
-
const nivel = mapearCategoriaNivel(fixResultado.category);
|
|
38
|
-
// Criar ocorrência base
|
|
39
|
-
const ocorrencia = criarOcorrencia({
|
|
40
|
-
tipo: 'auto-fix-disponivel',
|
|
41
|
-
nivel,
|
|
42
|
-
mensagem: fixResultado.title,
|
|
43
|
-
relPath,
|
|
44
|
-
linha
|
|
45
|
-
});
|
|
46
|
-
// Adicionar campos extras como propriedades do objeto genérico
|
|
47
|
-
const ocorrenciaGenerica = ocorrencia; // OcorrenciaGenerica allows extra properties
|
|
48
|
-
ocorrenciaGenerica.sugestao = sugestao;
|
|
49
|
-
ocorrenciaGenerica.quickFixId = fixResultado.id;
|
|
50
|
-
ocorrenciaGenerica.confidence = fixResultado.confidence;
|
|
51
|
-
ocorrenciaGenerica.category = fixResultado.category;
|
|
52
|
-
ocorrenciaGenerica.matchIndex = match.index;
|
|
53
|
-
ocorrenciaGenerica.matchLength = match[0].length;
|
|
54
|
-
ocorrencias.push(ocorrencia);
|
|
27
|
+
processQuickFixMatch(match, fixResultado, src, relPath, ocorrencias);
|
|
55
28
|
}
|
|
56
29
|
}
|
|
57
30
|
return ocorrencias;
|
|
58
31
|
}
|
|
59
32
|
};
|
|
33
|
+
function processQuickFixMatch(match, fixResultado, src, relPath, ocorrencias) {
|
|
34
|
+
const beforeMatch = src.substring(0, match.index ?? 0);
|
|
35
|
+
const linha = beforeMatch.split('\n').length;
|
|
36
|
+
const previewCorrecao = fixResultado.fix(match, src);
|
|
37
|
+
const originalLine = src.split('\n')[linha - 1] || '';
|
|
38
|
+
const fixedLine = previewCorrecao.split('\n')[linha - 1] || '';
|
|
39
|
+
const sugestao = [fixResultado.description, '', `[CHAVE-INGLESA] Correção sugerida:`, `[ERRO] Antes: ${originalLine.trim()}`, `[OK] Depois: ${fixedLine.trim()}`, '', `Confiança: ${fixResultado.confidence}%`, `Categoria: ${fixResultado.category}`, `ID do Fix: ${fixResultado.id}`].join('\n');
|
|
40
|
+
const nivel = mapearCategoriaNivel(fixResultado.category);
|
|
41
|
+
const ocorrencia = criarOcorrencia({
|
|
42
|
+
tipo: 'auto-fix-disponivel',
|
|
43
|
+
nivel,
|
|
44
|
+
mensagem: fixResultado.title,
|
|
45
|
+
relPath,
|
|
46
|
+
linha
|
|
47
|
+
});
|
|
48
|
+
const ocorrenciaGenerica = ocorrencia;
|
|
49
|
+
ocorrenciaGenerica.sugestao = sugestao;
|
|
50
|
+
ocorrenciaGenerica.quickFixId = fixResultado.id;
|
|
51
|
+
ocorrenciaGenerica.confidence = fixResultado.confidence;
|
|
52
|
+
ocorrenciaGenerica.category = fixResultado.category;
|
|
53
|
+
ocorrenciaGenerica.matchIndex = match.index;
|
|
54
|
+
ocorrenciaGenerica.matchLength = match[0].length;
|
|
55
|
+
ocorrencias.push(ocorrencia);
|
|
56
|
+
}
|
|
60
57
|
function mapearCategoriaNivel(category) {
|
|
61
58
|
switch (category) {
|
|
62
59
|
case 'security':
|