pumuki 6.3.10 → 6.3.12
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 +4 -1
- package/VERSION +1 -1
- package/core/facts/detectors/text/ios.test.ts +74 -0
- package/core/facts/detectors/text/ios.ts +145 -0
- package/core/facts/detectors/typescript/index.test.ts +147 -0
- package/core/facts/detectors/typescript/index.ts +288 -0
- package/core/facts/extractHeuristicFacts.ts +33 -0
- package/core/gate/Finding.ts +5 -0
- package/core/rules/presets/astHeuristicsRuleSet.test.ts +55 -0
- package/core/rules/presets/heuristics/android.test.ts +37 -0
- package/core/rules/presets/heuristics/browser.test.ts +37 -0
- package/core/rules/presets/heuristics/fsCallbacks.test.ts +27 -0
- package/core/rules/presets/heuristics/fsCallbacksFileOperationsRules.test.ts +56 -0
- package/core/rules/presets/heuristics/fsCallbacksMetadataRules.test.ts +57 -0
- package/core/rules/presets/heuristics/fsPromises.test.ts +27 -0
- package/core/rules/presets/heuristics/fsPromisesFileOperations.test.ts +44 -0
- package/core/rules/presets/heuristics/fsPromisesMetadataRules.test.ts +47 -0
- package/core/rules/presets/heuristics/fsSync.test.ts +34 -0
- package/core/rules/presets/heuristics/fsSyncAppendRules.test.ts +17 -0
- package/core/rules/presets/heuristics/fsSyncDescriptorRules.test.ts +52 -0
- package/core/rules/presets/heuristics/fsSyncFileOperationsRules.test.ts +55 -0
- package/core/rules/presets/heuristics/fsSyncPathRules.test.ts +8 -0
- package/core/rules/presets/heuristics/ios.test.ts +50 -0
- package/core/rules/presets/heuristics/ios.ts +198 -0
- package/core/rules/presets/heuristics/process.test.ts +51 -0
- package/core/rules/presets/heuristics/security.test.ts +35 -0
- package/core/rules/presets/heuristics/securityCredentialsRules.test.ts +42 -0
- package/core/rules/presets/heuristics/securityCryptoRules.test.ts +37 -0
- package/core/rules/presets/heuristics/securityJwtRules.test.ts +37 -0
- package/core/rules/presets/heuristics/securityTlsRules.test.ts +32 -0
- package/core/rules/presets/heuristics/typescript.test.ts +56 -0
- package/core/rules/presets/heuristics/typescript.ts +113 -0
- package/core/rules/presets/heuristics/vm.test.ts +17 -0
- package/core/rules/presets/iosEnterpriseRuleSet.test.ts +33 -0
- package/core/rules/presets/iosNonNegotiableRuleSet.test.ts +30 -0
- package/core/rules/presets/rulePackVersions.test.ts +1 -1
- package/core/rules/presets/rulePackVersions.ts +1 -1
- package/core/utils/stableStringify.test.ts +22 -0
- package/docs/INSTALLATION.md +1 -0
- package/docs/PUMUKI_FULL_VALIDATION_CHECKLIST.md +151 -0
- package/docs/REFRACTOR_PROGRESS.md +110 -2
- package/docs/USAGE.md +1 -0
- package/docs/evidence-v2.1.md +5 -0
- package/docs/rule-packs/README.md +14 -1
- package/docs/rule-packs/engineering-baseline.md +54 -0
- package/docs/rule-packs/heuristics.md +1 -1
- package/docs/rule-packs/ios.md +11 -0
- package/integrations/config/heuristics.test.ts +54 -0
- package/integrations/config/loadProjectRules.test.ts +148 -0
- package/integrations/config/loadProjectRules.ts +21 -3
- package/integrations/config/projectRules.test.ts +111 -0
- package/integrations/config/projectRulesSchema.test.ts +148 -0
- package/integrations/config/skillsCompilerTemplates.test.ts +67 -0
- package/integrations/config/skillsCompilerTemplates.ts +121 -0
- package/integrations/config/skillsLock.test.ts +154 -0
- package/integrations/config/skillsPolicy.test.ts +118 -0
- package/integrations/config/skillsRuleSet.ts +12 -0
- package/integrations/config/skillsSources.test.ts +119 -0
- package/integrations/evidence/buildEvidence.ts +32 -3
- package/integrations/evidence/generateEvidence.test.ts +123 -0
- package/integrations/evidence/readEvidence.test.ts +106 -0
- package/integrations/evidence/schema.test.ts +170 -0
- package/integrations/evidence/schema.ts +4 -0
- package/integrations/evidence/writeEvidence.test.ts +180 -0
- package/integrations/gate/stagePolicies.ts +18 -0
- package/integrations/git/findingTraceability.ts +313 -0
- package/integrations/git/runPlatformGateEvaluation.ts +55 -11
- package/integrations/git/runPlatformGateEvidence.ts +15 -2
- package/integrations/lifecycle/artifacts.ts +21 -1
- package/integrations/lifecycle/hookBlock.ts +6 -1
- package/integrations/lifecycle/remove.ts +162 -31
- package/package.json +1 -1
- package/skills.lock.json +149 -2
- package/skills.sources.json +15 -1
package/README.md
CHANGED
|
@@ -104,6 +104,7 @@ npx --yes pumuki remove
|
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
`pumuki remove` performs managed uninstall + artifact purge in one command.
|
|
107
|
+
`npm uninstall pumuki` only removes the dependency from `package.json`; it does not remove managed hooks or lifecycle state.
|
|
107
108
|
|
|
108
109
|
## Lifecycle Commands
|
|
109
110
|
|
|
@@ -118,7 +119,8 @@ The `pumuki` binary provides repository lifecycle operations:
|
|
|
118
119
|
| `pumuki doctor` | Safety checks (hook drift, tracked `node_modules`, lifecycle state) |
|
|
119
120
|
| `pumuki status` | Current lifecycle snapshot |
|
|
120
121
|
|
|
121
|
-
`pumuki remove` is dependency-safe by design: it never deletes non-Pumuki third-party dependencies.
|
|
122
|
+
`pumuki remove` is dependency-safe by design: it never deletes non-Pumuki third-party dependencies and preserves pre-existing third-party empty directories.
|
|
123
|
+
Use `pumuki remove` (or `pumuki uninstall --purge-artifacts` + `npm uninstall pumuki`) for complete teardown.
|
|
122
124
|
|
|
123
125
|
## Gate Commands
|
|
124
126
|
|
|
@@ -140,6 +142,7 @@ Dedicated gate binaries are available:
|
|
|
140
142
|
- Rules are evaluated with platform-aware packs and project overrides.
|
|
141
143
|
- Gate applies stage policy thresholds.
|
|
142
144
|
- Evidence is generated as deterministic, machine-readable contract.
|
|
145
|
+
- Findings carry deterministic traceability when available (`file`, `lines`, `matchedBy`, `source`).
|
|
143
146
|
|
|
144
147
|
### Default stage policies
|
|
145
148
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.11
|
|
@@ -3,9 +3,20 @@ import test from 'node:test';
|
|
|
3
3
|
import {
|
|
4
4
|
hasSwiftAnyViewUsage,
|
|
5
5
|
hasSwiftCallbackStyleSignature,
|
|
6
|
+
hasSwiftDispatchGroupUsage,
|
|
7
|
+
hasSwiftDispatchQueueUsage,
|
|
8
|
+
hasSwiftDispatchSemaphoreUsage,
|
|
6
9
|
hasSwiftForceCastUsage,
|
|
7
10
|
hasSwiftForceTryUsage,
|
|
8
11
|
hasSwiftForceUnwrap,
|
|
12
|
+
hasSwiftNavigationViewUsage,
|
|
13
|
+
hasSwiftObservableObjectUsage,
|
|
14
|
+
hasSwiftOnTapGestureUsage,
|
|
15
|
+
hasSwiftOperationQueueUsage,
|
|
16
|
+
hasSwiftStringFormatUsage,
|
|
17
|
+
hasSwiftTaskDetachedUsage,
|
|
18
|
+
hasSwiftUIScreenMainBoundsUsage,
|
|
19
|
+
hasSwiftUncheckedSendableUsage,
|
|
9
20
|
} from './ios';
|
|
10
21
|
|
|
11
22
|
test('hasSwiftForceUnwrap detecta force unwrap postfix en expresiones', () => {
|
|
@@ -81,3 +92,66 @@ test('hasSwiftCallbackStyleSignature ignora usos fuera de firmas callback', () =
|
|
|
81
92
|
const source = `\n// @escaping completion: @escaping () -> Void\nlet text = "@escaping completion: @escaping () -> Void"\n`;
|
|
82
93
|
assert.equal(hasSwiftCallbackStyleSignature(source), false);
|
|
83
94
|
});
|
|
95
|
+
|
|
96
|
+
test('detecta primitivas GCD y OperationQueue en codigo ejecutable', () => {
|
|
97
|
+
const source = `
|
|
98
|
+
DispatchQueue.main.async { }
|
|
99
|
+
DispatchGroup()
|
|
100
|
+
DispatchSemaphore(value: 1)
|
|
101
|
+
OperationQueue()
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
assert.equal(hasSwiftDispatchQueueUsage(source), true);
|
|
105
|
+
assert.equal(hasSwiftDispatchGroupUsage(source), true);
|
|
106
|
+
assert.equal(hasSwiftDispatchSemaphoreUsage(source), true);
|
|
107
|
+
assert.equal(hasSwiftOperationQueueUsage(source), true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('hasSwiftTaskDetachedUsage detecta Task.detached y evita Task normal', () => {
|
|
111
|
+
const positive = `
|
|
112
|
+
Task.detached(priority: .background) { }
|
|
113
|
+
`;
|
|
114
|
+
const negative = `
|
|
115
|
+
Task {
|
|
116
|
+
await work()
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
assert.equal(hasSwiftTaskDetachedUsage(positive), true);
|
|
120
|
+
assert.equal(hasSwiftTaskDetachedUsage(negative), false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
124
|
+
const source = `
|
|
125
|
+
final class LegacyBox: @unchecked Sendable {}
|
|
126
|
+
`;
|
|
127
|
+
assert.equal(hasSwiftUncheckedSendableUsage(source), true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('detectores SwiftUI modernos detectan patrones legacy relevantes', () => {
|
|
131
|
+
const source = `
|
|
132
|
+
final class LegacyViewModel: ObservableObject {}
|
|
133
|
+
NavigationView { Text("x") }
|
|
134
|
+
Text("Tap").onTapGesture { }
|
|
135
|
+
let value = String(format: "%d", 1)
|
|
136
|
+
let width = UIScreen.main.bounds.width
|
|
137
|
+
`;
|
|
138
|
+
assert.equal(hasSwiftObservableObjectUsage(source), true);
|
|
139
|
+
assert.equal(hasSwiftNavigationViewUsage(source), true);
|
|
140
|
+
assert.equal(hasSwiftOnTapGestureUsage(source), true);
|
|
141
|
+
assert.equal(hasSwiftStringFormatUsage(source), true);
|
|
142
|
+
assert.equal(hasSwiftUIScreenMainBoundsUsage(source), true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('detectores legacy ignoran strings y comentarios', () => {
|
|
146
|
+
const source = `
|
|
147
|
+
// Task.detached { }
|
|
148
|
+
let a = "Task.detached { }"
|
|
149
|
+
let b = "NavigationView { }"
|
|
150
|
+
let c = "String(format: \\\"%d\\\", 1)"
|
|
151
|
+
let d = "UIScreen.main.bounds.width"
|
|
152
|
+
`;
|
|
153
|
+
assert.equal(hasSwiftTaskDetachedUsage(source), false);
|
|
154
|
+
assert.equal(hasSwiftNavigationViewUsage(source), false);
|
|
155
|
+
assert.equal(hasSwiftStringFormatUsage(source), false);
|
|
156
|
+
assert.equal(hasSwiftUIScreenMainBoundsUsage(source), false);
|
|
157
|
+
});
|
|
@@ -67,6 +67,151 @@ export const hasSwiftAnyViewUsage = (source: string): boolean => {
|
|
|
67
67
|
});
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
export const hasSwiftDispatchQueueUsage = (source: string): boolean => {
|
|
71
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
72
|
+
if (current !== 'D' || !hasIdentifierAt(swiftSource, index, 'DispatchQueue')) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const dotIndex = nextNonWhitespaceIndex(swiftSource, index + 'DispatchQueue'.length);
|
|
77
|
+
return dotIndex >= 0 && swiftSource[dotIndex] === '.';
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const hasSwiftDispatchGroupUsage = (source: string): boolean => {
|
|
82
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
83
|
+
if (current !== 'D') {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return hasIdentifierAt(swiftSource, index, 'DispatchGroup');
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const hasSwiftDispatchSemaphoreUsage = (source: string): boolean => {
|
|
92
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
93
|
+
if (current !== 'D') {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return hasIdentifierAt(swiftSource, index, 'DispatchSemaphore');
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const hasSwiftOperationQueueUsage = (source: string): boolean => {
|
|
102
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
103
|
+
if (current !== 'O') {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return hasIdentifierAt(swiftSource, index, 'OperationQueue');
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const hasSwiftTaskDetachedUsage = (source: string): boolean => {
|
|
112
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
113
|
+
if (current !== 'T' || !hasIdentifierAt(swiftSource, index, 'Task')) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const dotIndex = nextNonWhitespaceIndex(swiftSource, index + 'Task'.length);
|
|
118
|
+
if (dotIndex < 0 || swiftSource[dotIndex] !== '.') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const detachedIndex = nextNonWhitespaceIndex(swiftSource, dotIndex + 1);
|
|
123
|
+
return detachedIndex >= 0 && hasIdentifierAt(swiftSource, detachedIndex, 'detached');
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
128
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
129
|
+
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const sendableIndex = nextNonWhitespaceIndex(swiftSource, index + '@unchecked'.length);
|
|
134
|
+
return sendableIndex >= 0 && hasIdentifierAt(swiftSource, sendableIndex, 'Sendable');
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const hasSwiftObservableObjectUsage = (source: string): boolean => {
|
|
139
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
140
|
+
if (current !== 'O') {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return hasIdentifierAt(swiftSource, index, 'ObservableObject');
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const hasSwiftNavigationViewUsage = (source: string): boolean => {
|
|
149
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
150
|
+
if (current !== 'N') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return hasIdentifierAt(swiftSource, index, 'NavigationView');
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const hasSwiftOnTapGestureUsage = (source: string): boolean => {
|
|
159
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
160
|
+
if (current !== 'o') {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return hasIdentifierAt(swiftSource, index, 'onTapGesture');
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const hasSwiftStringFormatUsage = (source: string): boolean => {
|
|
169
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
170
|
+
if (current !== 'S' || !hasIdentifierAt(swiftSource, index, 'String')) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const openingParenIndex = nextNonWhitespaceIndex(swiftSource, index + 'String'.length);
|
|
175
|
+
if (openingParenIndex < 0 || swiftSource[openingParenIndex] !== '(') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const formatIndex = nextNonWhitespaceIndex(swiftSource, openingParenIndex + 1);
|
|
180
|
+
if (formatIndex < 0 || !hasIdentifierAt(swiftSource, formatIndex, 'format')) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const colonIndex = nextNonWhitespaceIndex(swiftSource, formatIndex + 'format'.length);
|
|
185
|
+
return colonIndex >= 0 && swiftSource[colonIndex] === ':';
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const hasSwiftUIScreenMainBoundsUsage = (source: string): boolean => {
|
|
190
|
+
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
191
|
+
if (current !== 'U' || !hasIdentifierAt(swiftSource, index, 'UIScreen')) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const dotMainIndex = nextNonWhitespaceIndex(swiftSource, index + 'UIScreen'.length);
|
|
196
|
+
if (dotMainIndex < 0 || swiftSource[dotMainIndex] !== '.') {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const mainIndex = nextNonWhitespaceIndex(swiftSource, dotMainIndex + 1);
|
|
201
|
+
if (mainIndex < 0 || !hasIdentifierAt(swiftSource, mainIndex, 'main')) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const dotBoundsIndex = nextNonWhitespaceIndex(swiftSource, mainIndex + 'main'.length);
|
|
206
|
+
if (dotBoundsIndex < 0 || swiftSource[dotBoundsIndex] !== '.') {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const boundsIndex = nextNonWhitespaceIndex(swiftSource, dotBoundsIndex + 1);
|
|
211
|
+
return boundsIndex >= 0 && hasIdentifierAt(swiftSource, boundsIndex, 'bounds');
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
70
215
|
export const hasSwiftForceTryUsage = (source: string): boolean => {
|
|
71
216
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
72
217
|
if (current !== 't' || !hasIdentifierAt(swiftSource, index, 'try')) {
|
|
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import {
|
|
4
4
|
hasAsyncPromiseExecutor,
|
|
5
|
+
hasConcreteDependencyInstantiation,
|
|
5
6
|
hasConsoleErrorCall,
|
|
6
7
|
hasConsoleLogCall,
|
|
7
8
|
hasDebuggerStatement,
|
|
@@ -9,9 +10,14 @@ import {
|
|
|
9
10
|
hasEmptyCatchClause,
|
|
10
11
|
hasEvalCall,
|
|
11
12
|
hasExplicitAnyType,
|
|
13
|
+
hasFrameworkDependencyImport,
|
|
12
14
|
hasFunctionConstructorUsage,
|
|
15
|
+
hasMixedCommandQueryClass,
|
|
16
|
+
hasMixedCommandQueryInterface,
|
|
17
|
+
hasOverrideMethodThrowingNotImplemented,
|
|
13
18
|
hasSetIntervalStringCallback,
|
|
14
19
|
hasSetTimeoutStringCallback,
|
|
20
|
+
hasTypeDiscriminatorSwitch,
|
|
15
21
|
hasWithStatement,
|
|
16
22
|
} from './index';
|
|
17
23
|
|
|
@@ -229,3 +235,144 @@ test('hasDebuggerStatement detecta nodos debugger', () => {
|
|
|
229
235
|
assert.equal(hasDebuggerStatement(debuggerAst), true);
|
|
230
236
|
assert.equal(hasDebuggerStatement(noDebuggerAst), false);
|
|
231
237
|
});
|
|
238
|
+
|
|
239
|
+
test('hasMixedCommandQueryClass detecta mezcla command/query en la misma clase', () => {
|
|
240
|
+
const mixedClassAst = {
|
|
241
|
+
type: 'ClassDeclaration',
|
|
242
|
+
body: {
|
|
243
|
+
type: 'ClassBody',
|
|
244
|
+
body: [
|
|
245
|
+
{ type: 'ClassMethod', key: { type: 'Identifier', name: 'getById' } },
|
|
246
|
+
{ type: 'ClassMethod', key: { type: 'Identifier', name: 'save' } },
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const queryOnlyClassAst = {
|
|
251
|
+
type: 'ClassDeclaration',
|
|
252
|
+
body: {
|
|
253
|
+
type: 'ClassBody',
|
|
254
|
+
body: [{ type: 'ClassMethod', key: { type: 'Identifier', name: 'getById' } }],
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
assert.equal(hasMixedCommandQueryClass(mixedClassAst), true);
|
|
259
|
+
assert.equal(hasMixedCommandQueryClass(queryOnlyClassAst), false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('hasMixedCommandQueryInterface detecta mezcla command/query en la misma interfaz', () => {
|
|
263
|
+
const mixedInterfaceAst = {
|
|
264
|
+
type: 'TSInterfaceDeclaration',
|
|
265
|
+
body: {
|
|
266
|
+
type: 'TSInterfaceBody',
|
|
267
|
+
body: [
|
|
268
|
+
{ type: 'TSMethodSignature', key: { type: 'Identifier', name: 'findById' } },
|
|
269
|
+
{ type: 'TSMethodSignature', key: { type: 'Identifier', name: 'create' } },
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
const queryOnlyInterfaceAst = {
|
|
274
|
+
type: 'TSInterfaceDeclaration',
|
|
275
|
+
body: {
|
|
276
|
+
type: 'TSInterfaceBody',
|
|
277
|
+
body: [{ type: 'TSMethodSignature', key: { type: 'Identifier', name: 'findById' } }],
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
assert.equal(hasMixedCommandQueryInterface(mixedInterfaceAst), true);
|
|
282
|
+
assert.equal(hasMixedCommandQueryInterface(queryOnlyInterfaceAst), false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('hasTypeDiscriminatorSwitch detecta switch por tipo/kind con multiples cases', () => {
|
|
286
|
+
const switchAst = {
|
|
287
|
+
type: 'SwitchStatement',
|
|
288
|
+
discriminant: {
|
|
289
|
+
type: 'MemberExpression',
|
|
290
|
+
computed: false,
|
|
291
|
+
object: { type: 'Identifier', name: 'event' },
|
|
292
|
+
property: { type: 'Identifier', name: 'kind' },
|
|
293
|
+
},
|
|
294
|
+
cases: [
|
|
295
|
+
{ type: 'SwitchCase', test: { type: 'StringLiteral', value: 'a' } },
|
|
296
|
+
{ type: 'SwitchCase', test: { type: 'StringLiteral', value: 'b' } },
|
|
297
|
+
],
|
|
298
|
+
};
|
|
299
|
+
const nonDiscriminatorAst = {
|
|
300
|
+
type: 'SwitchStatement',
|
|
301
|
+
discriminant: { type: 'Identifier', name: 'index' },
|
|
302
|
+
cases: [
|
|
303
|
+
{ type: 'SwitchCase', test: { type: 'NumericLiteral', value: 1 } },
|
|
304
|
+
{ type: 'SwitchCase', test: { type: 'NumericLiteral', value: 2 } },
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
assert.equal(hasTypeDiscriminatorSwitch(switchAst), true);
|
|
309
|
+
assert.equal(hasTypeDiscriminatorSwitch(nonDiscriminatorAst), false);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('hasOverrideMethodThrowingNotImplemented detecta override con throw not implemented', () => {
|
|
313
|
+
const overrideThrowAst = {
|
|
314
|
+
type: 'ClassMethod',
|
|
315
|
+
override: true,
|
|
316
|
+
body: {
|
|
317
|
+
type: 'BlockStatement',
|
|
318
|
+
body: [
|
|
319
|
+
{
|
|
320
|
+
type: 'ThrowStatement',
|
|
321
|
+
argument: {
|
|
322
|
+
type: 'NewExpression',
|
|
323
|
+
callee: { type: 'Identifier', name: 'Error' },
|
|
324
|
+
arguments: [{ type: 'StringLiteral', value: 'Not implemented' }],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
const overrideValidAst = {
|
|
331
|
+
type: 'ClassMethod',
|
|
332
|
+
override: true,
|
|
333
|
+
body: {
|
|
334
|
+
type: 'BlockStatement',
|
|
335
|
+
body: [{ type: 'ReturnStatement', argument: { type: 'NumericLiteral', value: 1 } }],
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
assert.equal(hasOverrideMethodThrowingNotImplemented(overrideThrowAst), true);
|
|
340
|
+
assert.equal(hasOverrideMethodThrowingNotImplemented(overrideValidAst), false);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('hasFrameworkDependencyImport detecta import/require de frameworks concretos', () => {
|
|
344
|
+
const importAst = {
|
|
345
|
+
type: 'ImportDeclaration',
|
|
346
|
+
source: { type: 'StringLiteral', value: '@nestjs/common' },
|
|
347
|
+
};
|
|
348
|
+
const requireAst = {
|
|
349
|
+
type: 'CallExpression',
|
|
350
|
+
callee: { type: 'Identifier', name: 'require' },
|
|
351
|
+
arguments: [{ type: 'StringLiteral', value: '@prisma/client' }],
|
|
352
|
+
};
|
|
353
|
+
const localRequireAst = {
|
|
354
|
+
type: 'CallExpression',
|
|
355
|
+
callee: { type: 'Identifier', name: 'require' },
|
|
356
|
+
arguments: [{ type: 'StringLiteral', value: './local' }],
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
assert.equal(hasFrameworkDependencyImport(importAst), true);
|
|
360
|
+
assert.equal(hasFrameworkDependencyImport(requireAst), true);
|
|
361
|
+
assert.equal(hasFrameworkDependencyImport(localRequireAst), false);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('hasConcreteDependencyInstantiation detecta instanciacion directa de dependencias concretas', () => {
|
|
365
|
+
const concreteAst = {
|
|
366
|
+
type: 'NewExpression',
|
|
367
|
+
callee: { type: 'Identifier', name: 'PrismaClient' },
|
|
368
|
+
arguments: [],
|
|
369
|
+
};
|
|
370
|
+
const localAst = {
|
|
371
|
+
type: 'NewExpression',
|
|
372
|
+
callee: { type: 'Identifier', name: 'LocalBuilder' },
|
|
373
|
+
arguments: [],
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
assert.equal(hasConcreteDependencyInstantiation(concreteAst), true);
|
|
377
|
+
assert.equal(hasConcreteDependencyInstantiation(localAst), false);
|
|
378
|
+
});
|