pumuki 6.3.328 → 6.3.330
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.
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
collectSwiftAnyViewLines,
|
|
20
20
|
collectSwiftAnyTypeErasureLines,
|
|
21
21
|
collectSwiftCallbackStyleSignatureLines,
|
|
22
|
+
collectSwiftCombineSinkWithoutStoreLines,
|
|
22
23
|
collectSwiftDispatchGroupLines,
|
|
23
24
|
collectSwiftDispatchSemaphoreLines,
|
|
24
25
|
collectSwiftDummyAwaitLines,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
collectSwiftAdHocLoggingLines,
|
|
38
39
|
hasSwiftAdHocLoggingUsage,
|
|
39
40
|
hasSwiftAlamofireUsage,
|
|
41
|
+
collectSwiftAlamofireLines,
|
|
40
42
|
collectSwiftComposableArchitectureUsageLines,
|
|
41
43
|
hasSwiftEmptyCatchUsage,
|
|
42
44
|
collectSwiftNSErrorThrowLines,
|
|
@@ -182,6 +184,7 @@ import {
|
|
|
182
184
|
hasSwiftSensitiveUserDefaultsStorageUsage,
|
|
183
185
|
hasSwiftInsecureTransportUsage,
|
|
184
186
|
hasSwiftJSONSerializationUsage,
|
|
187
|
+
collectSwiftJSONSerializationLines,
|
|
185
188
|
hasSwiftExplicitColorStaticMemberUsage,
|
|
186
189
|
hasSwiftClosureBasedViewBuilderContentUsage,
|
|
187
190
|
collectSwiftForceUnwrapLines,
|
|
@@ -526,7 +529,9 @@ let ignoredAssign = ".assign(to: \\\\Model.title, on: model)"
|
|
|
526
529
|
`;
|
|
527
530
|
|
|
528
531
|
assert.equal(hasSwiftCombineSinkWithoutStoreUsage(source), true);
|
|
532
|
+
assert.deepEqual(collectSwiftCombineSinkWithoutStoreLines(source), [3, 8]);
|
|
529
533
|
assert.equal(hasSwiftCombineSinkWithoutStoreUsage(safe), false);
|
|
534
|
+
assert.deepEqual(collectSwiftCombineSinkWithoutStoreLines(safe), []);
|
|
530
535
|
});
|
|
531
536
|
|
|
532
537
|
test('hasSwiftProductionTestDoubleUsage detecta mocks/spies productivos y preserva strings', () => {
|
|
@@ -2042,8 +2047,12 @@ final class APIClient {
|
|
|
2042
2047
|
|
|
2043
2048
|
assert.equal(hasSwiftAlamofireUsage(source), true);
|
|
2044
2049
|
assert.equal(hasSwiftJSONSerializationUsage(source), true);
|
|
2050
|
+
assert.deepEqual(collectSwiftAlamofireLines(source), [2, 6]);
|
|
2051
|
+
assert.deepEqual(collectSwiftJSONSerializationLines(source), [7]);
|
|
2045
2052
|
assert.equal(hasSwiftAlamofireUsage(ignored), false);
|
|
2046
2053
|
assert.equal(hasSwiftJSONSerializationUsage(ignored), false);
|
|
2054
|
+
assert.deepEqual(collectSwiftAlamofireLines(ignored), []);
|
|
2055
|
+
assert.deepEqual(collectSwiftJSONSerializationLines(ignored), []);
|
|
2047
2056
|
});
|
|
2048
2057
|
|
|
2049
2058
|
test('detector iOS de seguridad detecta secretos en UserDefaults y AppStorage', () => {
|
|
@@ -129,8 +129,9 @@ export const hasSwiftWarningSuppressionUsage = (source: string): boolean => {
|
|
|
129
129
|
return collectSwiftWarningSuppressionLines(source).length > 0;
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
export const
|
|
132
|
+
export const collectSwiftCombineSinkWithoutStoreLines = (source: string): readonly number[] => {
|
|
133
133
|
const lines = source.split(/\r?\n/);
|
|
134
|
+
const matches: number[] = [];
|
|
134
135
|
|
|
135
136
|
for (let index = 0; index < lines.length; index += 1) {
|
|
136
137
|
const line = stripSwiftLineForSemanticScan(lines[index] ?? '');
|
|
@@ -144,11 +145,15 @@ export const hasSwiftCombineSinkWithoutStoreUsage = (source: string): boolean =>
|
|
|
144
145
|
.join('\n');
|
|
145
146
|
|
|
146
147
|
if (!/\.store\s*\(\s*in\s*:\s*&[A-Za-z_][A-Za-z0-9_]*\s*\)/.test(chain)) {
|
|
147
|
-
|
|
148
|
+
matches.push(index + 1);
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
return
|
|
152
|
+
return sortedUniqueLines(matches);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const hasSwiftCombineSinkWithoutStoreUsage = (source: string): boolean => {
|
|
156
|
+
return collectSwiftCombineSinkWithoutStoreLines(source).length > 0;
|
|
152
157
|
};
|
|
153
158
|
|
|
154
159
|
export const hasSwiftProductionTestDoubleUsage = (source: string): boolean => {
|
|
@@ -1758,15 +1763,23 @@ export const hasSwiftUnlocalizedDateFormatterUsage = (source: string): boolean =
|
|
|
1758
1763
|
return false;
|
|
1759
1764
|
};
|
|
1760
1765
|
|
|
1766
|
+
export const collectSwiftAlamofireLines = (source: string): readonly number[] => {
|
|
1767
|
+
return sortedUniqueLines([
|
|
1768
|
+
...collectSwiftRegexLines(source, /^\s*import\s+Alamofire\b/),
|
|
1769
|
+
...collectSwiftRegexLines(source, /\b(?:AF|Alamofire)\s*\.\s*request\b/),
|
|
1770
|
+
]);
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1761
1773
|
export const hasSwiftAlamofireUsage = (source: string): boolean => {
|
|
1762
|
-
return (
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1774
|
+
return collectSwiftAlamofireLines(source).length > 0;
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
export const collectSwiftJSONSerializationLines = (source: string): readonly number[] => {
|
|
1778
|
+
return collectSwiftRegexLines(source, /\bJSONSerialization\s*\./);
|
|
1766
1779
|
};
|
|
1767
1780
|
|
|
1768
1781
|
export const hasSwiftJSONSerializationUsage = (source: string): boolean => {
|
|
1769
|
-
return
|
|
1782
|
+
return collectSwiftJSONSerializationLines(source).length > 0;
|
|
1770
1783
|
};
|
|
1771
1784
|
|
|
1772
1785
|
export const hasSwiftSensitiveUserDefaultsStorageUsage = (source: string): boolean => {
|
|
@@ -774,7 +774,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
774
774
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForceTryUsage, locateLines: TextIOS.collectSwiftForceTryLines, primaryNode: (lines) => ({ kind: 'call', name: 'force try expression try!', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: do/catch or throwing boundary', lines }], why: 'Force try converts a throwable operation into an unconditional runtime crash instead of preserving the typed error boundary.', impact: 'A recoverable domain, network, persistence or decoding error can terminate the app and bypass user-facing recovery, telemetry and tests.', expected_fix: 'Replace try! with do/catch, try await propagation, throws on the current boundary, or a guarded fallback that handles the error explicitly.', ruleId: 'heuristics.ios.force-try.ast', code: 'HEURISTICS_IOS_FORCE_TRY_AST', message: 'AST heuristic detected force try usage.' },
|
|
775
775
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForceCastUsage, locateLines: TextIOS.collectSwiftForceCastLines, primaryNode: (lines) => ({ kind: 'call', name: 'force cast expression as!', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: conditional cast or typed boundary', lines }], why: 'Force cast converts a type mismatch into an unconditional runtime crash instead of preserving a checked domain or presentation boundary.', impact: 'Unexpected payloads, dependency substitutions or navigation models can terminate the app instead of producing a recoverable validation path.', expected_fix: 'Replace as! with as?, guard let, pattern matching, generic constraints, protocol boundaries, or a typed mapper that validates the runtime value explicitly.', ruleId: 'heuristics.ios.force-cast.ast', code: 'HEURISTICS_IOS_FORCE_CAST_AST', message: 'AST heuristic detected force cast usage.' },
|
|
776
776
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath, isApprovedIOSBridgePath], detect: TextIOS.hasSwiftCallbackStyleSignature, locateLines: TextIOS.collectSwiftCallbackStyleSignatureLines, primaryNode: (lines) => ({ kind: 'call', name: 'escaping callback-style API signature', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: async/await API or explicit bridge adapter', lines }], why: 'Callback-style completion APIs outside bridge layers bypass Swift structured concurrency and make cancellation, isolation and error flow implicit.', impact: 'Consumers must reason about escaping lifetime, actor hops and callback ordering manually, which increases race, leak and flaky-test risk in production iOS flows.', expected_fix: 'Expose async/await or AsyncSequence APIs in production boundaries. Keep callbacks only inside approved bridge/adapters that wrap legacy SDKs and document the conversion point explicitly.', ruleId: 'heuristics.ios.callback-style.ast', code: 'HEURISTICS_IOS_CALLBACK_STYLE_AST', message: 'AST heuristic detected callback-style API signature outside bridge layers.' },
|
|
777
|
-
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCombineSinkWithoutStoreUsage, ruleId: 'heuristics.ios.combine.sink-without-store.ast', code: 'HEURISTICS_IOS_COMBINE_SINK_WITHOUT_STORE_AST', message: 'AST heuristic detected Combine sink without store(in:); keep cancellables retained explicitly.' },
|
|
777
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCombineSinkWithoutStoreUsage, locateLines: TextIOS.collectSwiftCombineSinkWithoutStoreLines, primaryNode: (lines) => ({ kind: 'call', name: 'Combine sink/assign subscription without store(in:)', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: .store(in: &cancellables)', lines }], why: 'Combine subscriptions returned by sink/assign are cancelled immediately unless the AnyCancellable is retained explicitly.', impact: 'Reactive flows can silently stop receiving values, leak intent across view model lifetimes, or behave differently after navigation if subscription ownership is not clear.', expected_fix: 'Store the returned AnyCancellable with .store(in: &cancellables) or assign it to an explicit cancellable property owned by the view model/service lifetime.', ruleId: 'heuristics.ios.combine.sink-without-store.ast', code: 'HEURISTICS_IOS_COMBINE_SINK_WITHOUT_STORE_AST', message: 'AST heuristic detected Combine sink without store(in:); keep cancellables retained explicitly.' },
|
|
778
778
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftProductionTestDoubleUsage, ruleId: 'heuristics.ios.testing.production-test-double.ast', code: 'HEURISTICS_IOS_TESTING_PRODUCTION_TEST_DOUBLE_AST', message: 'AST heuristic detected Mock/Fake/Spy/Stub usage in iOS production code.' },
|
|
779
779
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftDispatchQueueUsage, locateLines: TextIOS.collectSwiftDispatchQueueLines, primaryNode: (lines) => ({ kind: 'call', name: 'GCD DispatchQueue call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: structured concurrency Task/actor/MainActor boundary', lines }], why: 'DispatchQueue introduces unstructured GCD scheduling in production Swift code instead of preserving Swift concurrency cancellation, priority and actor isolation semantics.', impact: 'Manual queue hops make ordering, cancellation and main-actor safety harder to reason about, increasing race and flaky UI update risk.', expected_fix: 'Use async/await, Task, TaskGroup, actors, MainActor.run or isolated async APIs. Keep GCD only inside explicitly approved legacy bridge layers with documented ownership.', ruleId: 'heuristics.ios.dispatchqueue.ast', code: 'HEURISTICS_IOS_DISPATCHQUEUE_AST', message: 'AST heuristic detected DispatchQueue usage.' },
|
|
780
780
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftDispatchGroupUsage, locateLines: TextIOS.collectSwiftDispatchGroupLines, primaryNode: (lines) => ({ kind: 'call', name: 'GCD DispatchGroup call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: structured concurrency TaskGroup or async aggregation boundary', lines }], why: 'DispatchGroup is an unstructured coordination primitive that makes asynchronous control flow and cancellation implicit instead of modeled by Swift concurrency.', impact: 'Group coordination is harder to reason about and can hide waiting or deadlock risks in production code paths that should be expressed through TaskGroup or async aggregation.', expected_fix: 'Use TaskGroup, async let, await aggregation, actors or explicit async APIs. Keep DispatchGroup only inside approved legacy bridge layers with documented ownership and migration scope.', ruleId: 'heuristics.ios.dispatchgroup.ast', code: 'HEURISTICS_IOS_DISPATCHGROUP_AST', message: 'AST heuristic detected DispatchGroup usage.' },
|
|
@@ -808,8 +808,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
808
808
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveLoggingUsage, locateLines: TextIOS.collectSwiftSensitiveLoggingLines, primaryNode: (lines) => ({ kind: 'call', name: 'logging call with sensitive data', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: redacted structured log without token/password/email/userId', lines }], why: 'Production logs must not include tokens, credentials, emails or user identifiers because logs are copied to diagnostics, crash reports and external observability stores.', impact: 'Sensitive values can leak outside the device or backend trust boundary and make incident response depend on scrubbing historical logs.', expected_fix: 'Remove the sensitive value from the log, log a redacted marker, or emit structured metadata that cannot expose token, password, email, authorization or userId values.', ruleId: 'heuristics.ios.logging.sensitive-data.ast', code: 'HEURISTICS_IOS_LOGGING_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data in an iOS logging call.' },
|
|
809
809
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedSensitiveStringUsage, locateLines: TextIOS.collectSwiftHardcodedSensitiveStringLines, primaryNode: (lines) => ({ kind: 'property', name: 'hardcoded sensitive Swift string', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: Keychain, secure config or environment-specific secret source', lines }], why: 'Sensitive strings assigned directly to token/password/secret properties create static production secrets and cannot be rotated safely.', impact: 'Credentials or identifiers can leak through source, binaries, logs or screenshots and block release until the concrete assignment is removed.', expected_fix: 'Read sensitive values from Keychain, secure configuration, injected environment or a repository-approved secret provider. Keep user-facing copy in localization assets, not sensitive variables.', ruleId: 'heuristics.ios.security.hardcoded-sensitive-string.ast', code: 'HEURISTICS_IOS_SECURITY_HARDCODED_SENSITIVE_STRING_AST', message: 'AST heuristic detected hardcoded sensitive Swift string; Keychain, secure config or environment-specific secrets remain the preferred baseline.' },
|
|
810
810
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUnlocalizedDateFormatterUsage, ruleId: 'heuristics.ios.localization.unlocalized-dateformatter.ast', code: 'HEURISTICS_IOS_LOCALIZATION_UNLOCALIZED_DATEFORMATTER_AST', message: 'AST heuristic detected DateFormatter dateFormat usage without an explicit locale.' },
|
|
811
|
-
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAlamofireUsage, ruleId: 'heuristics.ios.networking.alamofire.ast', code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST', message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.' },
|
|
812
|
-
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, ruleId: 'heuristics.ios.json.jsonserialization.ast', code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST', message: 'AST heuristic detected JSONSerialization usage in iOS production code; Codable remains the preferred baseline for new code.' },
|
|
811
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAlamofireUsage, locateLines: TextIOS.collectSwiftAlamofireLines, primaryNode: (lines) => ({ kind: 'call', name: 'Alamofire import or request call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: native URLSession networking boundary', lines }], why: 'iOS production networking should use the native URLSession boundary unless the repository explicitly approves a third-party networking dependency.', impact: 'Third-party networking calls create a separate policy surface for retries, cancellation, privacy and observability, and whole-file findings are not actionable during consumer remediation.', expected_fix: 'Replace Alamofire imports and AF/Alamofire request calls with the repository-approved URLSession client or a native networking adapter.', ruleId: 'heuristics.ios.networking.alamofire.ast', code: 'HEURISTICS_IOS_NETWORKING_ALAMOFIRE_AST', message: 'AST heuristic detected Alamofire usage in iOS production code; URLSession remains the preferred baseline for new code.' },
|
|
812
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftJSONSerializationUsage, locateLines: TextIOS.collectSwiftJSONSerializationLines, primaryNode: (lines) => ({ kind: 'call', name: 'JSONSerialization call', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: Codable decoder or encoder', lines }], why: 'JSONSerialization bypasses typed decoding and makes payload contracts harder to validate than Codable models.', impact: 'Runtime casts and dictionary traversal can hide API drift until production and make the gate report vague file-level serialization debt.', expected_fix: 'Replace JSONSerialization calls with Codable DTOs using JSONDecoder or JSONEncoder at the repository networking boundary.', ruleId: 'heuristics.ios.json.jsonserialization.ast', code: 'HEURISTICS_IOS_JSON_JSONSERIALIZATION_AST', message: 'AST heuristic detected JSONSerialization usage in iOS production code; Codable remains the preferred baseline for new code.' },
|
|
813
813
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSensitiveUserDefaultsStorageUsage, locateLines: TextIOS.collectSwiftSensitiveUserDefaultsStorageLines, primaryNode: (lines) => ({ kind: 'call', name: 'sensitive UserDefaults/AppStorage storage', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: native Keychain or secure secret store', lines }], why: 'Tokens, passwords, credentials and session identifiers must not be stored in UserDefaults or AppStorage because those stores are not the approved secret boundary.', impact: 'Secrets persisted in UserDefaults/AppStorage can leak through backups, diagnostics or simple local inspection, and the gate needs the exact storage node to avoid whole-file remediation.', expected_fix: 'Move tokens, passwords, credentials and session identifiers to native Keychain or the repository-approved secure storage adapter. Keep UserDefaults/AppStorage only for non-sensitive preferences.', ruleId: 'heuristics.ios.security.userdefaults-sensitive-data.ast', code: 'HEURISTICS_IOS_SECURITY_USERDEFAULTS_SENSITIVE_DATA_AST', message: 'AST heuristic detected sensitive data stored in UserDefaults/AppStorage; native Keychain remains the preferred baseline for secrets.' },
|
|
814
814
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInsecureTransportUsage, locateLines: TextIOS.collectSwiftInsecureTransportLines, primaryNode: (lines) => ({ kind: 'call', name: 'insecure HTTP URL literal', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: HTTPS URLSession endpoint with ATS enabled', lines }], why: 'Production iOS networking must use HTTPS and keep App Transport Security enabled by default.', impact: 'HTTP endpoints can expose credentials, session data or payloads in transit and create release-blocking transport exceptions.', expected_fix: 'Replace http:// endpoints with https:// endpoints and keep ATS enabled. If a temporary exception is unavoidable, isolate it to a documented debug-only configuration, not production code.', ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected insecure HTTP transport in iOS production code.' },
|
|
815
815
|
{ platform: 'ios', pathCheck: isIOSInfoPlistPath, excludePaths: [], detect: TextIOS.hasSwiftInsecureTransportUsage, locateLines: TextIOS.collectSwiftInsecureTransportLines, primaryNode: (lines) => ({ kind: 'property', name: 'ATS NSAllowsArbitraryLoads enabled', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: ATS enabled with explicit HTTPS-only exceptions', lines }], why: 'NSAllowsArbitraryLoads disables the default iOS App Transport Security protection and should not be enabled for production builds.', impact: 'Permissive ATS configuration allows insecure transport globally, making networking violations harder to audit per endpoint.', expected_fix: 'Remove NSAllowsArbitraryLoads=true. Keep ATS enabled by default and add the narrowest documented domain exception only when the production requirement is approved.', ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected permissive App Transport Security configuration.' },
|
package/core/facts/index.test.ts
CHANGED
|
@@ -134,3 +134,81 @@ final class CartViewModel {
|
|
|
134
134
|
assert.deepEqual(match.primary_node?.lines, [5]);
|
|
135
135
|
assert.match(match.expected_fix ?? '', /\[weak self\]/i);
|
|
136
136
|
});
|
|
137
|
+
|
|
138
|
+
test('extractHeuristicFacts emite networking iOS con lineas y remediation accionable', () => {
|
|
139
|
+
const params: HeuristicExtractionParams = {
|
|
140
|
+
facts: [
|
|
141
|
+
{
|
|
142
|
+
kind: 'FileContent',
|
|
143
|
+
source: 'repo',
|
|
144
|
+
path: 'apps/ios/Infrastructure/Networking/BuyerAPIClient.swift',
|
|
145
|
+
content: `
|
|
146
|
+
import Alamofire
|
|
147
|
+
|
|
148
|
+
final class BuyerAPIClient {
|
|
149
|
+
func load() {
|
|
150
|
+
AF.request("https://api.example.com")
|
|
151
|
+
let object = try? JSONSerialization.jsonObject(with: Data())
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
detectedPlatforms: {
|
|
158
|
+
ios: { detected: true, confidence: 'HIGH' },
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const extractedFacts = extractHeuristicFacts(params);
|
|
163
|
+
const alamofire = extractedFacts.find(
|
|
164
|
+
(fact) => fact.ruleId === 'heuristics.ios.networking.alamofire.ast'
|
|
165
|
+
);
|
|
166
|
+
const jsonSerialization = extractedFacts.find(
|
|
167
|
+
(fact) => fact.ruleId === 'heuristics.ios.json.jsonserialization.ast'
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
assert.ok(alamofire);
|
|
171
|
+
assert.deepEqual(alamofire.lines, [2, 6]);
|
|
172
|
+
assert.deepEqual(alamofire.primary_node?.lines, [2, 6]);
|
|
173
|
+
assert.match(alamofire.expected_fix ?? '', /URLSession/i);
|
|
174
|
+
|
|
175
|
+
assert.ok(jsonSerialization);
|
|
176
|
+
assert.deepEqual(jsonSerialization.lines, [7]);
|
|
177
|
+
assert.deepEqual(jsonSerialization.primary_node?.lines, [7]);
|
|
178
|
+
assert.match(jsonSerialization.expected_fix ?? '', /Codable/i);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('extractHeuristicFacts emite Combine sink iOS con linea y remediation accionable', () => {
|
|
182
|
+
const params: HeuristicExtractionParams = {
|
|
183
|
+
facts: [
|
|
184
|
+
{
|
|
185
|
+
kind: 'FileContent',
|
|
186
|
+
source: 'repo',
|
|
187
|
+
path: 'apps/ios/Presentation/Features/Orders/OrdersViewModel.swift',
|
|
188
|
+
content: `
|
|
189
|
+
final class OrdersViewModel {
|
|
190
|
+
func bind() {
|
|
191
|
+
publisher
|
|
192
|
+
.sink { value in
|
|
193
|
+
self.render(value)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
detectedPlatforms: {
|
|
201
|
+
ios: { detected: true, confidence: 'HIGH' },
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const extractedFacts = extractHeuristicFacts(params);
|
|
206
|
+
const match = extractedFacts.find(
|
|
207
|
+
(fact) => fact.ruleId === 'heuristics.ios.combine.sink-without-store.ast'
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
assert.ok(match);
|
|
211
|
+
assert.deepEqual(match.lines, [5]);
|
|
212
|
+
assert.deepEqual(match.primary_node?.lines, [5]);
|
|
213
|
+
assert.match(match.expected_fix ?? '', /\.store\(in:/i);
|
|
214
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.330",
|
|
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": {
|