@mocoto/mahoraga 0.14.5 → 0.14.6
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 +9 -39
- package/dist/analysts/detectors/detector-structure.js +1 -1
- package/dist/analysts/react/analysts/analyst-react-hooks.js +103 -6
- package/dist/analysts/react/analysts/analyst-react.js +12 -0
- package/dist/analysts/react/detectors/detector-react-best-practices.js +24 -1
- package/dist/analysts/tailwind/analysts/analyst-tailwind.js +5 -2
- package/dist/core/utils/exec-safe.js +23 -5
- package/dist/node.loader.js +0 -1
- package/package.json +1 -1
- package/dist/index.js +0 -2
- package/dist/sdk/analyzer.js +0 -88
- package/dist/sdk/index.js +0 -2
- package/dist/sdk/sdk-legacy.js +0 -36
- package/dist/types/sdk/analyzer.js +0 -1
- package/dist/types/sdk/index.js +0 -1
package/README.md
CHANGED
|
@@ -8,15 +8,19 @@ Proveniência e Autoria: Este documento integra o projeto Mahoraga (licença MIT
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
<p>
|
|
11
|
+
<p> Desisti de desistir do projeto, até parece que vou aceitar perder pra vocês ...</p>
|
|
12
|
+
|
|
13
|
+
<p> caso tenha interesse em ajudar no projeto o E-mail é esse ai em baixo o **lint que me aguarde 😜 </p>
|
|
12
14
|
|
|
13
15
|
[Gmail](mailto:mocoto.persona@gmail.com)
|
|
14
16
|
|
|
17
|
+
<h5> Alias!! ignora o sugestoes-00*.json ainda esta em ajustes.</h5>
|
|
18
|
+
|
|
15
19
|
## Começar Rápido (30 segundos)
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
22
|
# 1. Instalar
|
|
19
|
-
npm install -g mahoraga
|
|
23
|
+
npm install -g @mocoto/mahoraga
|
|
20
24
|
|
|
21
25
|
# 2. Analisar seu projeto
|
|
22
26
|
cd meu-projeto
|
|
@@ -185,25 +189,16 @@ mahoraga metricas
|
|
|
185
189
|
|
|
186
190
|
**Uso global:**
|
|
187
191
|
```bash
|
|
188
|
-
npm install -g mahoraga
|
|
192
|
+
npm install -g @mocoto/mahoraga
|
|
189
193
|
mahoraga --version
|
|
190
194
|
```
|
|
191
195
|
|
|
192
196
|
**Uso local no projeto:**
|
|
193
197
|
```bash
|
|
194
|
-
npm install --save-dev mahoraga
|
|
198
|
+
npm install --save-dev @mocoto/mahoraga
|
|
195
199
|
npx mahoraga --help
|
|
196
200
|
```
|
|
197
201
|
|
|
198
|
-
### Do Repositório
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
git clone https://github.com/o-atoa/mahoraga.git
|
|
202
|
-
cd mahoraga
|
|
203
|
-
npm install
|
|
204
|
-
npm run build
|
|
205
|
-
npm link
|
|
206
|
-
```
|
|
207
202
|
|
|
208
203
|
### Docker
|
|
209
204
|
|
|
@@ -612,19 +607,6 @@ mahoraga github-actions gate --threshold 80 --fail-on erro
|
|
|
612
607
|
|
|
613
608
|
---
|
|
614
609
|
|
|
615
|
-
## Desenvolvimento
|
|
616
|
-
|
|
617
|
-
### Setup
|
|
618
|
-
|
|
619
|
-
```bash
|
|
620
|
-
git clone https://github.com/o-atoa/mahoraga.git
|
|
621
|
-
cd mahoraga
|
|
622
|
-
npm install
|
|
623
|
-
npm run build
|
|
624
|
-
npm link
|
|
625
|
-
mahoraga --version
|
|
626
|
-
```
|
|
627
|
-
|
|
628
610
|
### Scripts
|
|
629
611
|
|
|
630
612
|
```bash
|
|
@@ -691,18 +673,6 @@ Dependências de terceiros listadas em [THIRD-PARTY-NOTICES.txt](./THIRD-PARTY-N
|
|
|
691
673
|
|
|
692
674
|
---
|
|
693
675
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
[](https://www.npmjs.com/package/mahoraga)
|
|
697
|
-
[](https://github.com/o-atoa/mahoraga)
|
|
698
|
-
[](https://github.com/o-atoa/mahoraga/issues)
|
|
699
|
-
[](https://github.com/o-atoa/mahoraga/releases)
|
|
700
|
-
|
|
701
|
-
**Feito para os iniciantes** — [Voltar ao topo](#mahoraga)
|
|
676
|
+
**Feito para os iniciantes**
|
|
702
677
|
|
|
703
678
|
</div>
|
|
704
|
-
# mahoraga
|
|
705
|
-
|
|
706
|
-
# mahoraga
|
|
707
|
-
# mahoraga
|
|
708
|
-
# mahoraga
|
|
@@ -51,7 +51,7 @@ export const detectorEstrutura = {
|
|
|
51
51
|
const setCaminhos = new Set(caminhos);
|
|
52
52
|
// Detecta contexto geral do projeto para análise inteligente
|
|
53
53
|
const arquivoPrincipal = caminhos?.find(caminho => /package\.json$/.test(caminho)) ?? caminhos[0];
|
|
54
|
-
const conteudoPrincipal = contexto.arquivos
|
|
54
|
+
const conteudoPrincipal = contexto.arquivos?.find(arquivo => arquivo.relPath === arquivoPrincipal)?.content ?? '';
|
|
55
55
|
const contextoProjeto = detectarContextoProjeto({
|
|
56
56
|
arquivo: arquivoPrincipal,
|
|
57
57
|
conteudo: conteudoPrincipal,
|
|
@@ -33,8 +33,63 @@ function extractHookCallContent(src, startIndex) {
|
|
|
33
33
|
let depth = 0;
|
|
34
34
|
let started = false;
|
|
35
35
|
let content = '';
|
|
36
|
+
let inSingle = false;
|
|
37
|
+
let inDouble = false;
|
|
38
|
+
let inBacktick = false;
|
|
39
|
+
let escaped = false;
|
|
36
40
|
for (let i = startIndex; i < src.length; i++) {
|
|
37
41
|
const char = src[i];
|
|
42
|
+
if (escaped) {
|
|
43
|
+
escaped = false;
|
|
44
|
+
if (started)
|
|
45
|
+
content += char;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '\\' && (inSingle || inDouble || inBacktick)) {
|
|
49
|
+
escaped = true;
|
|
50
|
+
if (started)
|
|
51
|
+
content += char;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (inSingle) {
|
|
55
|
+
if (char === "'")
|
|
56
|
+
inSingle = false;
|
|
57
|
+
if (started)
|
|
58
|
+
content += char;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (inDouble) {
|
|
62
|
+
if (char === '"')
|
|
63
|
+
inDouble = false;
|
|
64
|
+
if (started)
|
|
65
|
+
content += char;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (inBacktick) {
|
|
69
|
+
if (char === '`')
|
|
70
|
+
inBacktick = false;
|
|
71
|
+
if (started)
|
|
72
|
+
content += char;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (char === "'") {
|
|
76
|
+
inSingle = true;
|
|
77
|
+
if (started)
|
|
78
|
+
content += char;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"') {
|
|
82
|
+
inDouble = true;
|
|
83
|
+
if (started)
|
|
84
|
+
content += char;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (char === '`') {
|
|
88
|
+
inBacktick = true;
|
|
89
|
+
if (started)
|
|
90
|
+
content += char;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
38
93
|
if (char === '(') {
|
|
39
94
|
depth++;
|
|
40
95
|
started = true;
|
|
@@ -59,7 +114,22 @@ function collectHookIssues(src, relPath) {
|
|
|
59
114
|
effectMatches.forEach(m => {
|
|
60
115
|
const hookInicio = m.index;
|
|
61
116
|
const fullCall = extractHookCallContent(src, hookInicio + m[0].length - 1);
|
|
62
|
-
|
|
117
|
+
// More robust deps detection: find the last comma followed by an array or identifier before the closing paren
|
|
118
|
+
const hasDepsArg = (() => {
|
|
119
|
+
// Find the last ')' (closing paren of the full call)
|
|
120
|
+
const lastClosing = fullCall.lastIndexOf(')');
|
|
121
|
+
if (lastClosing === -1)
|
|
122
|
+
return false;
|
|
123
|
+
const beforeClosing = fullCall.slice(0, lastClosing);
|
|
124
|
+
// Find the last '[' or identifier before the closing paren
|
|
125
|
+
const arrayMatch = beforeClosing.match(/,\s*(\[[\s\S]*?\])\s*$/);
|
|
126
|
+
if (arrayMatch)
|
|
127
|
+
return true;
|
|
128
|
+
const identMatch = beforeClosing.match(/,\s*(\w+)\s*$/);
|
|
129
|
+
if (identMatch)
|
|
130
|
+
return true;
|
|
131
|
+
return false;
|
|
132
|
+
})();
|
|
63
133
|
const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
|
|
64
134
|
if (!hasDepsArg && !skip) {
|
|
65
135
|
const line = lineOf(hookInicio);
|
|
@@ -89,10 +159,18 @@ function collectHookIssues(src, relPath) {
|
|
|
89
159
|
ocorrencias.push(warn(messages.ReactHooksMensagens.memoCallbackNoDeps, relPath, line));
|
|
90
160
|
}
|
|
91
161
|
});
|
|
92
|
-
// Conditional hooks
|
|
93
|
-
|
|
162
|
+
// Conditional hooks — only flag when a hook call is directly inside a conditional block,
|
|
163
|
+
// not when the conditional is inside a hook callback (e.g. cleanup inside useEffect).
|
|
164
|
+
// The regex matches: if/for/while (...) { ... useXxx ... } but limits the match scope
|
|
165
|
+
// to avoid matching conditionals that appear inside hook callbacks.
|
|
166
|
+
const conditionalHooks = [...src.matchAll(/(?:^|(?<=[\n;]))\s*(if|for|while)\s*\([^)]*\)\s*\{[\s\S]{0,160}?use[A-Z][A-Za-z0-9_]*/g)];
|
|
94
167
|
conditionalHooks.forEach(m => {
|
|
95
168
|
const line = lineOf(m.index || 0);
|
|
169
|
+
// Heuristic: skip if the 'if' keyword is preceded by a closing paren or brace at the same indentation,
|
|
170
|
+
// which suggests it's inside a callback body rather than at the component top level.
|
|
171
|
+
const pre = src.slice(Math.max(0, (m.index ?? 0) - 80), m.index ?? 0);
|
|
172
|
+
if (/=>\s*$|\)\s*=>\s*$|\)\s*\{[\s\S]{0,40}$/.test(pre))
|
|
173
|
+
return;
|
|
96
174
|
ocorrencias.push(warn(messages.ReactHooksMensagens.hookInConditional, relPath, line));
|
|
97
175
|
});
|
|
98
176
|
// React 19: useOptimistic detected
|
|
@@ -127,7 +205,7 @@ function collectHookIssues(src, relPath) {
|
|
|
127
205
|
if (!ALL_BUILTIN_HOOKS.includes(hookName)) {
|
|
128
206
|
if (/^use[A-Z]/.test(hookName)) {
|
|
129
207
|
const line = lineOf(m.index || 0);
|
|
130
|
-
ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line));
|
|
208
|
+
ocorrencias.push(warn(`Custom hook '${hookName}' - verify naming follows useXxx convention`, relPath, line, messages.SeverityNiveis.info));
|
|
131
209
|
}
|
|
132
210
|
}
|
|
133
211
|
});
|
|
@@ -182,7 +260,26 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
182
260
|
const isMemoLike = (name) => name === 'useMemo' || name === 'useCallback';
|
|
183
261
|
const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name) || name === 'use';
|
|
184
262
|
const inConditionalOrLoop = (path) => {
|
|
185
|
-
|
|
263
|
+
// Only consider conditionals/loops in the SAME function scope, not inside nested callbacks.
|
|
264
|
+
// Stop climbing at the nearest function boundary to avoid false positives when
|
|
265
|
+
// a hook is used inside a callback that happens to be in a conditional context.
|
|
266
|
+
let current = path;
|
|
267
|
+
while (current) {
|
|
268
|
+
const parent = current.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression());
|
|
269
|
+
if (parent)
|
|
270
|
+
return true;
|
|
271
|
+
// Climb to the nearest function boundary; stop if we hit one
|
|
272
|
+
const fnBoundary = current.findParent(p => p.isFunction());
|
|
273
|
+
if (!fnBoundary)
|
|
274
|
+
break;
|
|
275
|
+
// If the fnBoundary is the component function itself, we've exhausted this scope
|
|
276
|
+
// Check if there's a conditional ABOVE this function boundary
|
|
277
|
+
const aboveFn = fnBoundary.findParent(p => p.isIfStatement() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isSwitchStatement() || p.isConditionalExpression());
|
|
278
|
+
if (aboveFn)
|
|
279
|
+
return true;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
186
283
|
};
|
|
187
284
|
const getIdentifiersInNode = (node) => {
|
|
188
285
|
const ids = new Set();
|
|
@@ -329,7 +426,7 @@ function parseHooksWithBabel(src, relPath) {
|
|
|
329
426
|
}
|
|
330
427
|
}
|
|
331
428
|
if (isAnyHook(name) && !isEffectLike(name) && !isMemoLike(name) && name !== 'useState' && name !== 'useRef' && name !== 'useContext' && name !== 'useReducer' && name !== 'useDebugValue' && name !== 'useId' && name !== 'useOptimistic' && name !== 'useActionState' && name !== 'useFormStatus' && name !== 'use') {
|
|
332
|
-
pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine));
|
|
429
|
+
pushOnce(warn(`Custom hook '${name}' usage detected`, relPath, locLine, messages.SeverityNiveis.info));
|
|
333
430
|
}
|
|
334
431
|
}
|
|
335
432
|
catch {
|
|
@@ -38,6 +38,11 @@ function collectReactIssues(src, relPath) {
|
|
|
38
38
|
// dangerouslySetInnerHTML
|
|
39
39
|
for (const match of src.matchAll(/dangerouslySetInnerHTML/gi)) {
|
|
40
40
|
const line = lineOf(match.index);
|
|
41
|
+
// Skip if this is a <script type="speculationrules"> element — dangerouslySetInnerHTML
|
|
42
|
+
// is required for Speculation Rules API (no children allowed, only innerHTML).
|
|
43
|
+
const lineContent = src.split('\n')[line - 1] ?? '';
|
|
44
|
+
if (/speculationrules/.test(lineContent))
|
|
45
|
+
continue;
|
|
41
46
|
ocorrencias.push(warn(messages.ReactMensagens.dangerouslySetInnerHTML, relPath, line));
|
|
42
47
|
}
|
|
43
48
|
// <img> sem alt
|
|
@@ -233,6 +238,13 @@ function parseReactWithBabel(scan, relPath) {
|
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
if (findAttr(attrs, 'dangerouslySetInnerHTML')) {
|
|
241
|
+
// Skip if this is a <script type="speculationrules"> — dangerouslySetInnerHTML
|
|
242
|
+
// is required for Speculation Rules API (no children, only innerHTML).
|
|
243
|
+
if (tag.toLowerCase() === 'script') {
|
|
244
|
+
const typeAttr = findAttr(attrs, 'type');
|
|
245
|
+
if (typeAttr && /speculationrules/i.test(normalizeStringValue(typeAttr.value)))
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
236
248
|
pushOnce(warn(messages.ReactMensagens.dangerouslySetInnerHTML, relPath, locLine));
|
|
237
249
|
}
|
|
238
250
|
// Inline Styles
|
|
@@ -23,6 +23,9 @@ export const detectorReactBestPractices = {
|
|
|
23
23
|
linhas.forEach((linha, index) => {
|
|
24
24
|
const numeroLinha = index + 1;
|
|
25
25
|
if (/dangerouslySetInnerHTML/.test(linha)) {
|
|
26
|
+
// Skip <script type="speculationrules"> — dangerouslySetInnerHTML is required
|
|
27
|
+
if (/speculationrules/i.test(linha))
|
|
28
|
+
return;
|
|
26
29
|
ocorrencias.push(warn('react-dangerous-html', D.dangerousHtml, relPath, numeroLinha, D.dangerousHtmlSugestao, 'erro'));
|
|
27
30
|
}
|
|
28
31
|
if (/onClick=|onSubmit=|onChange=|onMouseOver=/.test(linha) && /{[^}]*=>/.test(linha)) {
|
|
@@ -101,7 +104,27 @@ function detectWithBabel(src, relPath) {
|
|
|
101
104
|
}
|
|
102
105
|
const locLine = path.node.loc?.start.line;
|
|
103
106
|
if (isHookName(name)) {
|
|
104
|
-
|
|
107
|
+
// Only flag hooks inside conditionals in the SAME function scope.
|
|
108
|
+
// Stop climbing at function boundaries (callbacks, event handlers, etc.)
|
|
109
|
+
// to avoid false positives when a conditional is inside a hook callback.
|
|
110
|
+
const parent = (() => {
|
|
111
|
+
let currentPath = path;
|
|
112
|
+
while (currentPath) {
|
|
113
|
+
const found = currentPath.findParent(p => p.isIfStatement() || p.isConditionalExpression() || p.isLogicalExpression() || p.isSwitchStatement() || p.isSwitchCase() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isLoop());
|
|
114
|
+
if (found)
|
|
115
|
+
return found;
|
|
116
|
+
// Stop at function boundaries
|
|
117
|
+
const fnBoundary = currentPath.findParent(p => p.isFunction());
|
|
118
|
+
if (!fnBoundary)
|
|
119
|
+
return null;
|
|
120
|
+
// Check above the function boundary
|
|
121
|
+
const aboveFn = fnBoundary.findParent(p => p.isIfStatement() || p.isConditionalExpression() || p.isLogicalExpression() || p.isSwitchStatement() || p.isSwitchCase() || p.isForStatement() || p.isWhileStatement() || p.isDoWhileStatement() || p.isLoop());
|
|
122
|
+
if (aboveFn)
|
|
123
|
+
return aboveFn;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
})();
|
|
105
128
|
if (parent) {
|
|
106
129
|
pushOnce(warn('react-hook-conditional', D.hookConditionalCall, relPath, locLine, D.hookConditionalCallSugestao, 'erro'));
|
|
107
130
|
}
|
|
@@ -311,8 +311,11 @@ function propertyKey(token) {
|
|
|
311
311
|
re: /^max-h(?:-|\[)/,
|
|
312
312
|
key: 'max-h'
|
|
313
313
|
}, {
|
|
314
|
-
re: /^(top|
|
|
315
|
-
key: 'position'
|
|
314
|
+
re: /^(top|bottom)(?:-|\[)/,
|
|
315
|
+
key: 'position-y'
|
|
316
|
+
}, {
|
|
317
|
+
re: /^(left|right)(?:-|\[)/,
|
|
318
|
+
key: 'position-x'
|
|
316
319
|
}, {
|
|
317
320
|
re: /^field-sizing(?:-|\[)/,
|
|
318
321
|
key: 'field-sizing'
|
|
@@ -2,19 +2,37 @@
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { config } from '../config/index.js';
|
|
4
4
|
import { getMessages } from '../messages/index.js';
|
|
5
|
+
// Expressão regular para detectar shell metacharacters perigosos
|
|
6
|
+
// que permitiriam injeção de comandos adicionais.
|
|
7
|
+
// Bloqueia: ; | & ` $() ${} <> \n
|
|
8
|
+
const SHELL_INJECTION_RE = /[;|&`]|\$[({]|\${|>|<|\\n/;
|
|
9
|
+
/**
|
|
10
|
+
* Valida que o comando não contém caracteres de injeção shell.
|
|
11
|
+
* @param cmd - Comando a validar
|
|
12
|
+
* @throws Error se o comando contiver padrões perigosos
|
|
13
|
+
*/
|
|
14
|
+
function validarComando(cmd) {
|
|
15
|
+
if (SHELL_INJECTION_RE.test(cmd)) {
|
|
16
|
+
throw new Error(`Comando rejeitado por segurança: contém caracteres de injeção shell.`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function verificarSafeMode() {
|
|
20
|
+
if (config.SAFE_MODE && !config.ALLOW_EXEC) {
|
|
21
|
+
throw new Error(getMessages().ExecSafeMensagens.safeModeBloqueado);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
5
24
|
/**
|
|
6
25
|
* Executa um comando shell de forma segura.
|
|
7
26
|
* Bloqueia a execução quando SAFE_MODE está ativo e ALLOW_EXEC não está habilitado.
|
|
27
|
+
* Também valida o comando contra injeção shell mesmo quando a execução é permitida.
|
|
8
28
|
* @param cmd - Comando shell a executar
|
|
9
29
|
* @param opts - Opções do execSync do Node.js
|
|
10
30
|
* @returns Buffer ou string com a saída do comando
|
|
11
|
-
* @throws Error se SAFE_MODE estiver ativo sem ALLOW_EXEC
|
|
31
|
+
* @throws Error se SAFE_MODE estiver ativo sem ALLOW_EXEC ou se o comando for inválido
|
|
12
32
|
*/
|
|
13
33
|
export function executarShellSeguro(cmd, opts = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
throw new Error(getMessages().ExecSafeMensagens.safeModeBloqueado);
|
|
17
|
-
}
|
|
34
|
+
validarComando(cmd);
|
|
35
|
+
verificarSafeMode();
|
|
18
36
|
return execSync(cmd, opts);
|
|
19
37
|
}
|
|
20
38
|
/**
|
package/dist/node.loader.js
CHANGED
|
@@ -197,7 +197,6 @@ const aliases = {
|
|
|
197
197
|
"@reports": "./src/reports/index",
|
|
198
198
|
"@reports/compliance": "./src/reports/compliance/index",
|
|
199
199
|
"@scripts": "./src/scripts/index",
|
|
200
|
-
"@sdk": "./src/sdk/index",
|
|
201
200
|
"@shared": "./src/shared/index",
|
|
202
201
|
"@shared/data-processing": "./src/shared/data-processing/index",
|
|
203
202
|
"@shared/formatters": "./src/shared/formatters/index",
|
package/package.json
CHANGED
package/dist/index.js
DELETED
package/dist/sdk/analyzer.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { executarInquisicao, scanRepository } from '../core/execution/index.js';
|
|
4
|
-
import { decifrarSintaxe } from '../core/parsing/index.js';
|
|
5
|
-
import { asTecnicas } from '../types/index.js';
|
|
6
|
-
const EXTENSOES_COM_AST = new Set([
|
|
7
|
-
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
8
|
-
'.css', '.scss', '.html', '.xml', '.svg',
|
|
9
|
-
'.py', '.go', '.rs', '.php', '.sh',
|
|
10
|
-
'.yaml', '.yml', '.json',
|
|
11
|
-
]);
|
|
12
|
-
function extensaoRelevante(relPath) {
|
|
13
|
-
const ext = path.extname(relPath).toLowerCase();
|
|
14
|
-
return EXTENSOES_COM_AST.has(ext);
|
|
15
|
-
}
|
|
16
|
-
async function carregarTecnicas() {
|
|
17
|
-
const { registroAnalistas } = await import('../analysts/registry/index.js');
|
|
18
|
-
return asTecnicas(registroAnalistas);
|
|
19
|
-
}
|
|
20
|
-
function tecnicasParaArquivo(tecnicas, relPath) {
|
|
21
|
-
return tecnicas.filter(t => {
|
|
22
|
-
if (t.global) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
if (!t.test) {
|
|
26
|
-
return extensaoRelevante(relPath);
|
|
27
|
-
}
|
|
28
|
-
return t.test(relPath);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
const AST_SENTINEL = {};
|
|
32
|
-
export async function analyzeFile(filePath, content, baseDir) {
|
|
33
|
-
const inicio = performance.now();
|
|
34
|
-
const relPath = baseDir ? path.relative(baseDir, filePath) : path.basename(filePath);
|
|
35
|
-
if (!extensaoRelevante(relPath)) {
|
|
36
|
-
return { ocorrencias: [], arquivo: relPath, tempoMs: 0 };
|
|
37
|
-
}
|
|
38
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
39
|
-
const parsed = await decifrarSintaxe(content, ext, { relPath });
|
|
40
|
-
const ast = parsed ? AST_SENTINEL : null;
|
|
41
|
-
const tecnicas = await carregarTecnicas();
|
|
42
|
-
const tecnicasFile = tecnicasParaArquivo(tecnicas, relPath);
|
|
43
|
-
if (tecnicasFile.length === 0) {
|
|
44
|
-
return { ocorrencias: [], arquivo: relPath, tempoMs: performance.now() - inicio };
|
|
45
|
-
}
|
|
46
|
-
const ocorrencias = [];
|
|
47
|
-
for (const tecnica of tecnicasFile) {
|
|
48
|
-
try {
|
|
49
|
-
const resultado = await tecnica.aplicar(content, relPath, ast, filePath);
|
|
50
|
-
if (Array.isArray(resultado)) {
|
|
51
|
-
ocorrencias.push(...resultado);
|
|
52
|
-
}
|
|
53
|
-
else if (resultado) {
|
|
54
|
-
ocorrencias.push(resultado);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Silencia erros por analista
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
ocorrencias,
|
|
63
|
-
arquivo: relPath,
|
|
64
|
-
tempoMs: performance.now() - inicio,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
export async function analyzeProject(projectDir, opts) {
|
|
68
|
-
const inicio = performance.now();
|
|
69
|
-
const fileEntries = await scanRepository(projectDir, {
|
|
70
|
-
includeContent: true,
|
|
71
|
-
});
|
|
72
|
-
const entries = Object.values(fileEntries);
|
|
73
|
-
const fileEntriesComAst = entries.map(f => ({ ...f, ast: undefined }));
|
|
74
|
-
let filteredEntries = fileEntriesComAst;
|
|
75
|
-
if (opts?.fast) {
|
|
76
|
-
filteredEntries = fileEntriesComAst.filter(fe => {
|
|
77
|
-
const rel = fe.relPath || '';
|
|
78
|
-
return !rel.includes('node_modules') && !rel.includes('.test.') && !rel.includes('.spec.');
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
const tecnicas = await carregarTecnicas();
|
|
82
|
-
const resultadoExecucao = await executarInquisicao(filteredEntries, tecnicas, projectDir, null, { verbose: false, compact: true, fast: opts?.fast });
|
|
83
|
-
return {
|
|
84
|
-
ocorrencias: resultadoExecucao.ocorrencias,
|
|
85
|
-
totalArquivos: entries.length,
|
|
86
|
-
tempoMs: performance.now() - inicio,
|
|
87
|
-
};
|
|
88
|
-
}
|
package/dist/sdk/index.js
DELETED
package/dist/sdk/sdk-legacy.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
import { analistaGithubActions, analistaGithubActionsGlobal, registrarDetectorGithubActions } from '../analysts/github-actions/index.js';
|
|
3
|
-
/**
|
|
4
|
-
* Mahoraga SDK Principal
|
|
5
|
-
* Fornece acesso programático aos analistas e sistema de plugins
|
|
6
|
-
*/
|
|
7
|
-
export const MahoragaSDK = {
|
|
8
|
-
/**
|
|
9
|
-
* Analisa um conteúdo de workflow individual
|
|
10
|
-
*/
|
|
11
|
-
async analisarGithubActions(conteudo, relPath = '.github/workflows/main.yml') {
|
|
12
|
-
try {
|
|
13
|
-
const res = await analistaGithubActions.aplicar(conteudo, relPath, null, undefined, undefined);
|
|
14
|
-
return Array.isArray(res) ? res : [];
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return [];
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
/**
|
|
21
|
-
* Analisa um repositório completo (Contexto global)
|
|
22
|
-
*/
|
|
23
|
-
async analisarRepositorio(contexto) {
|
|
24
|
-
try {
|
|
25
|
-
const res = await analistaGithubActionsGlobal.aplicar('', '', null, undefined, contexto);
|
|
26
|
-
return Array.isArray(res) ? res : [];
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
/**
|
|
33
|
-
* Registra um novo plugin de detector
|
|
34
|
-
*/
|
|
35
|
-
registrarDetector: registrarDetectorGithubActions
|
|
36
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/sdk/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './analyzer.js';
|