pumuki-ast-hooks 5.5.46 → 5.5.48
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/docs/CODE_STANDARDS.md +5 -0
- package/docs/VIOLATIONS_RESOLUTION_PLAN.md +22 -34
- package/package.json +2 -2
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +36 -0
- package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js +1 -1
- package/scripts/hooks-system/application/services/installation/VSCodeTaskConfigurator.js +8 -2
- package/scripts/hooks-system/bin/gitflow-cycle.js +0 -0
- package/scripts/hooks-system/config/project.config.json +1 -1
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidSOLIDAnalyzer.js +11 -255
- package/scripts/hooks-system/infrastructure/ast/android/detectors/android-solid-detectors.js +227 -0
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +12 -3
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +36 -13
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +10 -83
- package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +83 -0
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +17 -2
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/FrontendArchitectureDetector.js +12 -142
- package/scripts/hooks-system/infrastructure/ast/frontend/detectors/frontend-architecture-strategies.js +126 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +30 -783
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureDetector.js +21 -224
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +18 -605
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSModernPracticesRules.js +4 -1
- package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +4 -1
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-architecture-rules-strategies.js +595 -0
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-architecture-strategies.js +192 -0
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +789 -0
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-god-class-detector.js +79 -0
- package/scripts/hooks-system/infrastructure/ast/ios/native-bridge.js +4 -1
- package/skills/android-guidelines/SKILL.md +1 -0
- package/skills/backend-guidelines/SKILL.md +1 -0
- package/skills/frontend-guidelines/SKILL.md +1 -0
- package/skills/ios-guidelines/SKILL.md +1 -0
package/docs/CODE_STANDARDS.md
CHANGED
|
@@ -179,6 +179,11 @@ const DEFAULT_TIMEOUT = 5000;
|
|
|
179
179
|
const defaultConfig = { timeout: 5000 };
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
### Error Handling
|
|
183
|
+
|
|
184
|
+
- **No empty catch blocks**: Never write `catch {}` or `catch (e) {}` with an empty body. Always handle the error (log, rethrow, wrap, or return a Result).
|
|
185
|
+
- **AST enforcement**: `common.error.empty_catch` (CRITICAL)
|
|
186
|
+
|
|
182
187
|
### Files
|
|
183
188
|
|
|
184
189
|
- **kebab-case** for file names
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## 📊 Executive Summary
|
|
13
|
-
- **Current status:** ⚠️
|
|
14
|
-
- **Progress:** 673 →
|
|
15
|
-
- **Branch:** `
|
|
13
|
+
- **Current status:** ⚠️ Acción requerida (0 critical, 0 high, 33 medium, 515 low)
|
|
14
|
+
- **Progress:** 673 → 548 violaciones (-125 tras correcciones y exclusiones afinadas)
|
|
15
|
+
- **Branch:** `develop`
|
|
16
16
|
- **Start date:** 2026-01-05 — **Overall ETA:** 2026-01-15
|
|
17
|
-
- **Goal:**
|
|
17
|
+
- **Goal:** Mantener CRITICAL/HIGH en 0 y reducir MEDIUM/LOW priorizando impacto.
|
|
18
18
|
- **Risks:**
|
|
19
19
|
1) Cambiar severities no elimina violaciones existentes, solo previene nuevas; 2) Mantener estabilidad de librería; 3) Evitar regressions en AST analysis.
|
|
20
20
|
|
|
@@ -65,44 +65,32 @@ gantt
|
|
|
65
65
|
## 🔴 Phase 1: BLOCKER Violations (CRITICAL + HIGH)
|
|
66
66
|
| Status | Severity | Count | Owner | DOD (Definition of Done) | Source |
|
|
67
67
|
|--------|-----------|-------|-------------|--------------------------|--------|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
68
|
+
| ✅ | CRITICAL | 0 | Backend | Mantener CRITICAL en 0 | Audit 2026-01-06 13:56 |
|
|
69
|
+
| ✅ | HIGH | 0 | Backend | Mantener HIGH en 0 | Audit 2026-01-06 13:56 |
|
|
70
70
|
|
|
71
|
-
**
|
|
72
|
-
- `backend.security.missing_audit_logging`: 22 violaciones
|
|
73
|
-
- `backend.antipattern.god_classes`: 15 violaciones
|
|
74
|
-
- `shell.antipattern.god_script`: 1 violación
|
|
75
|
-
- `shell.maintainability.large_script`: 1 violación
|
|
71
|
+
**CRITICAL/HIGH actuales:** Ninguna. Mantener vigilancia en nuevas reglas.
|
|
76
72
|
|
|
77
|
-
**
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
**Fixes aplicados (cambio de severities para evitar CRITICAL con AUDIT_STRICT=1):**
|
|
82
|
-
- `backend.testing.mocks` (40): Ya estaba en 'info' (falso positivo en tests)
|
|
83
|
-
- `backend.error.custom_exceptions` (104): Cambiado de 'low' a 'info'
|
|
84
|
-
- `backend.config.missing_env_separation` (79): Cambiado de 'low' a 'info'
|
|
85
|
-
- `backend.security.missing_audit_logging` (22): Cambiado de 'low' a 'info'
|
|
86
|
-
|
|
87
|
-
**Resultado esperado:** CRITICAL = 0 después de estos cambios.
|
|
73
|
+
**Fixes previos destacados:**
|
|
74
|
+
- Detección de god classes y god scripts en analizadores/shell.
|
|
75
|
+
- Ajustes de severidad para falsas alarmas (mocks en tests, custom_exceptions info, env_separation info, audit_logging info).
|
|
88
76
|
|
|
89
77
|
---
|
|
90
78
|
|
|
91
|
-
## 🟠 Phase 2: MEDIUM Violations (
|
|
79
|
+
## 🟠 Phase 2: MEDIUM Violations (33)
|
|
92
80
|
| Status | Violation | Count | Owner | DOD | Doc |
|
|
93
81
|
|--------|-----------|-------|-------------|-----|-----|
|
|
94
|
-
| ⏳ | MEDIUM |
|
|
82
|
+
| ⏳ | MEDIUM | 33 | Backend | Reducir MEDIUM priorizando reglas de testing y observabilidad | [Medium violations](../docs/medium-violations.md) |
|
|
95
83
|
|
|
96
84
|
**Top MEDIUM violations:**
|
|
97
|
-
- `backend.error.custom_exceptions`:
|
|
98
|
-
- `backend.config.missing_env_separation`:
|
|
99
|
-
- `backend.metrics.missing_prometheus`:
|
|
100
|
-
- `backend.reliability.missing_bulkhead`:
|
|
85
|
+
- `backend.error.custom_exceptions`: 108 violaciones
|
|
86
|
+
- `backend.config.missing_env_separation`: 112 violaciones
|
|
87
|
+
- `backend.metrics.missing_prometheus`: 79 violaciones
|
|
88
|
+
- `backend.reliability.missing_bulkhead`: 57 violaciones
|
|
101
89
|
- `backend.testing.mocks`: 40 violaciones
|
|
102
90
|
|
|
103
91
|
---
|
|
104
92
|
|
|
105
|
-
## 🔵 Phase 3: LOW Violations (
|
|
93
|
+
## 🔵 Phase 3: LOW Violations (602)
|
|
106
94
|
| Status | Violation | Count | Owner | DOD | Doc |
|
|
107
95
|
|--------|-----------|-------|-------------|-----|-----|
|
|
108
96
|
| ⏳ | LOW | 602 | Backend | Reducir LOW con foco en patrones de desarrollo y documentación | [Low violations](../docs/low-violations.md) |
|
|
@@ -121,17 +109,17 @@ gantt
|
|
|
121
109
|
| P1 | backend.observability.missing_prometheus | 37 | Métricas de observabilidad |
|
|
122
110
|
| P1 | backend.auth.missing_cors | 25 | CORS no configurado |
|
|
123
111
|
| P1 | backend.security.missing_audit_logging | 22 | Logging de auditoría faltante |
|
|
124
|
-
| P1 |
|
|
112
|
+
| P1 | shell.maintainability.large_script | 1 | Script extenso en infraestructura |
|
|
125
113
|
|
|
126
114
|
---
|
|
127
115
|
|
|
128
116
|
## 📈 Progress Metrics
|
|
129
117
|
| Phase | Total | Completed | % |
|
|
130
118
|
|------|-------|------------|---|
|
|
131
|
-
| BLOCKERS (CRITICAL + HIGH) |
|
|
132
|
-
| MEDIUM |
|
|
133
|
-
| LOW |
|
|
134
|
-
| **TOTAL** | **
|
|
119
|
+
| BLOCKERS (CRITICAL + HIGH) | 0 | 0 | 0% |
|
|
120
|
+
| MEDIUM | 33 | 0 | 0% |
|
|
121
|
+
| LOW | 515 | 0 | 0% |
|
|
122
|
+
| **TOTAL** | **548** | **0** | **0%** |
|
|
135
123
|
|
|
136
124
|
**Updated risks:**
|
|
137
125
|
1) MEDIUM/LOW pueden requerir cambios más invasivos en la arquitectura; 2) Mantener compatibilidad backward en librería; 3) Evitar impacto en performance de análisis AST.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki-ast-hooks",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.48",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -134,4 +134,4 @@
|
|
|
134
134
|
"./skills": "./skills/skill-rules.json",
|
|
135
135
|
"./hooks": "./hooks/index.js"
|
|
136
136
|
}
|
|
137
|
-
}
|
|
137
|
+
}
|
|
@@ -6,3 +6,39 @@
|
|
|
6
6
|
{"timestamp":1767651940178,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
7
7
|
{"timestamp":1767651940178,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
8
8
|
{"timestamp":1767651940178,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
9
|
+
{"timestamp":1767704104971,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
10
|
+
{"timestamp":1767704104972,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
11
|
+
{"timestamp":1767704104972,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
12
|
+
{"timestamp":1767704104972,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
13
|
+
{"timestamp":1767716313724,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
14
|
+
{"timestamp":1767716313724,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
15
|
+
{"timestamp":1767716313724,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
16
|
+
{"timestamp":1767716313724,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
17
|
+
{"timestamp":1767725525265,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
18
|
+
{"timestamp":1767725525265,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
19
|
+
{"timestamp":1767725525265,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
20
|
+
{"timestamp":1767725525265,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
21
|
+
{"timestamp":1767725979141,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
22
|
+
{"timestamp":1767725979141,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
23
|
+
{"timestamp":1767725979141,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
24
|
+
{"timestamp":1767725979141,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
25
|
+
{"timestamp":1767726402642,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
26
|
+
{"timestamp":1767726402642,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
27
|
+
{"timestamp":1767726402642,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
28
|
+
{"timestamp":1767726402642,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
29
|
+
{"timestamp":1767726935328,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
30
|
+
{"timestamp":1767726935328,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
31
|
+
{"timestamp":1767726935328,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
32
|
+
{"timestamp":1767726935328,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
33
|
+
{"timestamp":1767728590260,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
34
|
+
{"timestamp":1767728590260,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
35
|
+
{"timestamp":1767728590261,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
36
|
+
{"timestamp":1767728590261,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
37
|
+
{"timestamp":1767729164115,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
38
|
+
{"timestamp":1767729164116,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
39
|
+
{"timestamp":1767729164116,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
40
|
+
{"timestamp":1767729164116,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
41
|
+
{"timestamp":1767729940607,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
42
|
+
{"timestamp":1767729940607,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
43
|
+
{"timestamp":1767729940607,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
44
|
+
{"timestamp":1767729940607,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js
CHANGED
|
@@ -56,7 +56,7 @@ class FileSystemInstallerService {
|
|
|
56
56
|
this.copyRecursiveExcluding(source, dest, ['scripts']);
|
|
57
57
|
} else if (item === 'bin/') {
|
|
58
58
|
// Exclude wrapper scripts that would cause fork bombs
|
|
59
|
-
this.copyRecursiveExcluding(source, dest, ['
|
|
59
|
+
this.copyRecursiveExcluding(source, dest, ['cli.js', 'install.js']);
|
|
60
60
|
} else {
|
|
61
61
|
this.copyRecursive(source, dest);
|
|
62
62
|
}
|
|
@@ -63,16 +63,20 @@ class VSCodeTaskConfigurator {
|
|
|
63
63
|
[
|
|
64
64
|
'ROOT="${workspaceFolder}"',
|
|
65
65
|
'PRIMARY="$ROOT/scripts/hooks-system/bin/session-loader.sh"',
|
|
66
|
-
'FALLBACK="$ROOT/node_modules
|
|
66
|
+
'FALLBACK="$ROOT/node_modules/pumuki-ast-hooks/scripts/hooks-system/bin/session-loader.sh"',
|
|
67
|
+
'FALLBACK2="$ROOT/node_modules/@pumuki/ast-intelligence-hooks/bin/session-loader.sh"',
|
|
67
68
|
'if [ -f "$PRIMARY" ]; then',
|
|
68
69
|
' exec bash "$PRIMARY"',
|
|
69
70
|
'elif [ -f "$FALLBACK" ]; then',
|
|
70
71
|
' exec bash "$FALLBACK"',
|
|
72
|
+
'elif [ -f "$FALLBACK2" ]; then',
|
|
73
|
+
' exec bash "$FALLBACK2"',
|
|
71
74
|
'else',
|
|
72
75
|
' echo "AST Session Loader not found." >&2',
|
|
73
76
|
' echo "Tried:" >&2',
|
|
74
77
|
' echo " - $PRIMARY" >&2',
|
|
75
78
|
' echo " - $FALLBACK" >&2',
|
|
79
|
+
' echo " - $FALLBACK2" >&2',
|
|
76
80
|
' exit 127',
|
|
77
81
|
'fi'
|
|
78
82
|
].join('\n')
|
|
@@ -83,7 +87,9 @@ class VSCodeTaskConfigurator {
|
|
|
83
87
|
},
|
|
84
88
|
presentation: {
|
|
85
89
|
reveal: 'always',
|
|
86
|
-
panel: 'new'
|
|
90
|
+
panel: 'new',
|
|
91
|
+
clear: true,
|
|
92
|
+
showReuseMessage: false
|
|
87
93
|
},
|
|
88
94
|
identifier: 'ast-session-loader'
|
|
89
95
|
};
|
|
File without changes
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { pushFinding, SyntaxKind } = require(path.join(__dirname, '../../ast-core'));
|
|
4
|
+
const {
|
|
5
|
+
analyzeOCP,
|
|
6
|
+
analyzeDIP,
|
|
7
|
+
analyzeSRP,
|
|
8
|
+
analyzeISP,
|
|
9
|
+
} = require('../detectors/android-solid-detectors');
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* AndroidSOLIDAnalyzer
|
|
@@ -27,261 +33,11 @@ class AndroidSOLIDAnalyzer {
|
|
|
27
33
|
this.findings = findings;
|
|
28
34
|
this.pushFinding = pushFinding;
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
analyzeOCP(sf) {
|
|
38
|
-
const filePath = sf.getFilePath();
|
|
39
|
-
const fileName = filePath.split('/').pop() || 'unknown';
|
|
40
|
-
|
|
41
|
-
const functions = sf.getFunctions();
|
|
42
|
-
const arrowFunctions = sf.getVariableDeclarations().filter(vd => {
|
|
43
|
-
const init = vd.getInitializer();
|
|
44
|
-
return init && init.getKind() === SyntaxKind.ArrowFunction;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const classes = sf.getClasses();
|
|
48
|
-
|
|
49
|
-
const allNodes = [
|
|
50
|
-
...functions.map(f => ({ type: 'function', node: f, name: f.getName() || 'anonymous' })),
|
|
51
|
-
...arrowFunctions.map(af => ({ type: 'arrow', node: af, name: af.getName() || 'anonymous' })),
|
|
52
|
-
...classes.map(c => ({ type: 'class', node: c, name: c.getName() || 'AnonymousClass' })),
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
allNodes.forEach(({ type, node, name }) => {
|
|
56
|
-
this.analyzeNodeForOCP(node, name, fileName, sf);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
analyzeNodeForOCP(node, nodeName, fileName, sf) {
|
|
61
|
-
let body;
|
|
62
|
-
|
|
63
|
-
if (node.getKind() === SyntaxKind.FunctionDeclaration ||
|
|
64
|
-
node.getKind() === SyntaxKind.FunctionExpression) {
|
|
65
|
-
body = node.getBody();
|
|
66
|
-
} else if (node.getKind() === SyntaxKind.VariableDeclaration) {
|
|
67
|
-
const init = node.getInitializer();
|
|
68
|
-
if (init && init.getKind() === SyntaxKind.ArrowFunction) {
|
|
69
|
-
body = init.getBody();
|
|
70
|
-
}
|
|
71
|
-
} else if (node.getKind() === SyntaxKind.ClassDeclaration) {
|
|
72
|
-
const methods = node.getMethods();
|
|
73
|
-
methods.forEach(method => {
|
|
74
|
-
const methodBody = method.getBody();
|
|
75
|
-
if (methodBody) {
|
|
76
|
-
this.analyzeBodyForOCP(methodBody, `${nodeName}.${method.getName()}`, fileName, sf);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
return;
|
|
80
|
-
} else {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!body) return;
|
|
85
|
-
this.analyzeBodyForOCP(body, nodeName, fileName, sf);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
analyzeBodyForOCP(body, nodeName, fileName, sf) {
|
|
89
|
-
const switches = body.getDescendantsOfKind(SyntaxKind.SwitchStatement);
|
|
90
|
-
|
|
91
|
-
switches.forEach(switchStmt => {
|
|
92
|
-
const switchExpr = switchStmt.getExpression();
|
|
93
|
-
const exprText = switchExpr.getText();
|
|
94
|
-
const cases = switchStmt.getCaseClauses();
|
|
95
|
-
|
|
96
|
-
if (cases.length >= 3) {
|
|
97
|
-
const isDomainValue = /status|type|priority|role|kind|category|state|mode/i.test(exprText);
|
|
98
|
-
|
|
99
|
-
if (isDomainValue) {
|
|
100
|
-
const message = `OCP VIOLATION in ${fileName}::${nodeName}: switch on '${exprText}' with ${cases.length} cases - use Strategy/Map pattern with centralized constants`;
|
|
101
|
-
this.pushFinding('solid.ocp.switch_statement', 'critical', sf, switchStmt, message, this.findings);
|
|
102
|
-
} else if (cases.length >= 5) {
|
|
103
|
-
const message = `OCP VIOLATION in ${fileName}::${nodeName}: large switch (${cases.length} cases) on '${exprText}' - consider Strategy/Factory pattern`;
|
|
104
|
-
this.pushFinding('solid.ocp.switch_statement', 'high', sf, switchStmt, message, this.findings);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const ifStatements = body.getDescendantsOfKind(SyntaxKind.IfStatement);
|
|
110
|
-
const typeIfChains = this.detectTypeIfChains(ifStatements);
|
|
111
|
-
|
|
112
|
-
if (typeIfChains.length >= 3) {
|
|
113
|
-
const chainVars = new Set(typeIfChains.map(chain => chain.variable));
|
|
114
|
-
if (chainVars.size === 1) {
|
|
115
|
-
const varName = Array.from(chainVars)[0];
|
|
116
|
-
const message = `OCP VIOLATION in ${fileName}::${nodeName}: ${typeIfChains.length} if-else checking '${varName}' - use Strategy pattern or polymorphism`;
|
|
117
|
-
this.pushFinding('solid.ocp.if_type_chain', 'critical', sf, typeIfChains[0].node, message, this.findings);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
switches.forEach(switchStmt => {
|
|
122
|
-
const nestedSwitches = switchStmt.getDescendantsOfKind(SyntaxKind.SwitchStatement);
|
|
123
|
-
if (nestedSwitches.length > 0) {
|
|
124
|
-
const message = `OCP VIOLATION in ${fileName}::${nodeName}: nested switch statements - use Strategy/Map pattern with composition`;
|
|
125
|
-
this.pushFinding('solid.ocp.nested_switch', 'critical', sf, switchStmt, message, this.findings);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
detectTypeIfChains(ifStatements) {
|
|
131
|
-
const chains = [];
|
|
132
|
-
|
|
133
|
-
ifStatements.forEach(ifStmt => {
|
|
134
|
-
const condition = ifStmt.getExpression();
|
|
135
|
-
const conditionText = condition.getText();
|
|
136
|
-
|
|
137
|
-
const typeCheckPattern = /(\w+)\s*(===|==|!==|!=)\s*['"]([\w\s]+)['"]|typeof\s+(\w+)|(\w+)\s+instanceof/i;
|
|
138
|
-
const match = conditionText.match(typeCheckPattern);
|
|
139
|
-
|
|
140
|
-
if (match) {
|
|
141
|
-
const variable = match[1] || match[4] || match[5];
|
|
142
|
-
chains.push({ variable, node: ifStmt });
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return chains;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
analyzeDIP(sf) {
|
|
151
|
-
const filePath = sf.getFilePath();
|
|
152
|
-
const fileName = filePath.split('/').pop() || 'unknown';
|
|
153
|
-
|
|
154
|
-
const isDomain = /\/domain\//i.test(filePath);
|
|
155
|
-
|
|
156
|
-
if (isDomain) {
|
|
157
|
-
const imports = sf.getImportDeclarations();
|
|
158
|
-
|
|
159
|
-
imports.forEach(imp => {
|
|
160
|
-
const importPath = imp.getModuleSpecifierValue();
|
|
161
|
-
|
|
162
|
-
if (/\/infrastructure\//i.test(importPath) ||
|
|
163
|
-
/androidx|kotlinx|retrofit|room|hilt/i.test(importPath)) {
|
|
164
|
-
const message = `DIP VIOLATION in ${fileName}: Domain layer importing from Infrastructure/Framework: ${importPath} - Domain should depend only on abstractions`;
|
|
165
|
-
this.pushFinding('solid.dip.domain_depends_infrastructure', 'critical', sf, imp, message, this.findings);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const isPresentation = /\/presentation\//i.test(filePath);
|
|
171
|
-
|
|
172
|
-
if (isPresentation) {
|
|
173
|
-
const imports = sf.getImportDeclarations();
|
|
174
|
-
|
|
175
|
-
imports.forEach(imp => {
|
|
176
|
-
const importPath = imp.getModuleSpecifierValue();
|
|
177
|
-
|
|
178
|
-
if (/\/infrastructure\//i.test(importPath) &&
|
|
179
|
-
!/\/infrastructure\/repositories\/|\/infrastructure\/config\//i.test(importPath)) {
|
|
180
|
-
const message = `DIP VIOLATION in ${fileName}: Presentation layer importing from Infrastructure: ${importPath} - use repository interfaces or abstractions`;
|
|
181
|
-
this.pushFinding('solid.dip.presentation_infrastructure', 'critical', sf, imp, message, this.findings);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const classes = sf.getClasses();
|
|
187
|
-
classes.forEach(cls => {
|
|
188
|
-
const className = cls.getName() || 'AnonymousClass';
|
|
189
|
-
|
|
190
|
-
if (/ViewModel|UseCase/i.test(className)) {
|
|
191
|
-
const imports = sf.getImportDeclarations();
|
|
192
|
-
|
|
193
|
-
imports.forEach(imp => {
|
|
194
|
-
const importPath = imp.getModuleSpecifierValue();
|
|
195
|
-
|
|
196
|
-
if (/Repository|Service|Client/i.test(importPath) &&
|
|
197
|
-
!/interface|protocol|Repository.*Protocol/i.test(importPath)) {
|
|
198
|
-
const message = `DIP VIOLATION in ${fileName}::${className}: depends on concrete implementation '${importPath}' - inject interface/abstraction`;
|
|
199
|
-
this.pushFinding('solid.dip.concrete_dependency', 'critical', sf, imp, message, this.findings);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
analyzeSRP(sf) {
|
|
208
|
-
const filePath = sf.getFilePath();
|
|
209
|
-
const fileName = filePath.split('/').pop() || 'unknown';
|
|
210
|
-
|
|
211
|
-
const functions = sf.getFunctions();
|
|
212
|
-
const arrowFunctions = sf.getVariableDeclarations().filter(vd => {
|
|
213
|
-
const init = vd.getInitializer();
|
|
214
|
-
return init && init.getKind() === SyntaxKind.ArrowFunction;
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
[...functions, ...arrowFunctions].forEach(func => {
|
|
218
|
-
const funcName = func.getName?.() || 'anonymous';
|
|
219
|
-
const body = func.getBody?.() || func.getInitializer()?.getBody();
|
|
220
|
-
|
|
221
|
-
if (!body) return;
|
|
222
|
-
|
|
223
|
-
const statements = body.getStatements();
|
|
224
|
-
const ifStatements = body.getDescendantsOfKind(SyntaxKind.IfStatement);
|
|
225
|
-
const switchStatements = body.getDescendantsOfKind(SyntaxKind.SwitchStatement);
|
|
226
|
-
const loops = body.getDescendantsOfKind(SyntaxKind.ForStatement)
|
|
227
|
-
.concat(body.getDescendantsOfKind(SyntaxKind.ForInStatement))
|
|
228
|
-
.concat(body.getDescendantsOfKind(SyntaxKind.WhileStatement));
|
|
229
|
-
|
|
230
|
-
if (statements.length > 30 || ifStatements.length > 10 || switchStatements.length > 2 || loops.length > 5) {
|
|
231
|
-
const message = `SRP VIOLATION in ${fileName}::${funcName}: high complexity (${statements.length} statements, ${ifStatements.length} ifs, ${switchStatements.length} switches) - extract responsibilities`;
|
|
232
|
-
this.pushFinding('solid.srp.high_complexity', 'critical', sf, func, message, this.findings);
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const classes = sf.getClasses();
|
|
237
|
-
classes.forEach(cls => {
|
|
238
|
-
const className = cls.getName() || 'AnonymousClass';
|
|
239
|
-
const methods = cls.getMethods();
|
|
240
|
-
|
|
241
|
-
if (methods.length > 20) {
|
|
242
|
-
const message = `SRP VIOLATION in ${fileName}::${className}: God class with ${methods.length} methods - split into focused classes`;
|
|
243
|
-
this.pushFinding('solid.srp.god_class', 'critical', sf, cls, message, this.findings);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
analyzeISP(sf) {
|
|
250
|
-
const interfaces = sf.getInterfaces();
|
|
251
|
-
|
|
252
|
-
interfaces.forEach(iface => {
|
|
253
|
-
const interfaceName = iface.getName();
|
|
254
|
-
const properties = iface.getProperties();
|
|
255
|
-
const methods = iface.getMethods();
|
|
256
|
-
|
|
257
|
-
if (properties.length > 10) {
|
|
258
|
-
const message = `ISP VIOLATION: ${interfaceName} has ${properties.length} properties - split into focused interfaces`;
|
|
259
|
-
this.pushFinding('solid.isp.fat_interface', 'critical', sf, iface, message, this.findings);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (methods.length > 0) {
|
|
263
|
-
const methodConcerns = methods.map(m => this.detectMethodConcern(m.getName()));
|
|
264
|
-
const uniqueConcerns = new Set(methodConcerns.filter(c => c !== 'unknown'));
|
|
265
|
-
|
|
266
|
-
if (uniqueConcerns.size >= 3) {
|
|
267
|
-
const message = `ISP VIOLATION: ${interfaceName} mixes ${uniqueConcerns.size} concerns (${Array.from(uniqueConcerns).join(', ')}) - segregate into focused interfaces`;
|
|
268
|
-
this.pushFinding('solid.isp.multiple_concerns', 'critical', sf, iface, message, this.findings);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
detectMethodConcern(methodName) {
|
|
275
|
-
const name = methodName.toLowerCase();
|
|
276
|
-
|
|
277
|
-
if (/get|fetch|load|read|find|query/i.test(name)) return 'data-access';
|
|
278
|
-
if (/set|save|create|update|delete|remove/i.test(name)) return 'data-mutation';
|
|
279
|
-
if (/validate|check|verify/i.test(name)) return 'validation';
|
|
280
|
-
if (/format|parse|transform/i.test(name)) return 'transformation';
|
|
281
|
-
if (/render|display|show/i.test(name)) return 'rendering';
|
|
282
|
-
|
|
283
|
-
return 'unknown';
|
|
36
|
+
analyzeOCP(sf, findings, pushFinding);
|
|
37
|
+
analyzeDIP(sf, findings, pushFinding);
|
|
38
|
+
analyzeSRP(sf, findings, pushFinding);
|
|
39
|
+
analyzeISP(sf, findings, pushFinding);
|
|
284
40
|
}
|
|
285
41
|
}
|
|
286
42
|
|
|
287
|
-
module.exports =
|
|
43
|
+
module.exports = AndroidSOLIDAnalyzer;
|