pumuki 6.3.326 → 6.3.327
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.
|
@@ -150,6 +150,7 @@ import {
|
|
|
150
150
|
hasSwiftOnChangeTaskUsage,
|
|
151
151
|
collectSwiftOnChangeTaskLines,
|
|
152
152
|
collectSwiftOnChangeReadonlyVarLines,
|
|
153
|
+
collectSwiftStrongDelegateReferenceLines,
|
|
153
154
|
hasSwiftOnChangeReadonlyVarUsage,
|
|
154
155
|
hasSwiftOnAppearTaskUsage,
|
|
155
156
|
collectSwiftOnAppearTaskLines,
|
|
@@ -1638,7 +1639,9 @@ final class CheckoutCoordinator {
|
|
|
1638
1639
|
`;
|
|
1639
1640
|
|
|
1640
1641
|
assert.equal(hasSwiftStrongDelegateReferenceUsage(positive), true);
|
|
1642
|
+
assert.deepEqual(collectSwiftStrongDelegateReferenceLines(positive), [3, 4]);
|
|
1641
1643
|
assert.equal(hasSwiftStrongDelegateReferenceUsage(negative), false);
|
|
1644
|
+
assert.deepEqual(collectSwiftStrongDelegateReferenceLines(negative), []);
|
|
1642
1645
|
});
|
|
1643
1646
|
|
|
1644
1647
|
test('hasSwiftStrongDelegateReferenceUsage no marca propiedades no delegate', () => {
|
|
@@ -1474,6 +1474,25 @@ export const hasSwiftStrongDelegateReferenceUsage = (source: string): boolean =>
|
|
|
1474
1474
|
});
|
|
1475
1475
|
};
|
|
1476
1476
|
|
|
1477
|
+
export const collectSwiftStrongDelegateReferenceLines = (source: string): readonly number[] => {
|
|
1478
|
+
const delegatePropertyPattern =
|
|
1479
|
+
/\b(?:var|let)\s+(?:[A-Za-z_][A-Za-z0-9_]*(?:Delegate|DataSource)|delegate|dataSource)\s*:\s*(?:any\s+)?[A-Za-z_][A-Za-z0-9_]*(?:Delegate|DataSource)\b/;
|
|
1480
|
+
const lines: number[] = [];
|
|
1481
|
+
|
|
1482
|
+
source.split(/\r?\n/).forEach((line, index) => {
|
|
1483
|
+
const sanitizedLine = stripSwiftLineForSemanticScan(line);
|
|
1484
|
+
if (!delegatePropertyPattern.test(sanitizedLine)) {
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
if (/\bweak\s+var\b/.test(sanitizedLine)) {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
lines.push(index + 1);
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
return sortedUniqueLines(lines);
|
|
1494
|
+
};
|
|
1495
|
+
|
|
1477
1496
|
const swiftStrongSelfEscapingClosurePatterns = [
|
|
1478
1497
|
/\bTask\s*(?:\([^)]*\))?\s*\{/g,
|
|
1479
1498
|
/\bDispatchQueue\s*\.\s*[A-Za-z0-9_.]+\s*\.\s*async(?:After)?\s*(?:\([^)]*\))?\s*\{/g,
|
|
@@ -788,7 +788,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
788
788
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnAppearTaskUsage, locateLines: TextIOS.collectSwiftOnAppearTaskLines, primaryNode: (lines) => ({ kind: 'call', name: 'Task launched inside SwiftUI onAppear', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: .task { ... } on the view', lines }], why: 'A Task launched from onAppear is not owned by the SwiftUI view lifecycle in the same way as .task.', impact: 'Async work can outlive view disappearance or require manual cancellation, and the gate must point to the exact Task line instead of blocking the whole file.', expected_fix: 'Move the async work from .onAppear { Task { ... } } into .task { ... } so SwiftUI owns automatic cancellation. Keep onAppear only for synchronous side effects such as analytics.', ruleId: 'heuristics.ios.swiftui.onappear-task.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONAPPEAR_TASK_AST', message: 'AST heuristic detected Task launched from SwiftUI onAppear; .task/.task(id:) provides lifecycle-aware cancellation.' },
|
|
789
789
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnChangeTaskUsage, locateLines: TextIOS.collectSwiftOnChangeTaskLines, primaryNode: (lines) => ({ kind: 'call', name: 'Task launched inside SwiftUI onChange', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: .task(id:) for value-dependent async work', lines }], why: 'A Task launched from onChange makes value-dependent async work manual instead of tying cancellation to the changing value.', impact: 'Search, load or refresh work can race after state changes because cancellation is not expressed through SwiftUI .task(id:).', expected_fix: 'Move value-dependent async work from .onChange { Task { ... } } into .task(id: value) { ... }. Keep onChange only for synchronous derivations or analytics.', ruleId: 'heuristics.ios.swiftui.onchange-task.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST', message: 'AST heuristic detected Task launched from SwiftUI onChange; .task(id:) provides lifecycle-aware cancellation for value-dependent async work.' },
|
|
790
790
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnChangeReadonlyVarUsage, locateLines: TextIOS.collectSwiftOnChangeReadonlyVarLines, primaryNode: (lines) => ({ kind: 'property', name: 'var declared inside SwiftUI onChange closure', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: let for read-only derived value inside onChange', lines }], why: 'A local var inside onChange hides whether the closure is deriving a read-only value or mutating state as part of a reactive update.', impact: 'Reactive closures become harder to audit because unnecessary mutability can mask accidental state changes and value-dependent side effects.', expected_fix: 'Use let for read-only derived values inside onChange. Keep var only when the local value is intentionally mutated and extract complex mutation out of the view closure.', ruleId: 'heuristics.ios.swiftui.onchange-readonly-var.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_READONLY_VAR_AST', message: 'AST heuristic detected local var inside SwiftUI onChange; prefer let for read-only derived values.' },
|
|
791
|
-
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongDelegateReferenceUsage, ruleId: 'heuristics.ios.memory.strong-delegate.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_DELEGATE_AST', message: 'AST heuristic detected a strong delegate/dataSource reference; weak delegates remain the preferred baseline to avoid retain cycles.' },
|
|
791
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongDelegateReferenceUsage, locateLines: TextIOS.collectSwiftStrongDelegateReferenceLines, primaryNode: (lines) => ({ kind: 'property', name: 'strong delegate or dataSource property', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: weak var delegate/dataSource', lines }], why: 'Delegate and dataSource references usually point back to an owner or coordinator and should not be retained strongly by the callee.', impact: 'A strong delegate/dataSource property can create retain cycles between services, coordinators, view controllers or adapters, causing leaks that tests may not catch.', expected_fix: 'Declare delegate/dataSource references as weak var delegate or weak var dataSource, and keep the protocol class-bound with AnyObject when Swift requires weak storage.', ruleId: 'heuristics.ios.memory.strong-delegate.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_DELEGATE_AST', message: 'AST heuristic detected a strong delegate/dataSource reference; weak delegates remain the preferred baseline to avoid retain cycles.' },
|
|
792
792
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongSelfEscapingClosureUsage, ruleId: 'heuristics.ios.memory.strong-self-escaping-closure.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_SELF_ESCAPING_CLOSURE_AST', message: 'AST heuristic detected strong self capture in an escaping iOS closure; weak or unowned captures remain the preferred baseline when ownership is not explicit.' },
|
|
793
793
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUnownedSelfCaptureUsage, ruleId: 'heuristics.ios.memory.unowned-self-capture.ast', code: 'HEURISTICS_IOS_MEMORY_UNOWNED_SELF_CAPTURE_AST', message: 'AST heuristic detected unowned capture in an iOS closure; use weak capture unless lifetime is explicitly guaranteed.' },
|
|
794
794
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftManualMemoryManagementUsage, locateLines: TextIOS.collectSwiftManualMemoryManagementLines, primaryNode: (lines) => ({ kind: 'call', name: 'manual ARC bypass / Core Foundation retain-release', lines }), relatedNodes: (lines) => [{ kind: 'class', name: 'replacement: ARC-owned Swift object lifetime', lines }], why: 'Manual Unmanaged or Core Foundation retain/release bypasses normal Swift ARC ownership and needs an explicit bridge boundary.', impact: 'Manual memory ownership can leak or over-release objects, and without line evidence the gate cannot identify the unsafe bridge call.', expected_fix: 'Prefer ARC-owned Swift references. If a Core Foundation bridge is unavoidable, isolate it in infrastructure with documented ownership invariants and typed wrappers.', ruleId: 'heuristics.ios.memory.manual-management.ast', code: 'HEURISTICS_IOS_MEMORY_MANUAL_MANAGEMENT_AST', message: 'AST heuristic detected manual memory management that bypasses Swift ARC.' },
|
package/core/facts/index.test.ts
CHANGED
|
@@ -70,3 +70,33 @@ test('facts barrel expone extractHeuristicFacts y permite retorno vacio sin plat
|
|
|
70
70
|
|
|
71
71
|
assert.deepEqual(extractedFacts, []);
|
|
72
72
|
});
|
|
73
|
+
|
|
74
|
+
test('extractHeuristicFacts emite strong delegate iOS con linea y remediation accionable', () => {
|
|
75
|
+
const params: HeuristicExtractionParams = {
|
|
76
|
+
facts: [
|
|
77
|
+
{
|
|
78
|
+
kind: 'FileContent',
|
|
79
|
+
source: 'repo',
|
|
80
|
+
path: 'apps/ios/Infrastructure/Permissions/SystemPermissionsService.swift',
|
|
81
|
+
content: `
|
|
82
|
+
final class SystemPermissionsService {
|
|
83
|
+
var delegate: PermissionsServiceDelegate?
|
|
84
|
+
}
|
|
85
|
+
`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
detectedPlatforms: {
|
|
89
|
+
ios: { detected: true, confidence: 'HIGH' },
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const extractedFacts = extractHeuristicFacts(params);
|
|
94
|
+
const match = extractedFacts.find(
|
|
95
|
+
(fact) => fact.ruleId === 'heuristics.ios.memory.strong-delegate.ast'
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
assert.ok(match);
|
|
99
|
+
assert.deepEqual(match.lines, [3]);
|
|
100
|
+
assert.deepEqual(match.primary_node?.lines, [3]);
|
|
101
|
+
assert.match(match.expected_fix ?? '', /weak var delegate/i);
|
|
102
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.327",
|
|
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": {
|