@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
|
@@ -104,6 +104,35 @@ const visitorFragil = {
|
|
|
104
104
|
visitorState.resultados.set('codigo-fragil', fragilidades);
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
|
+
function converterFragilidadesParaOcorrencias(fragilidades, relPath) {
|
|
108
|
+
const ocorrencias = [];
|
|
109
|
+
const porSeveridade = agruparPor(fragilidades, arquivo => arquivo.severidade ?? 'media');
|
|
110
|
+
for (const [severidade, items] of Object.entries(porSeveridade)) {
|
|
111
|
+
if (items.length === 0) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const nivel = severidade === 'critica' || severidade === 'alta' ? 'erro' : severidade === 'media' ? 'aviso' : 'info';
|
|
115
|
+
const tipos = {};
|
|
116
|
+
for (const it of items) {
|
|
117
|
+
tipos[it.tipo] = (tipos[it.tipo] || 0) + 1;
|
|
118
|
+
}
|
|
119
|
+
const resumo = items.slice(0, 3).map(caminho => caminho.tipo).join(', ');
|
|
120
|
+
const amostra = items.slice(0, 5).map(it => `${it.tipo} (L${it.linha})`);
|
|
121
|
+
ocorrencias.push(criarOcorrencia({
|
|
122
|
+
tipo: 'codigo-fragil',
|
|
123
|
+
nivel,
|
|
124
|
+
mensagem: messages.DetectorCodigoFragilMensagens.fragilidadesResumo(severidade, resumo, {
|
|
125
|
+
severidade,
|
|
126
|
+
total: items.length,
|
|
127
|
+
tipos,
|
|
128
|
+
amostra
|
|
129
|
+
}),
|
|
130
|
+
relPath,
|
|
131
|
+
linha: items[0].linha
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
return ocorrencias;
|
|
135
|
+
}
|
|
107
136
|
export const analistaCodigoFragil = {
|
|
108
137
|
nome: 'codigo-fragil',
|
|
109
138
|
categoria: 'qualidade',
|
|
@@ -170,32 +199,7 @@ export const analistaCodigoFragil = {
|
|
|
170
199
|
}
|
|
171
200
|
// Detecções de texto complementares (Heurísticas)
|
|
172
201
|
detectarProblemasAvancados(src, fragilidades);
|
|
173
|
-
|
|
174
|
-
const ocorrencias = [];
|
|
175
|
-
const porSeveridade = agruparPor(fragilidades, arquivo => arquivo.severidade ?? 'media');
|
|
176
|
-
for (const [severidade, items] of Object.entries(porSeveridade)) {
|
|
177
|
-
if (items.length > 0) {
|
|
178
|
-
const nivel = severidade === 'critica' || severidade === 'alta' ? 'erro' : severidade === 'media' ? 'aviso' : 'info';
|
|
179
|
-
const resumo = items.slice(0, 3).map(caminho => caminho.tipo).join(', ');
|
|
180
|
-
const tipos = {};
|
|
181
|
-
for (const it of items) {
|
|
182
|
-
tipos[it.tipo] = (tipos[it.tipo] || 0) + 1;
|
|
183
|
-
}
|
|
184
|
-
const amostra = items.slice(0, 5).map(it => `${it.tipo} (L${it.linha})`);
|
|
185
|
-
ocorrencias.push(criarOcorrencia({
|
|
186
|
-
tipo: 'codigo-fragil',
|
|
187
|
-
nivel,
|
|
188
|
-
mensagem: messages.DetectorCodigoFragilMensagens.fragilidadesResumo(severidade, resumo, {
|
|
189
|
-
severidade,
|
|
190
|
-
total: items.length,
|
|
191
|
-
tipos,
|
|
192
|
-
amostra
|
|
193
|
-
}),
|
|
194
|
-
relPath,
|
|
195
|
-
linha: items[0].linha
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
202
|
+
const ocorrencias = converterFragilidadesParaOcorrencias(fragilidades, relPath);
|
|
199
203
|
return filtrarOcorrenciasSuprimidas(ocorrencias, 'codigo-fragil', src);
|
|
200
204
|
}
|
|
201
205
|
};
|
|
@@ -40,24 +40,7 @@ export const analistaDuplicacoes = {
|
|
|
40
40
|
if (duplicacoes.length === 0) {
|
|
41
41
|
return [];
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
const porTipo = agruparPorTipoSimilaridade(duplicacoes);
|
|
45
|
-
const ocorrencias = [];
|
|
46
|
-
for (const [tipo, dups] of Object.entries(porTipo)) {
|
|
47
|
-
if (dups.length > 0) {
|
|
48
|
-
const nivel = tipo === 'identica' ? 'aviso' : 'info';
|
|
49
|
-
const primeiraFunc = dups[0]?.funcaoA;
|
|
50
|
-
const resumo = dups.map(diretorio => `${diretorio.funcaoA?.nome ?? 'unknown'} ≈ ${diretorio.funcaoB?.nome ?? 'unknown'} (${diretorio.similaridade.toFixed(0)}%)`).slice(0, 3).join(', ');
|
|
51
|
-
ocorrencias.push(criarOcorrencia({
|
|
52
|
-
tipo: 'codigo_duplicado',
|
|
53
|
-
nivel,
|
|
54
|
-
mensagem: messages.DetectorAgregadosMensagens.duplicacoesResumo(tipo, resumo, dups.length),
|
|
55
|
-
relPath,
|
|
56
|
-
linha: primeiraFunc?.inicio ?? 1
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return ocorrencias;
|
|
43
|
+
return agruparEConverterDuplicacoes(duplicacoes, relPath);
|
|
61
44
|
}
|
|
62
45
|
catch (erro) {
|
|
63
46
|
return [criarOcorrencia({
|
|
@@ -70,6 +53,26 @@ export const analistaDuplicacoes = {
|
|
|
70
53
|
}
|
|
71
54
|
}
|
|
72
55
|
};
|
|
56
|
+
function agruparEConverterDuplicacoes(duplicacoes, relPath) {
|
|
57
|
+
const porTipo = agruparPorTipoSimilaridade(duplicacoes);
|
|
58
|
+
const ocorrencias = [];
|
|
59
|
+
for (const [tipo, dups] of Object.entries(porTipo)) {
|
|
60
|
+
if (dups.length === 0) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const nivel = tipo === 'identica' ? 'aviso' : 'info';
|
|
64
|
+
const primeiraFunc = dups[0]?.funcaoA;
|
|
65
|
+
const resumo = dups.map(diretorio => `${diretorio.funcaoA?.nome ?? 'unknown'} ≈ ${diretorio.funcaoB?.nome ?? 'unknown'} (${diretorio.similaridade.toFixed(0)}%)`).slice(0, 3).join(', ');
|
|
66
|
+
ocorrencias.push(criarOcorrencia({
|
|
67
|
+
tipo: 'codigo_duplicado',
|
|
68
|
+
nivel,
|
|
69
|
+
mensagem: messages.DetectorAgregadosMensagens.duplicacoesResumo(tipo, resumo, dups.length),
|
|
70
|
+
relPath,
|
|
71
|
+
linha: primeiraFunc?.inicio ?? 1
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return ocorrencias;
|
|
75
|
+
}
|
|
73
76
|
function extrairFuncoes(ast, caminho, src) {
|
|
74
77
|
const funcoes = [];
|
|
75
78
|
const linhas = src.split('\n');
|
|
@@ -14,7 +14,7 @@ export function detectarMonorepo(baseDir) {
|
|
|
14
14
|
return info;
|
|
15
15
|
}
|
|
16
16
|
// Detect package manager
|
|
17
|
-
const pm = rootPkg.packageManager
|
|
17
|
+
const pm = rootPkg.packageManager ?? '';
|
|
18
18
|
if (pm) {
|
|
19
19
|
info.packageManager = pm;
|
|
20
20
|
}
|
|
@@ -237,47 +237,52 @@ export const detectorMonorepo = {
|
|
|
237
237
|
versionMap.set(dep, verMap);
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
240
|
+
detectarVersionMismatch(versionMap, ocorrencias);
|
|
241
|
+
detectarDependenciasInternasFaltando(info, ocorrencias);
|
|
242
|
+
return ocorrencias;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
function detectarVersionMismatch(versionMap, ocorrencias) {
|
|
246
|
+
for (const [dep, versions] of versionMap) {
|
|
247
|
+
if (versions.size > 1) {
|
|
248
|
+
const uniqueVersions = new Set(versions.values());
|
|
249
|
+
if (uniqueVersions.size > 1) {
|
|
250
|
+
ocorrencias.push({
|
|
251
|
+
tipo: 'monorepo-versao-mismatch',
|
|
252
|
+
nivel: 'aviso',
|
|
253
|
+
mensagem: `Dependência ${dep} com versões diferentes entre workspaces: ${Array.from(versions.entries()).map(([w, v]) => `${w}@${v}`).join(', ')}`,
|
|
254
|
+
origem: 'detector-monorepo',
|
|
255
|
+
relPath: '[monorepo]',
|
|
256
|
+
linha: 0,
|
|
257
|
+
});
|
|
253
258
|
}
|
|
254
259
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function detectarDependenciasInternasFaltando(info, ocorrencias) {
|
|
263
|
+
const workspaceNames = new Set(info.workspaces.map(w => w.name));
|
|
264
|
+
for (const ws of info.workspaces) {
|
|
265
|
+
const imports = coletarImports(ws.dir);
|
|
266
|
+
for (const imp of imports) {
|
|
267
|
+
for (const wsName of workspaceNames) {
|
|
268
|
+
if (imp.includes(wsName) && wsName !== ws.name) {
|
|
269
|
+
const isDep = ws.dependencies[wsName] || ws.devDependencies[wsName] || ws.peerDependencies[wsName];
|
|
270
|
+
if (!isDep) {
|
|
271
|
+
ocorrencias.push({
|
|
272
|
+
tipo: 'monorepo-dependencia-interna-faltando',
|
|
273
|
+
nivel: 'aviso',
|
|
274
|
+
mensagem: `${ws.name} importa ${wsName} mas não o declara como dependência`,
|
|
275
|
+
origem: 'detector-monorepo',
|
|
276
|
+
relPath: `[${ws.name}]`,
|
|
277
|
+
linha: 0,
|
|
278
|
+
});
|
|
274
279
|
}
|
|
280
|
+
break;
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
281
286
|
function coletarImports(dir) {
|
|
282
287
|
const imports = [];
|
|
283
288
|
try {
|
|
@@ -169,6 +169,27 @@ function detectarPadroesPerformance(src, problemas, relPath) {
|
|
|
169
169
|
}
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
|
+
function detectarTimerSemCleanup(path, ast, problemas, relPath) {
|
|
173
|
+
if (path.node.callee.type !== 'Identifier') {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (path.node.callee.name !== 'setTimeout' && path.node.callee.name !== 'setInterval') {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const parentFunction = path.getFunctionParent();
|
|
180
|
+
const context = parentFunction ? parentFunction.toString() : ast.toString();
|
|
181
|
+
if (context.includes('clear') || relPath.includes('test')) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
problemas.push({
|
|
185
|
+
tipo: 'memory-leak',
|
|
186
|
+
descricao: `${path.node.callee.name} sem cleanup pode causar vazamento`,
|
|
187
|
+
impacto: 'medio',
|
|
188
|
+
linha: path.node.loc?.start.line ?? 0,
|
|
189
|
+
coluna: path.node.loc?.start.column ?? 0,
|
|
190
|
+
sugestao: `Use clear${path.node.callee.name.replace('set', '')} para limpar`
|
|
191
|
+
});
|
|
192
|
+
}
|
|
172
193
|
function detectarProblemasPerformanceAST(ast, problemas, relPath) {
|
|
173
194
|
try {
|
|
174
195
|
const isLoop = (type) => ['ForStatement', 'WhileStatement', 'DoWhileStatement', 'ForInStatement', 'ForOfStatement'].includes(type);
|
|
@@ -195,23 +216,8 @@ function detectarProblemasPerformanceAST(ast, problemas, relPath) {
|
|
|
195
216
|
DoWhileStatement: checkNesting,
|
|
196
217
|
ForInStatement: checkNesting,
|
|
197
218
|
ForOfStatement: checkNesting,
|
|
198
|
-
// Detectar função setTimeout/setInterval sem cleanup
|
|
199
219
|
CallExpression(path) {
|
|
200
|
-
|
|
201
|
-
// Heurística simples: se não há clear* na mesma função/arquivo
|
|
202
|
-
const parentFunction = path.getFunctionParent();
|
|
203
|
-
const context = parentFunction ? parentFunction.toString() : ast.toString();
|
|
204
|
-
if (!context.includes('clear') && !relPath.includes('test')) {
|
|
205
|
-
problemas.push({
|
|
206
|
-
tipo: 'memory-leak',
|
|
207
|
-
descricao: `${path.node.callee.name} sem cleanup pode causar vazamento`,
|
|
208
|
-
impacto: 'medio',
|
|
209
|
-
linha: path.node.loc?.start.line ?? 0,
|
|
210
|
-
coluna: path.node.loc?.start.column ?? 0,
|
|
211
|
-
sugestao: `Use clear${path.node.callee.name.replace('set', '')} para limpar`
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
220
|
+
detectarTimerSemCleanup(path, ast, problemas, relPath);
|
|
215
221
|
}
|
|
216
222
|
});
|
|
217
223
|
}
|
|
@@ -3,6 +3,43 @@ import { criarOcorrencia } from '../../types/index.js';
|
|
|
3
3
|
* Analista global que analisa o projeto completo e recomenda
|
|
4
4
|
* a melhor arquitetura (Clean Architecture, Microsserviços, MVC, etc.)
|
|
5
5
|
*/
|
|
6
|
+
const CONFIANCA_SIMPLES = 60;
|
|
7
|
+
function adicionarOcorrenciasRecomendacao(ocorrencias, _relPath, sinaisCleanArch, sinaisDistributed, recomendacoes, arquiteturaRecomendada, confianca) {
|
|
8
|
+
ocorrencias.push(criarOcorrencia({
|
|
9
|
+
tipo: 'recomendacao-arquitetura',
|
|
10
|
+
nivel: 'info',
|
|
11
|
+
relPath: _relPath,
|
|
12
|
+
linha: 1,
|
|
13
|
+
mensagem: `[TARGET] Arquitetura recomendada: ${arquiteturaRecomendada} (${confianca}% confiança)`
|
|
14
|
+
}));
|
|
15
|
+
if (sinaisCleanArch.length > 0) {
|
|
16
|
+
ocorrencias.push(criarOcorrencia({
|
|
17
|
+
tipo: 'sinais-clean-arch',
|
|
18
|
+
nivel: 'info',
|
|
19
|
+
relPath: _relPath,
|
|
20
|
+
linha: 1,
|
|
21
|
+
mensagem: `Sinais de Clean Architecture: ${sinaisCleanArch.join(', ')}`
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
if (sinaisDistributed.length > 0) {
|
|
25
|
+
ocorrencias.push(criarOcorrencia({
|
|
26
|
+
tipo: 'sinais-distributed-arch',
|
|
27
|
+
nivel: 'info',
|
|
28
|
+
relPath: _relPath,
|
|
29
|
+
linha: 1,
|
|
30
|
+
mensagem: `Sinais de Arquitetura Distribuída: ${sinaisDistributed.join(', ')}`
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
for (const rec of recomendacoes.slice(0, 5)) {
|
|
34
|
+
ocorrencias.push(criarOcorrencia({
|
|
35
|
+
tipo: 'recomendacao-arquitetura-item',
|
|
36
|
+
nivel: 'info',
|
|
37
|
+
relPath: _relPath,
|
|
38
|
+
linha: 1,
|
|
39
|
+
mensagem: `[IDEA] ${rec}`
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
6
43
|
export const analistaRecomendacaoArquitetura = {
|
|
7
44
|
nome: 'recomendacao-arquitetura',
|
|
8
45
|
categoria: 'arquitetura',
|
|
@@ -221,7 +258,7 @@ export const analistaRecomendacaoArquitetura = {
|
|
|
221
258
|
if (melhorScore === 0) {
|
|
222
259
|
if (complexidadeBaixa) {
|
|
223
260
|
arquiteturaRecomendada = 'Arquitetura Simples / Monolítica';
|
|
224
|
-
confianca =
|
|
261
|
+
confianca = CONFIANCA_SIMPLES;
|
|
225
262
|
recomendacoes.push('Projeto de baixa complexidade - comece com uma estrutura simples');
|
|
226
263
|
recomendacoes.push('Considere evoluir para Clean Architecture conforme o projeto crescer');
|
|
227
264
|
}
|
|
@@ -243,40 +280,7 @@ export const analistaRecomendacaoArquitetura = {
|
|
|
243
280
|
if (totalArquivos > 100 && servicosSet.size === 0) {
|
|
244
281
|
recomendacoes.push('Projeto grande e monolítico - considere dividir em serviços menores');
|
|
245
282
|
}
|
|
246
|
-
ocorrencias
|
|
247
|
-
tipo: 'recomendacao-arquitetura',
|
|
248
|
-
nivel: 'info',
|
|
249
|
-
relPath: _relPath,
|
|
250
|
-
linha: 1,
|
|
251
|
-
mensagem: `[TARGET] Arquitetura recomendada: ${arquiteturaRecomendada} (${confianca}% confiança)`
|
|
252
|
-
}));
|
|
253
|
-
if (sinaisCleanArch.length > 0) {
|
|
254
|
-
ocorrencias.push(criarOcorrencia({
|
|
255
|
-
tipo: 'sinais-clean-arch',
|
|
256
|
-
nivel: 'info',
|
|
257
|
-
relPath: _relPath,
|
|
258
|
-
linha: 1,
|
|
259
|
-
mensagem: `Sinais de Clean Architecture: ${sinaisCleanArch.join(', ')}`
|
|
260
|
-
}));
|
|
261
|
-
}
|
|
262
|
-
if (sinaisDistributed.length > 0) {
|
|
263
|
-
ocorrencias.push(criarOcorrencia({
|
|
264
|
-
tipo: 'sinais-distributed-arch',
|
|
265
|
-
nivel: 'info',
|
|
266
|
-
relPath: _relPath,
|
|
267
|
-
linha: 1,
|
|
268
|
-
mensagem: `Sinais de Arquitetura Distribuída: ${sinaisDistributed.join(', ')}`
|
|
269
|
-
}));
|
|
270
|
-
}
|
|
271
|
-
for (const rec of recomendacoes.slice(0, 5)) {
|
|
272
|
-
ocorrencias.push(criarOcorrencia({
|
|
273
|
-
tipo: 'recomendacao-arquitetura-item',
|
|
274
|
-
nivel: 'info',
|
|
275
|
-
relPath: _relPath,
|
|
276
|
-
linha: 1,
|
|
277
|
-
mensagem: `[IDEA] ${rec}`
|
|
278
|
-
}));
|
|
279
|
-
}
|
|
283
|
+
adicionarOcorrenciasRecomendacao(ocorrencias, _relPath, sinaisCleanArch, sinaisDistributed, recomendacoes, arquiteturaRecomendada, confianca);
|
|
280
284
|
return ocorrencias;
|
|
281
285
|
}
|
|
282
286
|
};
|
|
@@ -322,6 +322,47 @@ function detectarAsyncSemTryCatch(src, problemas) {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
+
function detectarCookieInseguro(callee, args, path, problemas) {
|
|
326
|
+
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier' && callee.property.name === 'cookie') {
|
|
327
|
+
const object = callee.object;
|
|
328
|
+
const isRequest = object.type === 'Identifier' && (object.name === 'req' || object.name === 'request');
|
|
329
|
+
if (!isRequest) {
|
|
330
|
+
const options = args[2];
|
|
331
|
+
let hasHttpOnly = false;
|
|
332
|
+
if (options.type === 'ObjectExpression') {
|
|
333
|
+
hasHttpOnly = options.properties.some((prop) => prop.type === 'ObjectProperty' && prop.key.type === 'Identifier' && prop.key.name === 'httpOnly' && (prop.value.type === 'BooleanLiteral' ? prop.value.value : false));
|
|
334
|
+
}
|
|
335
|
+
if (!hasHttpOnly) {
|
|
336
|
+
problemas.push({
|
|
337
|
+
tipo: 'insecure-cookie',
|
|
338
|
+
descricao: 'Cookie configurado sem a flag httpOnly',
|
|
339
|
+
severidade: 'media',
|
|
340
|
+
linha: path.node.loc?.start.line ?? 0,
|
|
341
|
+
sugestao: 'Sempre use httpOnly: true para evitar acesso via JavaScript (XSS)'
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function detectarSQLInjectionAST(callee, args, path, problemas) {
|
|
348
|
+
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier' && ['query', 'execute', 'raw', 'queryRaw', 'executeRaw'].includes(callee.property.name)) {
|
|
349
|
+
const firstArg = args[0];
|
|
350
|
+
const isUnsafe = firstArg.type === 'TemplateLiteral' && firstArg.expressions.length > 0 || firstArg.type === 'BinaryExpression' && firstArg.operator === '+';
|
|
351
|
+
if (isUnsafe) {
|
|
352
|
+
const code = firstArg.type === 'TemplateLiteral' ? firstArg.quasis.map((delimitadorString) => delimitadorString.value.raw).join('') : '';
|
|
353
|
+
const temParametros = /['"`].*(\?|\$\d|:\w+).*['"`]/.test(code);
|
|
354
|
+
if (!temParametros) {
|
|
355
|
+
problemas.push({
|
|
356
|
+
tipo: 'sql-injection',
|
|
357
|
+
descricao: 'Possível SQL Injection detectada via AST (concatenação no primeiro argumento)',
|
|
358
|
+
severidade: 'critica',
|
|
359
|
+
linha: path.node.loc?.start.line ?? 0,
|
|
360
|
+
sugestao: 'Use queries parametrizadas ao invés de concatenar strings ou template literals com variáveis'
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
325
366
|
function detectarProblemasAST(ast, problemas) {
|
|
326
367
|
try {
|
|
327
368
|
traverse(ast.node, {
|
|
@@ -351,48 +392,8 @@ function detectarProblemasAST(ast, problemas) {
|
|
|
351
392
|
sugestao: 'Use função ao invés de string'
|
|
352
393
|
});
|
|
353
394
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const object = callee.object;
|
|
357
|
-
const isRequest = object.type === 'Identifier' && (object.name === 'req' || object.name === 'request');
|
|
358
|
-
if (!isRequest) {
|
|
359
|
-
// Heurística: assume-se que se não é req/request, é provável ser a resposta (res)
|
|
360
|
-
const options = args[2];
|
|
361
|
-
let hasHttpOnly = false;
|
|
362
|
-
if (options.type === 'ObjectExpression') {
|
|
363
|
-
hasHttpOnly = options.properties.some(prop => prop.type === 'ObjectProperty' && prop.key.type === 'Identifier' && prop.key.name === 'httpOnly' && (prop.value.type === 'BooleanLiteral' ? prop.value.value : false));
|
|
364
|
-
}
|
|
365
|
-
if (!hasHttpOnly) {
|
|
366
|
-
problemas.push({
|
|
367
|
-
tipo: 'insecure-cookie',
|
|
368
|
-
descricao: 'Cookie configurado sem a flag httpOnly',
|
|
369
|
-
severidade: 'media',
|
|
370
|
-
linha: path.node.loc?.start.line ?? 0,
|
|
371
|
-
sugestao: 'Sempre use httpOnly: true para evitar acesso via JavaScript (XSS)'
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
// 3. SQL Injection (básico via AST)
|
|
377
|
-
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier' && ['query', 'execute', 'raw', 'queryRaw', 'executeRaw'].includes(callee.property.name)) {
|
|
378
|
-
const firstArg = args[0];
|
|
379
|
-
// Se o primeiro argumento é uma TemplateLiteral com expressões ou uma BinaryExpression (+)
|
|
380
|
-
const isUnsafe = firstArg.type === 'TemplateLiteral' && firstArg.expressions.length > 0 || firstArg.type === 'BinaryExpression' && firstArg.operator === '+';
|
|
381
|
-
if (isUnsafe) {
|
|
382
|
-
// Verificar se parece usar parâmetros de qualquer forma
|
|
383
|
-
const code = firstArg.type === 'TemplateLiteral' ? firstArg.quasis.map(delimitadorString => delimitadorString.value.raw).join('') : '';
|
|
384
|
-
const temParametros = /['"`].*(\?|\$\d|:\w+).*['"`]/.test(code);
|
|
385
|
-
if (!temParametros) {
|
|
386
|
-
problemas.push({
|
|
387
|
-
tipo: 'sql-injection',
|
|
388
|
-
descricao: 'Possível SQL Injection detectada via AST (concatenação no primeiro argumento)',
|
|
389
|
-
severidade: 'critica',
|
|
390
|
-
linha: path.node.loc?.start.line ?? 0,
|
|
391
|
-
sugestao: 'Use queries parametrizadas ao invés de concatenar strings ou template literals com variáveis'
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
395
|
+
detectarCookieInseguro(callee, args, path, problemas);
|
|
396
|
+
detectarSQLInjectionAST(callee, args, path, problemas);
|
|
396
397
|
}
|
|
397
398
|
});
|
|
398
399
|
}
|
|
@@ -4,6 +4,32 @@ import { config, getAllExpectedDirNames, getDirVariantPattern, getNameConvention
|
|
|
4
4
|
import { messages } from '../../core/messages/index.js';
|
|
5
5
|
import { grafoDependencias } from './detector-dependencies.js';
|
|
6
6
|
export const sinaisDetectados = {};
|
|
7
|
+
function verificarConvencoesNomesDiretorios(ocorrencias, caminhosNorm) {
|
|
8
|
+
const expectedDirs = getAllExpectedDirNames();
|
|
9
|
+
if (expectedDirs.length === 0) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const topLevelDirs = new Set(caminhosNorm.filter(caminho => caminho.includes('/') && !caminho.startsWith('node_modules/') && !caminho.startsWith('dist/')).map(caminho => caminho.split('/')[0]));
|
|
13
|
+
const configuredNames = new Set(Object.values(getNameConventions()).filter((valor) => typeof valor === 'string' && valor.trim().length > 0).map(valor => valor.trim().toLowerCase()));
|
|
14
|
+
for (const { key, expected } of expectedDirs) {
|
|
15
|
+
const expectedLower = expected.toLowerCase();
|
|
16
|
+
const match = [...topLevelDirs].find(diretorio => diretorio.toLowerCase() === expectedLower);
|
|
17
|
+
if (!match) {
|
|
18
|
+
const variantPattern = getDirVariantPattern(key);
|
|
19
|
+
const foundVariant = [...topLevelDirs].find(diretorio => variantPattern.test(diretorio));
|
|
20
|
+
if (foundVariant && !configuredNames.has(foundVariant.toLowerCase())) {
|
|
21
|
+
ocorrencias.push({
|
|
22
|
+
tipo: 'name-convention',
|
|
23
|
+
nivel: 'aviso',
|
|
24
|
+
mensagem: messages.DetectorEstruturaMensagens.nomeDiretorioNaoConforme(foundVariant, expected),
|
|
25
|
+
origem: 'detector-estrutura',
|
|
26
|
+
relPath: `[raiz do projeto]`,
|
|
27
|
+
linha: 0
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
7
33
|
/**
|
|
8
34
|
* Analisa a estrutura do projeto e detecta padrões como monorepo, fullstack, mistura de src/packages, etc.
|
|
9
35
|
* Retorna ocorrências para cada sinal relevante encontrado.
|
|
@@ -24,7 +50,7 @@ export const detectorEstrutura = {
|
|
|
24
50
|
// Conjuntos para buscas O(1) e deduplicação implícita
|
|
25
51
|
const setCaminhos = new Set(caminhos);
|
|
26
52
|
// Detecta contexto geral do projeto para análise inteligente
|
|
27
|
-
const arquivoPrincipal = caminhos?.find(caminho => /package\.json$/.test(caminho)) ?? caminhos
|
|
53
|
+
const arquivoPrincipal = caminhos?.find(caminho => /package\.json$/.test(caminho)) ?? caminhos[0];
|
|
28
54
|
const conteudoPrincipal = contexto.arquivos?.find(arquivo => arquivo.relPath === arquivoPrincipal)?.content ?? '';
|
|
29
55
|
const contextoProjeto = detectarContextoProjeto({
|
|
30
56
|
arquivo: arquivoPrincipal,
|
|
@@ -160,7 +186,7 @@ export const detectorEstrutura = {
|
|
|
160
186
|
// Múltiplos entrypoints
|
|
161
187
|
// Lista de entrypoints potencialmente grande em repositórios com dependências; filtra ignorados
|
|
162
188
|
const entrypointsAll = caminhosNorm.filter(caminho => /(^|[\\/])(cli|index|main)\.(ts|js)$/.test(caminho));
|
|
163
|
-
const ignoredDyn = config.INCLUDE_EXCLUDE_RULES.globalExcludeGlob
|
|
189
|
+
const ignoredDyn = config.INCLUDE_EXCLUDE_RULES.globalExcludeGlob;
|
|
164
190
|
const entrypoints = entrypointsAll.filter(caminho => !micromatch.isMatch(caminho, ignoredDyn)).sort();
|
|
165
191
|
if (entrypoints.length > 1) {
|
|
166
192
|
const MAX_LIST = 20;
|
|
@@ -220,30 +246,7 @@ export const detectorEstrutura = {
|
|
|
220
246
|
linha: 0
|
|
221
247
|
});
|
|
222
248
|
}
|
|
223
|
-
|
|
224
|
-
const expectedDirs = getAllExpectedDirNames();
|
|
225
|
-
if (expectedDirs.length > 0) {
|
|
226
|
-
const topLevelDirs = new Set(caminhosNorm.filter(caminho => caminho.includes('/') && !caminho.startsWith('node_modules/') && !caminho.startsWith('dist/')).map(caminho => caminho.split('/')[0]));
|
|
227
|
-
const configuredNames = new Set(Object.values(getNameConventions()).filter((valor) => typeof valor === 'string' && valor.trim().length > 0).map(valor => valor.trim().toLowerCase()));
|
|
228
|
-
for (const { key, expected } of expectedDirs) {
|
|
229
|
-
const expectedLower = expected.toLowerCase();
|
|
230
|
-
const match = [...topLevelDirs].find(diretorio => diretorio.toLowerCase() === expectedLower);
|
|
231
|
-
if (!match) {
|
|
232
|
-
const variantPattern = getDirVariantPattern(key);
|
|
233
|
-
const foundVariant = [...topLevelDirs].find(diretorio => variantPattern.test(diretorio));
|
|
234
|
-
if (foundVariant && !configuredNames.has(foundVariant.toLowerCase())) {
|
|
235
|
-
ocorrencias.push({
|
|
236
|
-
tipo: 'name-convention',
|
|
237
|
-
nivel: 'aviso',
|
|
238
|
-
mensagem: messages.DetectorEstruturaMensagens.nomeDiretorioNaoConforme(foundVariant, expected),
|
|
239
|
-
origem: 'detector-estrutura',
|
|
240
|
-
relPath: `[raiz do projeto]`,
|
|
241
|
-
linha: 0
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
249
|
+
verificarConvencoesNomesDiretorios(ocorrencias, caminhosNorm);
|
|
247
250
|
return Array.isArray(ocorrencias) ? ocorrencias : [];
|
|
248
251
|
}
|
|
249
252
|
};
|
|
@@ -276,7 +276,10 @@ export const analistaAntiPadroesAsync = {
|
|
|
276
276
|
traverse(nodeToTraverse, visitorAsync, undefined, stateObj);
|
|
277
277
|
// Top-level await detection via text
|
|
278
278
|
if (/^\s*await\s+/m.test(src)) {
|
|
279
|
-
const topLevelAwaitLine =
|
|
279
|
+
const topLevelAwaitLine = (() => {
|
|
280
|
+
const m = src.match(/^\s*await\s+/m);
|
|
281
|
+
return m ? src.substring(0, m.index).split('\n').length : 1;
|
|
282
|
+
})();
|
|
280
283
|
const fnParentLines = new Set();
|
|
281
284
|
const fnVisitor = {
|
|
282
285
|
Function(path) {
|
|
@@ -208,23 +208,20 @@ export const analistaConstrucoesSintaticas = {
|
|
|
208
208
|
});
|
|
209
209
|
// Text-based detection for ES2025/ES2026 patterns
|
|
210
210
|
const esPatterns = [
|
|
211
|
-
[/\busing\s+\w+\s*=\s*new\s+/g, 'explicit-resource-management', (linha) => pushExtra('explicit-resource-management', 'info', 'Explicit Resource Management with "using" (ES2026)', linha)],
|
|
212
|
-
[/\bawait\s+using\s+/g, 'explicit-resource-management-async', (linha) => pushExtra('explicit-resource-management-async', 'info', 'Async Resource Management with "await using" (ES2026)', linha)],
|
|
213
|
-
[/\bTemporal\.\w+/g, 'temporal-api-usage', (linha) => pushExtra('temporal-api-usage', 'info', 'Temporal API usage (ES2026)', linha)],
|
|
214
|
-
[/Promise\.withResolvers\s*\(/g, 'promise-with-resolvers', (linha) => pushExtra('promise-with-resolvers', 'info', 'Promise.withResolvers() (ES2025)', linha)],
|
|
215
|
-
[/Promise\.try\s*\(/g, 'promise-try', (linha) => pushExtra('promise-try', 'info', 'Promise.try() (ES2025)', linha)],
|
|
216
|
-
[/RegExp\.escape\s*\(/g, 'regexp-escape', (linha) => pushExtra('regexp-escape', 'info', 'RegExp.escape() (ES2025)', linha)],
|
|
217
|
-
[/Error\.isError\s*\(/g, 'error-is-error', (linha) => pushExtra('error-is-error', 'info', 'Error.isError() (ES2026)', linha)],
|
|
218
|
-
[/Array\.fromAsync\s*\(/g, 'array-from-async', (linha) => pushExtra('array-from-async', 'info', 'Array.fromAsync() (ES2026)', linha)],
|
|
219
|
-
[/\bIterator\.from\s*\(/g, 'iterator-from', (linha) => pushExtra('iterator-from', 'info', 'Iterator.from() (ES2025)', linha)],
|
|
211
|
+
[/\busing\s+\w+\s*=\s*new\s+/g, 'explicit-resource-management', (linha) => { pushExtra('explicit-resource-management', 'info', 'Explicit Resource Management with "using" (ES2026)', linha); }],
|
|
212
|
+
[/\bawait\s+using\s+/g, 'explicit-resource-management-async', (linha) => { pushExtra('explicit-resource-management-async', 'info', 'Async Resource Management with "await using" (ES2026)', linha); }],
|
|
213
|
+
[/\bTemporal\.\w+/g, 'temporal-api-usage', (linha) => { pushExtra('temporal-api-usage', 'info', 'Temporal API usage (ES2026)', linha); }],
|
|
214
|
+
[/Promise\.withResolvers\s*\(/g, 'promise-with-resolvers', (linha) => { pushExtra('promise-with-resolvers', 'info', 'Promise.withResolvers() (ES2025)', linha); }],
|
|
215
|
+
[/Promise\.try\s*\(/g, 'promise-try', (linha) => { pushExtra('promise-try', 'info', 'Promise.try() (ES2025)', linha); }],
|
|
216
|
+
[/RegExp\.escape\s*\(/g, 'regexp-escape', (linha) => { pushExtra('regexp-escape', 'info', 'RegExp.escape() (ES2025)', linha); }],
|
|
217
|
+
[/Error\.isError\s*\(/g, 'error-is-error', (linha) => { pushExtra('error-is-error', 'info', 'Error.isError() (ES2026)', linha); }],
|
|
218
|
+
[/Array\.fromAsync\s*\(/g, 'array-from-async', (linha) => { pushExtra('array-from-async', 'info', 'Array.fromAsync() (ES2026)', linha); }],
|
|
219
|
+
[/\bIterator\.from\s*\(/g, 'iterator-from', (linha) => { pushExtra('iterator-from', 'info', 'Iterator.from() (ES2025)', linha); }],
|
|
220
220
|
];
|
|
221
221
|
for (const [pattern, , fn] of esPatterns) {
|
|
222
222
|
let m;
|
|
223
223
|
while ((m = pattern.exec(src)) !== null) {
|
|
224
224
|
const position = m.index;
|
|
225
|
-
if (position == null) {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
225
|
if (!isInStringOrComment(src, position)) {
|
|
229
226
|
const linha = src.substring(0, position).split('\n').length;
|
|
230
227
|
fn(linha);
|
|
@@ -237,10 +234,7 @@ export const analistaConstrucoesSintaticas = {
|
|
|
237
234
|
while ((match = templateLiteralPadrao.exec(src)) !== null) {
|
|
238
235
|
const content = match[1];
|
|
239
236
|
if (content && !content.includes('${') && content.length < 10) {
|
|
240
|
-
const position = match.index;
|
|
241
|
-
if (position == null) {
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
237
|
+
const position = match.index ?? 0;
|
|
244
238
|
if (!isInStringOrComment(src, position)) {
|
|
245
239
|
const linha = src.substring(0, position).split('\n').length;
|
|
246
240
|
pushExtra('template-literal-overuse', 'info', messages.DetectorConstrucoesSintaticasMensagens.templateLiteralOveruse, linha);
|
|
@@ -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({
|
|
@@ -114,6 +114,7 @@ function collectHookIssues(src, relPath) {
|
|
|
114
114
|
for (const match of src.matchAll(/\buse\s*\(/g)) {
|
|
115
115
|
const line = lineOf(match.index);
|
|
116
116
|
// Avoid matching "useState", "useEffect", etc.
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime safety for match.index === 0
|
|
117
118
|
if (src[match.index - 1]?.match(/[a-zA-Z]/)) {
|
|
118
119
|
continue;
|
|
119
120
|
}
|
|
@@ -145,7 +146,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
145
146
|
if (isJsxLike) {
|
|
146
147
|
plugins.unshift('jsx');
|
|
147
148
|
}
|
|
148
|
-
const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins });
|
|
149
|
+
const ast = babelParse(src, { sourceType: 'unambiguous', errorRecovery: true, plugins: plugins });
|
|
149
150
|
const out = [];
|
|
150
151
|
const seen = new Set();
|
|
151
152
|
const pushOnce = (m) => {
|
|
@@ -161,8 +162,9 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
161
162
|
if (t.isIdentifier(callee)) {
|
|
162
163
|
const name = callee.name;
|
|
163
164
|
// React 19: use() is a special case (not prefixed with use but still a hook)
|
|
164
|
-
if (name === 'use')
|
|
165
|
+
if (name === 'use') {
|
|
165
166
|
return { name };
|
|
167
|
+
}
|
|
166
168
|
return /^use[A-Z0-9_]/.test(name) ? { name } : null;
|
|
167
169
|
}
|
|
168
170
|
if (t.isMemberExpression(callee)) {
|