pumuki 6.3.307 → 6.3.309
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/CHANGELOG.md +6 -0
- package/core/facts/detectors/text/ios.test.ts +621 -0
- package/core/facts/detectors/text/ios.ts +722 -0
- package/core/facts/extractHeuristicFacts.ts +22 -4
- package/core/rules/presets/heuristics/ios.test.ts +83 -1
- package/core/rules/presets/heuristics/ios.ts +329 -0
- package/integrations/config/skillsDetectorRegistry.ts +111 -1
- package/integrations/evidence/buildEvidence.ts +72 -0
- package/integrations/git/aiGateRepoPolicyFindings.ts +2 -1
- package/integrations/lifecycle/adapter.templates.json +50 -1
- package/integrations/lifecycle/doctor.ts +41 -5
- package/integrations/lifecycle/preWriteLease.ts +5 -1
- package/package.json +1 -1
- package/scripts/framework-menu-matrix-canary-evidence.ts +42 -1
- package/scripts/framework-menu-matrix-canary-lib.ts +25 -2
- package/scripts/framework-menu-matrix-canary-scenario.ts +3 -1
- package/scripts/framework-menu-matrix-canary-types.ts +6 -1
- package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +31 -3
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +76 -2
- package/scripts/framework-menu-system-notifications-payloads.ts +1 -0
- package/scripts/framework-menu-system-notifications-remediation.ts +97 -2
- package/skills.lock.json +1 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
collectSwiftMakeSUTWithoutMemoryTrackingLines,
|
|
13
13
|
collectSwiftMixedTestingFrameworkLines,
|
|
14
14
|
collectSwiftQuickNimbleLines,
|
|
15
|
+
collectSwiftThirdPartyUiTestFrameworkLines,
|
|
15
16
|
collectSwiftWaitForExpectationsLines,
|
|
16
17
|
collectSwiftXCTestAssertionLines,
|
|
17
18
|
collectSwiftXCTUnwrapLines,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
collectSwiftCallbackStyleSignatureLines,
|
|
21
22
|
collectSwiftDispatchGroupLines,
|
|
22
23
|
collectSwiftDispatchSemaphoreLines,
|
|
24
|
+
collectSwiftDummyAwaitLines,
|
|
23
25
|
hasSwiftAnyViewUsage,
|
|
24
26
|
hasSwiftAnyTypeErasureUsage,
|
|
25
27
|
hasSwiftAsyncWithoutAwaitUsage,
|
|
@@ -29,7 +31,9 @@ import {
|
|
|
29
31
|
hasSwiftDispatchGroupUsage,
|
|
30
32
|
collectSwiftDispatchQueueLines,
|
|
31
33
|
hasSwiftDispatchQueueUsage,
|
|
34
|
+
hasSwiftDummyAwaitUsage,
|
|
32
35
|
hasSwiftDispatchSemaphoreUsage,
|
|
36
|
+
collectSwiftLongAsyncOperationWithoutCancellationCheckLines,
|
|
33
37
|
hasSwiftAdHocLoggingUsage,
|
|
34
38
|
hasSwiftAlamofireUsage,
|
|
35
39
|
collectSwiftComposableArchitectureUsageLines,
|
|
@@ -40,8 +44,30 @@ import {
|
|
|
40
44
|
hasSwiftPackageBranchDependencyUsage,
|
|
41
45
|
collectSwiftPackageToolsVersionBelow62Lines,
|
|
42
46
|
hasSwiftPackageToolsVersionBelow62Usage,
|
|
47
|
+
collectSwiftPackageDefaultIsolationNotMainActorLines,
|
|
48
|
+
hasSwiftPackageDefaultIsolationNotMainActorUsage,
|
|
49
|
+
collectSwiftPackageStrictConcurrencyBelowCompleteLines,
|
|
50
|
+
hasSwiftPackageStrictConcurrencyBelowCompleteUsage,
|
|
51
|
+
collectSwiftCrossFeatureImportLines,
|
|
52
|
+
hasSwiftCrossFeatureImportUsage,
|
|
53
|
+
collectSwiftExcessivePublicApiLines,
|
|
54
|
+
hasSwiftExcessivePublicApiUsage,
|
|
55
|
+
collectSwiftLayerDirectionViolationLines,
|
|
56
|
+
hasSwiftLayerDirectionViolationUsage,
|
|
43
57
|
collectSwiftStrictConcurrencyBelowCompleteLines,
|
|
44
58
|
hasSwiftStrictConcurrencyBelowCompleteUsage,
|
|
59
|
+
collectSwiftDefaultActorIsolationNotMainActorLines,
|
|
60
|
+
hasSwiftDefaultActorIsolationNotMainActorUsage,
|
|
61
|
+
collectSwiftUpcomingFeatureDisabledLines,
|
|
62
|
+
hasSwiftUpcomingFeatureDisabledUsage,
|
|
63
|
+
collectSwiftUiStateWithoutMainActorLines,
|
|
64
|
+
hasSwiftUiStateWithoutMainActorUsage,
|
|
65
|
+
collectSwiftSharedMutableStateWithoutActorLines,
|
|
66
|
+
hasSwiftSharedMutableStateWithoutActorUsage,
|
|
67
|
+
collectSwiftMainActorRunPatchLines,
|
|
68
|
+
hasSwiftMainActorRunPatchUsage,
|
|
69
|
+
collectSwiftNavigationPathWithoutRestorationLines,
|
|
70
|
+
hasSwiftNavigationPathWithoutRestorationUsage,
|
|
45
71
|
hasSwiftForEachIndicesUsage,
|
|
46
72
|
hasSwiftForEachSelfIdentityUsage,
|
|
47
73
|
collectSwiftForceCastLines,
|
|
@@ -70,6 +96,8 @@ import {
|
|
|
70
96
|
collectSwiftAnimationWithoutReduceMotionLines,
|
|
71
97
|
collectSwiftUiInlineActionLogicLines,
|
|
72
98
|
collectSwiftOnTapGestureWithoutButtonTraitLines,
|
|
99
|
+
collectSwiftGlassInteractiveOnStaticElementLines,
|
|
100
|
+
collectSwiftGlassEffectIDWithoutNamespaceLines,
|
|
73
101
|
collectSwiftForEachIndicesLines,
|
|
74
102
|
collectSwiftForEachSelfIdentityLines,
|
|
75
103
|
collectSwiftInlineForEachTransformLines,
|
|
@@ -86,6 +114,7 @@ import {
|
|
|
86
114
|
collectSwiftEnvironmentObjectLines,
|
|
87
115
|
hasSwiftEnvironmentObjectUsage,
|
|
88
116
|
hasSwiftLowContrastStaticColorPairUsage,
|
|
117
|
+
hasSwiftLongAsyncOperationWithoutCancellationCheckUsage,
|
|
89
118
|
hasSwiftMainThreadBlockingSleepUsage,
|
|
90
119
|
hasSwiftDirectSUTInstantiationWithoutMakeSUTUsage,
|
|
91
120
|
hasSwiftMakeSUTWithoutMemoryTrackingUsage,
|
|
@@ -125,6 +154,8 @@ import {
|
|
|
125
154
|
collectSwiftOnAppearTaskLines,
|
|
126
155
|
hasSwiftOnTapGestureUsage,
|
|
127
156
|
hasSwiftOnTapGestureWithoutButtonTraitUsage,
|
|
157
|
+
hasSwiftGlassInteractiveOnStaticElementUsage,
|
|
158
|
+
hasSwiftGlassEffectIDWithoutNamespaceUsage,
|
|
128
159
|
hasSwiftOperationQueueUsage,
|
|
129
160
|
hasSwiftContainsUserFilterUsage,
|
|
130
161
|
hasSwiftCustomSingletonUsage,
|
|
@@ -132,8 +163,11 @@ import {
|
|
|
132
163
|
hasSwiftPhysicalTextAlignmentUsage,
|
|
133
164
|
hasSwiftPreconcurrencyUsage,
|
|
134
165
|
hasSwiftProductionCommentUsage,
|
|
166
|
+
collectSwiftThreadCentricDebuggingLines,
|
|
167
|
+
hasSwiftThreadCentricDebuggingUsage,
|
|
135
168
|
hasSwiftProductionTestDoubleUsage,
|
|
136
169
|
hasSwiftQuickNimbleUsage,
|
|
170
|
+
hasSwiftThirdPartyUiTestFrameworkUsage,
|
|
137
171
|
hasSwiftTestDoubleWithoutProtocolConformanceUsage,
|
|
138
172
|
hasSwiftSheetIsPresentedUsage,
|
|
139
173
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
@@ -164,6 +198,8 @@ import {
|
|
|
164
198
|
hasSwiftManualMemoryManagementUsage,
|
|
165
199
|
collectSwiftNonPascalCaseTypeDeclarationLines,
|
|
166
200
|
hasSwiftNonPascalCaseTypeDeclarationUsage,
|
|
201
|
+
collectSwiftEndpointEnumLines,
|
|
202
|
+
hasSwiftEndpointEnumUsage,
|
|
167
203
|
collectSwiftCellCreationWithoutReuseLines,
|
|
168
204
|
hasSwiftCellCreationWithoutReuseUsage,
|
|
169
205
|
hasSwiftWaitForExpectationsUsage,
|
|
@@ -256,6 +292,46 @@ let ignored = "[unowned self]"
|
|
|
256
292
|
assert.equal(hasSwiftUnownedSelfCaptureUsage(safe), false);
|
|
257
293
|
});
|
|
258
294
|
|
|
295
|
+
test('hasSwiftLongAsyncOperationWithoutCancellationCheckUsage detecta bucles async sin cancelacion cooperativa', () => {
|
|
296
|
+
const source = `
|
|
297
|
+
final class SyncService {
|
|
298
|
+
func syncAll(items: [Item]) async throws {
|
|
299
|
+
for item in items {
|
|
300
|
+
try await upload(item)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
func poll() async {
|
|
305
|
+
while true {
|
|
306
|
+
await refresh()
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
`;
|
|
311
|
+
const safe = `
|
|
312
|
+
final class SyncService {
|
|
313
|
+
func syncAll(items: [Item]) async throws {
|
|
314
|
+
for item in items {
|
|
315
|
+
if Task.isCancelled { return }
|
|
316
|
+
try await upload(item)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
func poll() async throws {
|
|
321
|
+
while isRunning {
|
|
322
|
+
try Task.checkCancellation()
|
|
323
|
+
await refresh()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
`;
|
|
328
|
+
|
|
329
|
+
assert.equal(hasSwiftLongAsyncOperationWithoutCancellationCheckUsage(source), true);
|
|
330
|
+
assert.deepEqual(collectSwiftLongAsyncOperationWithoutCancellationCheckLines(source), [4, 10]);
|
|
331
|
+
assert.equal(hasSwiftLongAsyncOperationWithoutCancellationCheckUsage(safe), false);
|
|
332
|
+
assert.deepEqual(collectSwiftLongAsyncOperationWithoutCancellationCheckLines(safe), []);
|
|
333
|
+
});
|
|
334
|
+
|
|
259
335
|
test('hasSwiftManualMemoryManagementUsage detecta bypass manual de ARC', () => {
|
|
260
336
|
const source = `
|
|
261
337
|
final class LegacyBridge {
|
|
@@ -307,6 +383,36 @@ let sample = "struct user_profile {}"
|
|
|
307
383
|
assert.deepEqual(collectSwiftNonPascalCaseTypeDeclarationLines(safe), []);
|
|
308
384
|
});
|
|
309
385
|
|
|
386
|
+
test('hasSwiftEndpointEnumUsage detecta endpoints iOS como enum central no OCP', () => {
|
|
387
|
+
const source = `
|
|
388
|
+
enum APIEndpoint {
|
|
389
|
+
case login
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
enum OrdersEndpoint: String {
|
|
393
|
+
case list = "/orders"
|
|
394
|
+
}
|
|
395
|
+
`;
|
|
396
|
+
const safe = `
|
|
397
|
+
struct APIEndpoint: Sendable {
|
|
398
|
+
let path: String
|
|
399
|
+
let method: HTTPMethod
|
|
400
|
+
|
|
401
|
+
static func login() -> APIEndpoint {
|
|
402
|
+
APIEndpoint(path: "/login", method: .post)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let sample = "enum APIEndpoint { case login }"
|
|
407
|
+
// enum OrdersEndpoint { case list }
|
|
408
|
+
`;
|
|
409
|
+
|
|
410
|
+
assert.equal(hasSwiftEndpointEnumUsage(source), true);
|
|
411
|
+
assert.deepEqual(collectSwiftEndpointEnumLines(source), [2, 6]);
|
|
412
|
+
assert.equal(hasSwiftEndpointEnumUsage(safe), false);
|
|
413
|
+
assert.deepEqual(collectSwiftEndpointEnumLines(safe), []);
|
|
414
|
+
});
|
|
415
|
+
|
|
310
416
|
test('hasSwiftCellCreationWithoutReuseUsage detecta celdas UIKit sin reutilizacion', () => {
|
|
311
417
|
const source = `
|
|
312
418
|
final class OrdersDataSource: NSObject, UITableViewDataSource {
|
|
@@ -596,6 +702,350 @@ buildSettings = {
|
|
|
596
702
|
assert.deepEqual(collectSwiftStrictConcurrencyBelowCompleteLines(safe), []);
|
|
597
703
|
});
|
|
598
704
|
|
|
705
|
+
test('detectores SwiftPM de concurrencia detectan aislamiento y strict concurrency inseguros', () => {
|
|
706
|
+
const source = `
|
|
707
|
+
let package = Package(
|
|
708
|
+
name: "App",
|
|
709
|
+
swiftSettings: [
|
|
710
|
+
.defaultIsolation(nonisolated),
|
|
711
|
+
.enableExperimentalFeature("StrictConcurrency=targeted"),
|
|
712
|
+
.enableUpcomingFeature("StrictConcurrency=minimal")
|
|
713
|
+
]
|
|
714
|
+
)
|
|
715
|
+
`;
|
|
716
|
+
const safe = `
|
|
717
|
+
let package = Package(
|
|
718
|
+
name: "App",
|
|
719
|
+
swiftSettings: [
|
|
720
|
+
.defaultIsolation(MainActor.self),
|
|
721
|
+
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
|
|
722
|
+
]
|
|
723
|
+
)
|
|
724
|
+
let sample = ".defaultIsolation(nonisolated)"
|
|
725
|
+
// .enableExperimentalFeature("StrictConcurrency=targeted")
|
|
726
|
+
`;
|
|
727
|
+
|
|
728
|
+
assert.equal(hasSwiftPackageDefaultIsolationNotMainActorUsage(source), true);
|
|
729
|
+
assert.deepEqual(collectSwiftPackageDefaultIsolationNotMainActorLines(source), [5]);
|
|
730
|
+
assert.equal(hasSwiftPackageStrictConcurrencyBelowCompleteUsage(source), true);
|
|
731
|
+
assert.deepEqual(collectSwiftPackageStrictConcurrencyBelowCompleteLines(source), [6, 7]);
|
|
732
|
+
assert.equal(hasSwiftPackageDefaultIsolationNotMainActorUsage(safe), false);
|
|
733
|
+
assert.deepEqual(collectSwiftPackageDefaultIsolationNotMainActorLines(safe), []);
|
|
734
|
+
assert.equal(hasSwiftPackageStrictConcurrencyBelowCompleteUsage(safe), false);
|
|
735
|
+
assert.deepEqual(collectSwiftPackageStrictConcurrencyBelowCompleteLines(safe), []);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test('hasSwiftCrossFeatureImportUsage detecta imports entre bounded contexts iOS', () => {
|
|
739
|
+
const source = `
|
|
740
|
+
import SwiftUI
|
|
741
|
+
import SharedKernel
|
|
742
|
+
import Orders
|
|
743
|
+
import BuyerCommerce
|
|
744
|
+
`;
|
|
745
|
+
const path = 'apps/ios/Presentation/Features/BuyerCommerce/LoginView.swift';
|
|
746
|
+
const safe = `
|
|
747
|
+
import SwiftUI
|
|
748
|
+
import SharedKernel
|
|
749
|
+
import BuyerCommerce
|
|
750
|
+
// import Orders
|
|
751
|
+
let sample = "import Orders"
|
|
752
|
+
`;
|
|
753
|
+
|
|
754
|
+
assert.equal(hasSwiftCrossFeatureImportUsage(source, path), true);
|
|
755
|
+
assert.deepEqual(collectSwiftCrossFeatureImportLines(source, path), [4]);
|
|
756
|
+
assert.equal(hasSwiftCrossFeatureImportUsage(safe, path), false);
|
|
757
|
+
assert.deepEqual(collectSwiftCrossFeatureImportLines(safe, path), []);
|
|
758
|
+
assert.equal(hasSwiftCrossFeatureImportUsage(source, 'apps/ios/Presentation/LoginView.swift'), false);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test('hasSwiftLayerDirectionViolationUsage detecta imports prohibidos por capa Clean Architecture', () => {
|
|
762
|
+
const domainSource = `
|
|
763
|
+
import Foundation
|
|
764
|
+
import SwiftUI
|
|
765
|
+
import CoreData
|
|
766
|
+
import CheckoutApplication
|
|
767
|
+
import Combine
|
|
768
|
+
`;
|
|
769
|
+
const applicationSource = `
|
|
770
|
+
import Foundation
|
|
771
|
+
import UIKit
|
|
772
|
+
import Alamofire
|
|
773
|
+
import AuthInfrastructure
|
|
774
|
+
`;
|
|
775
|
+
const presentationSource = `
|
|
776
|
+
import SwiftUI
|
|
777
|
+
import CoreData
|
|
778
|
+
import DesignSystem
|
|
779
|
+
import AuthInfrastructure
|
|
780
|
+
`;
|
|
781
|
+
const safeDomain = `
|
|
782
|
+
import Foundation
|
|
783
|
+
import SharedKernel
|
|
784
|
+
`;
|
|
785
|
+
|
|
786
|
+
assert.equal(
|
|
787
|
+
hasSwiftLayerDirectionViolationUsage(
|
|
788
|
+
domainSource,
|
|
789
|
+
'apps/ios/Features/Orders/Domain/Entities/Order.swift'
|
|
790
|
+
),
|
|
791
|
+
true
|
|
792
|
+
);
|
|
793
|
+
assert.deepEqual(
|
|
794
|
+
collectSwiftLayerDirectionViolationLines(
|
|
795
|
+
domainSource,
|
|
796
|
+
'apps/ios/Features/Orders/Domain/Entities/Order.swift'
|
|
797
|
+
),
|
|
798
|
+
[3, 4, 5, 6]
|
|
799
|
+
);
|
|
800
|
+
assert.deepEqual(
|
|
801
|
+
collectSwiftLayerDirectionViolationLines(
|
|
802
|
+
applicationSource,
|
|
803
|
+
'apps/ios/Features/Orders/Application/UseCases/LoadOrders.swift'
|
|
804
|
+
),
|
|
805
|
+
[3, 4, 5]
|
|
806
|
+
);
|
|
807
|
+
assert.deepEqual(
|
|
808
|
+
collectSwiftLayerDirectionViolationLines(
|
|
809
|
+
presentationSource,
|
|
810
|
+
'apps/ios/Features/Orders/Presentation/OrdersView.swift'
|
|
811
|
+
),
|
|
812
|
+
[3, 5]
|
|
813
|
+
);
|
|
814
|
+
assert.equal(
|
|
815
|
+
hasSwiftLayerDirectionViolationUsage(
|
|
816
|
+
safeDomain,
|
|
817
|
+
'apps/ios/Features/Orders/Domain/Entities/Order.swift'
|
|
818
|
+
),
|
|
819
|
+
false
|
|
820
|
+
);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test('hasSwiftExcessivePublicApiUsage detecta public/open excesivo en app iOS', () => {
|
|
824
|
+
const appSource = `
|
|
825
|
+
import SwiftUI
|
|
826
|
+
|
|
827
|
+
public final class BuyerShellViewModel {
|
|
828
|
+
public var selectedTab: Tab = .home
|
|
829
|
+
public init() {}
|
|
830
|
+
public func openCheckout() {}
|
|
831
|
+
}
|
|
832
|
+
`;
|
|
833
|
+
const minimalPublicSource = `
|
|
834
|
+
public struct CheckoutRoute {}
|
|
835
|
+
internal struct CheckoutState {}
|
|
836
|
+
private struct CheckoutReducer {}
|
|
837
|
+
`;
|
|
838
|
+
const packageSource = `
|
|
839
|
+
public struct PublicSDKSurface {}
|
|
840
|
+
public func makeClient() {}
|
|
841
|
+
public let version = "1"
|
|
842
|
+
`;
|
|
843
|
+
|
|
844
|
+
assert.equal(
|
|
845
|
+
hasSwiftExcessivePublicApiUsage(
|
|
846
|
+
appSource,
|
|
847
|
+
'apps/ios/Presentation/Features/BuyerCommerce/BuyerShellViewModel.swift'
|
|
848
|
+
),
|
|
849
|
+
true
|
|
850
|
+
);
|
|
851
|
+
assert.deepEqual(
|
|
852
|
+
collectSwiftExcessivePublicApiLines(
|
|
853
|
+
appSource,
|
|
854
|
+
'apps/ios/Presentation/Features/BuyerCommerce/BuyerShellViewModel.swift'
|
|
855
|
+
),
|
|
856
|
+
[4, 5, 6, 7]
|
|
857
|
+
);
|
|
858
|
+
assert.equal(
|
|
859
|
+
hasSwiftExcessivePublicApiUsage(
|
|
860
|
+
minimalPublicSource,
|
|
861
|
+
'apps/ios/Presentation/Features/BuyerCommerce/CheckoutRoute.swift'
|
|
862
|
+
),
|
|
863
|
+
false
|
|
864
|
+
);
|
|
865
|
+
assert.deepEqual(
|
|
866
|
+
collectSwiftExcessivePublicApiLines(
|
|
867
|
+
packageSource,
|
|
868
|
+
'apps/ios/Sources/PublicSDK/PublicSDK.swift'
|
|
869
|
+
),
|
|
870
|
+
[]
|
|
871
|
+
);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
test('hasSwiftDefaultActorIsolationNotMainActorUsage detecta aislamiento por defecto distinto de MainActor', () => {
|
|
875
|
+
const source = `
|
|
876
|
+
buildSettings = {
|
|
877
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
|
878
|
+
OTHER_SWIFT_FLAGS = "$(inherited)";
|
|
879
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = SomeCustomActor;
|
|
880
|
+
};
|
|
881
|
+
`;
|
|
882
|
+
const safe = `
|
|
883
|
+
buildSettings = {
|
|
884
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
|
885
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor.self;
|
|
886
|
+
let sample = "SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;"
|
|
887
|
+
// SWIFT_DEFAULT_ACTOR_ISOLATION = SomeCustomActor;
|
|
888
|
+
};
|
|
889
|
+
`;
|
|
890
|
+
|
|
891
|
+
assert.equal(hasSwiftDefaultActorIsolationNotMainActorUsage(source), true);
|
|
892
|
+
assert.deepEqual(collectSwiftDefaultActorIsolationNotMainActorLines(source), [3, 5]);
|
|
893
|
+
assert.equal(hasSwiftDefaultActorIsolationNotMainActorUsage(safe), false);
|
|
894
|
+
assert.deepEqual(collectSwiftDefaultActorIsolationNotMainActorLines(safe), []);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test('hasSwiftUpcomingFeatureDisabledUsage detecta upcoming features Swift desactivadas', () => {
|
|
898
|
+
const source = `
|
|
899
|
+
buildSettings = {
|
|
900
|
+
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = NO;
|
|
901
|
+
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = 0;
|
|
902
|
+
SWIFT_ENABLE_EXPERIMENTAL_FEATURES = ;
|
|
903
|
+
};
|
|
904
|
+
`;
|
|
905
|
+
const safe = `
|
|
906
|
+
buildSettings = {
|
|
907
|
+
SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;
|
|
908
|
+
SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = true;
|
|
909
|
+
SWIFT_ENABLE_EXPERIMENTAL_FEATURES = StrictConcurrency;
|
|
910
|
+
let sample = "SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = NO;"
|
|
911
|
+
// SWIFT_ENABLE_EXPERIMENTAL_FEATURES = ;
|
|
912
|
+
};
|
|
913
|
+
`;
|
|
914
|
+
|
|
915
|
+
assert.equal(hasSwiftUpcomingFeatureDisabledUsage(source), true);
|
|
916
|
+
assert.deepEqual(collectSwiftUpcomingFeatureDisabledLines(source), [3, 4, 5]);
|
|
917
|
+
assert.equal(hasSwiftUpcomingFeatureDisabledUsage(safe), false);
|
|
918
|
+
assert.deepEqual(collectSwiftUpcomingFeatureDisabledLines(safe), []);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test('hasSwiftUiStateWithoutMainActorUsage detecta estado UI observable sin MainActor', () => {
|
|
922
|
+
const source = `
|
|
923
|
+
final class BuyerAuthViewModel: ObservableObject {
|
|
924
|
+
@Published var title = "Login"
|
|
925
|
+
}
|
|
926
|
+
struct BuyerSessionStore {
|
|
927
|
+
var token: String?
|
|
928
|
+
}
|
|
929
|
+
`;
|
|
930
|
+
const safe = `
|
|
931
|
+
@MainActor
|
|
932
|
+
@Observable
|
|
933
|
+
final class BuyerAuthViewModel {
|
|
934
|
+
var title = "Login"
|
|
935
|
+
}
|
|
936
|
+
final class BackgroundStore {
|
|
937
|
+
let cache = NSCache<NSString, NSString>()
|
|
938
|
+
}
|
|
939
|
+
let sample = "final class FakeViewModel: ObservableObject"
|
|
940
|
+
// final class CommentedViewModel: ObservableObject {}
|
|
941
|
+
`;
|
|
942
|
+
|
|
943
|
+
assert.equal(hasSwiftUiStateWithoutMainActorUsage(source), true);
|
|
944
|
+
assert.deepEqual(collectSwiftUiStateWithoutMainActorLines(source), [2]);
|
|
945
|
+
assert.equal(hasSwiftUiStateWithoutMainActorUsage(safe), false);
|
|
946
|
+
assert.deepEqual(collectSwiftUiStateWithoutMainActorLines(safe), []);
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
test('hasSwiftSharedMutableStateWithoutActorUsage detecta estado compartido mutable sin actor', () => {
|
|
950
|
+
const source = `
|
|
951
|
+
final class UserSessionManager {
|
|
952
|
+
private var token: String?
|
|
953
|
+
|
|
954
|
+
func update(token: String) {
|
|
955
|
+
self.token = token
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
`;
|
|
959
|
+
const safe = `
|
|
960
|
+
actor UserSessionManager {
|
|
961
|
+
private var token: String?
|
|
962
|
+
|
|
963
|
+
func update(token: String) {
|
|
964
|
+
self.token = token
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
@MainActor
|
|
968
|
+
final class BuyerSessionStore {
|
|
969
|
+
private var route: String?
|
|
970
|
+
func update(route: String) { self.route = route }
|
|
971
|
+
}
|
|
972
|
+
final class ValueFormatter {
|
|
973
|
+
func title() -> String { "OK" }
|
|
974
|
+
}
|
|
975
|
+
let sample = "final class TokenCache { var token: String? }"
|
|
976
|
+
// final class CommentedCache { var value = "" }
|
|
977
|
+
`;
|
|
978
|
+
|
|
979
|
+
assert.equal(hasSwiftSharedMutableStateWithoutActorUsage(source), true);
|
|
980
|
+
assert.deepEqual(collectSwiftSharedMutableStateWithoutActorLines(source), [2]);
|
|
981
|
+
assert.equal(hasSwiftSharedMutableStateWithoutActorUsage(safe), false);
|
|
982
|
+
assert.deepEqual(collectSwiftSharedMutableStateWithoutActorLines(safe), []);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test('hasSwiftMainActorRunPatchUsage detecta MainActor.run como parche en owners no aislados', () => {
|
|
986
|
+
const source = `
|
|
987
|
+
final class BuyerAuthViewModel {
|
|
988
|
+
func load() async {
|
|
989
|
+
await MainActor.run {
|
|
990
|
+
self.title = "Ready"
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
`;
|
|
995
|
+
const safe = `
|
|
996
|
+
@MainActor
|
|
997
|
+
final class BuyerAuthViewModel {
|
|
998
|
+
func load() async {
|
|
999
|
+
await MainActor.run { self.title = "Ready" }
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
actor BuyerSessionManager {
|
|
1003
|
+
func load() async {
|
|
1004
|
+
await MainActor.run { print("bridge") }
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
func bridgeToUi() async {
|
|
1008
|
+
await MainActor.run { print("approved explicit boundary") }
|
|
1009
|
+
}
|
|
1010
|
+
let sample = "MainActor.run { value = 1 }"
|
|
1011
|
+
// await MainActor.run { value = 2 }
|
|
1012
|
+
`;
|
|
1013
|
+
|
|
1014
|
+
assert.equal(hasSwiftMainActorRunPatchUsage(source), true);
|
|
1015
|
+
assert.deepEqual(collectSwiftMainActorRunPatchLines(source), [4]);
|
|
1016
|
+
assert.equal(hasSwiftMainActorRunPatchUsage(safe), false);
|
|
1017
|
+
assert.deepEqual(collectSwiftMainActorRunPatchLines(safe), []);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test('hasSwiftNavigationPathWithoutRestorationUsage detecta NavigationPath sin restauracion', () => {
|
|
1021
|
+
const source = `
|
|
1022
|
+
struct ShellView: View {
|
|
1023
|
+
@State private var path = NavigationPath()
|
|
1024
|
+
|
|
1025
|
+
var body: some View {
|
|
1026
|
+
NavigationStack(path: $path) { EmptyView() }
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
`;
|
|
1030
|
+
const safe = `
|
|
1031
|
+
struct ShellView: View {
|
|
1032
|
+
@SceneStorage("navigation.path") private var encodedPath = Data()
|
|
1033
|
+
@State private var path = NavigationPath()
|
|
1034
|
+
|
|
1035
|
+
func restoreNavigationPath() {
|
|
1036
|
+
_ = path.codable
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
let sample = "NavigationPath()"
|
|
1040
|
+
// let ignored = NavigationPath()
|
|
1041
|
+
`;
|
|
1042
|
+
|
|
1043
|
+
assert.equal(hasSwiftNavigationPathWithoutRestorationUsage(source), true);
|
|
1044
|
+
assert.deepEqual(collectSwiftNavigationPathWithoutRestorationLines(source), [3]);
|
|
1045
|
+
assert.equal(hasSwiftNavigationPathWithoutRestorationUsage(safe), false);
|
|
1046
|
+
assert.deepEqual(collectSwiftNavigationPathWithoutRestorationLines(safe), []);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
599
1049
|
test('hasSwiftNonLazyScrollForEachUsage detecta ScrollView con stack no lazy y preserva LazyVStack', () => {
|
|
600
1050
|
const source = `
|
|
601
1051
|
struct FeedView: View {
|
|
@@ -1733,6 +2183,30 @@ func wait() async throws {
|
|
|
1733
2183
|
assert.equal(hasSwiftMainThreadBlockingSleepUsage(ignored), false);
|
|
1734
2184
|
});
|
|
1735
2185
|
|
|
2186
|
+
test('detector iOS de concurrencia detecta depuracion centrada en hilos', () => {
|
|
2187
|
+
const source = `
|
|
2188
|
+
func traceCurrentExecution() {
|
|
2189
|
+
print(Thread.current)
|
|
2190
|
+
if Thread.isMainThread { recordMainThread() }
|
|
2191
|
+
let pthread = pthread_self()
|
|
2192
|
+
let mach = pthread_mach_thread_np(pthread)
|
|
2193
|
+
}
|
|
2194
|
+
`;
|
|
2195
|
+
const ignored = `
|
|
2196
|
+
@MainActor
|
|
2197
|
+
func updateUI() {
|
|
2198
|
+
let description = "Thread.current"
|
|
2199
|
+
// Thread.isMainThread
|
|
2200
|
+
Task { await reload() }
|
|
2201
|
+
}
|
|
2202
|
+
`;
|
|
2203
|
+
|
|
2204
|
+
assert.equal(hasSwiftThreadCentricDebuggingUsage(source), true);
|
|
2205
|
+
assert.deepEqual(collectSwiftThreadCentricDebuggingLines(source), [3, 4, 5, 6]);
|
|
2206
|
+
assert.equal(hasSwiftThreadCentricDebuggingUsage(ignored), false);
|
|
2207
|
+
assert.deepEqual(collectSwiftThreadCentricDebuggingLines(ignored), []);
|
|
2208
|
+
});
|
|
2209
|
+
|
|
1736
2210
|
test('detector iOS de concurrencia detecta async privado sin await y evita boundaries publicos', () => {
|
|
1737
2211
|
const source = `
|
|
1738
2212
|
final class ProfileLoader {
|
|
@@ -1761,6 +2235,33 @@ final class ProfileLoader: RemoteLoader {
|
|
|
1761
2235
|
assert.equal(hasSwiftAsyncWithoutAwaitUsage(ignored), false);
|
|
1762
2236
|
});
|
|
1763
2237
|
|
|
2238
|
+
test('detector iOS de concurrencia detecta dummy awaits usados como workaround', () => {
|
|
2239
|
+
const source = `
|
|
2240
|
+
final class Loader {
|
|
2241
|
+
func load() async {
|
|
2242
|
+
await Task.yield()
|
|
2243
|
+
try? await Task.sleep(nanoseconds: 0)
|
|
2244
|
+
try? await Task.sleep(for: .zero)
|
|
2245
|
+
try? await Task.sleep(for: .seconds(0))
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
`;
|
|
2249
|
+
const ignored = `
|
|
2250
|
+
final class Loader {
|
|
2251
|
+
func load() async throws {
|
|
2252
|
+
try await Task.sleep(for: .seconds(1))
|
|
2253
|
+
let message = "await Task.yield()"
|
|
2254
|
+
// await Task.yield()
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
`;
|
|
2258
|
+
|
|
2259
|
+
assert.equal(hasSwiftDummyAwaitUsage(source), true);
|
|
2260
|
+
assert.deepEqual(collectSwiftDummyAwaitLines(source), [4, 5, 6, 7]);
|
|
2261
|
+
assert.equal(hasSwiftDummyAwaitUsage(ignored), false);
|
|
2262
|
+
assert.deepEqual(collectSwiftDummyAwaitLines(ignored), []);
|
|
2263
|
+
});
|
|
2264
|
+
|
|
1764
2265
|
test('detector iOS de accesibilidad detecta botones icon-only sin label explicita', () => {
|
|
1765
2266
|
const source = `
|
|
1766
2267
|
struct ToolbarView: View {
|
|
@@ -2005,6 +2506,81 @@ struct RowView: View {
|
|
|
2005
2506
|
assert.deepEqual(collectSwiftOnTapGestureWithoutButtonTraitLines(safe), []);
|
|
2006
2507
|
});
|
|
2007
2508
|
|
|
2509
|
+
test('hasSwiftGlassInteractiveOnStaticElementUsage detecta Liquid Glass interactivo en contenido estatico', () => {
|
|
2510
|
+
const source = `
|
|
2511
|
+
struct CardView: View {
|
|
2512
|
+
var body: some View {
|
|
2513
|
+
Text("Estado")
|
|
2514
|
+
.padding()
|
|
2515
|
+
.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
`;
|
|
2519
|
+
const safe = `
|
|
2520
|
+
struct CardView: View {
|
|
2521
|
+
var body: some View {
|
|
2522
|
+
Button("Continuar") { submit() }
|
|
2523
|
+
.glassEffect(.regular.interactive(), in: .capsule)
|
|
2524
|
+
|
|
2525
|
+
Text("Mas informacion")
|
|
2526
|
+
.onTapGesture { showDetails() }
|
|
2527
|
+
.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))
|
|
2528
|
+
|
|
2529
|
+
Text("decorativo")
|
|
2530
|
+
.glassEffect(.regular, in: .rect(cornerRadius: 16))
|
|
2531
|
+
|
|
2532
|
+
let sample = ".glassEffect(.regular.interactive())"
|
|
2533
|
+
// .glassEffect(.regular.interactive())
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
`;
|
|
2537
|
+
|
|
2538
|
+
assert.equal(hasSwiftGlassInteractiveOnStaticElementUsage(source), true);
|
|
2539
|
+
assert.deepEqual(collectSwiftGlassInteractiveOnStaticElementLines(source), [6]);
|
|
2540
|
+
assert.equal(hasSwiftGlassInteractiveOnStaticElementUsage(safe), false);
|
|
2541
|
+
assert.deepEqual(collectSwiftGlassInteractiveOnStaticElementLines(safe), []);
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
test('hasSwiftGlassEffectIDWithoutNamespaceUsage detecta glassEffectID sin Namespace real', () => {
|
|
2545
|
+
const source = `
|
|
2546
|
+
struct CardView: View {
|
|
2547
|
+
var body: some View {
|
|
2548
|
+
Text("Card")
|
|
2549
|
+
.glassEffect()
|
|
2550
|
+
.glassEffectID("card", in: animation)
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
`;
|
|
2554
|
+
const safe = `
|
|
2555
|
+
struct CardView: View {
|
|
2556
|
+
@Namespace private var animation
|
|
2557
|
+
|
|
2558
|
+
var body: some View {
|
|
2559
|
+
Text("Card")
|
|
2560
|
+
.glassEffect()
|
|
2561
|
+
.glassEffectID("card", in: animation)
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
struct ChildCard: View {
|
|
2566
|
+
let animation: Namespace.ID
|
|
2567
|
+
|
|
2568
|
+
var body: some View {
|
|
2569
|
+
Text("Card")
|
|
2570
|
+
.glassEffectID("card", in: animation)
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
let sample = ".glassEffectID(\\"card\\", in: animation)"
|
|
2575
|
+
// .glassEffectID("card", in: animation)
|
|
2576
|
+
`;
|
|
2577
|
+
|
|
2578
|
+
assert.equal(hasSwiftGlassEffectIDWithoutNamespaceUsage(source), true);
|
|
2579
|
+
assert.deepEqual(collectSwiftGlassEffectIDWithoutNamespaceLines(source), [6]);
|
|
2580
|
+
assert.equal(hasSwiftGlassEffectIDWithoutNamespaceUsage(safe), false);
|
|
2581
|
+
assert.deepEqual(collectSwiftGlassEffectIDWithoutNamespaceLines(safe), []);
|
|
2582
|
+
});
|
|
2583
|
+
|
|
2008
2584
|
test('detectores legacy ignoran strings y comentarios', () => {
|
|
2009
2585
|
const source = `
|
|
2010
2586
|
// Task.detached { }
|
|
@@ -2779,6 +3355,51 @@ struct LoginTests {
|
|
|
2779
3355
|
assert.deepEqual(collectSwiftQuickNimbleLines(swiftTesting), []);
|
|
2780
3356
|
});
|
|
2781
3357
|
|
|
3358
|
+
test('hasSwiftThirdPartyUiTestFrameworkUsage detecta frameworks UI test no nativos', () => {
|
|
3359
|
+
const kifSuite = `
|
|
3360
|
+
import XCTest
|
|
3361
|
+
import KIF
|
|
3362
|
+
|
|
3363
|
+
final class CheckoutUITests: XCTestCase {
|
|
3364
|
+
func testCheckout() {
|
|
3365
|
+
tester().tapView(withAccessibilityLabel: "Pay")
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
`;
|
|
3369
|
+
const earlGreySuite = `
|
|
3370
|
+
import XCTest
|
|
3371
|
+
import EarlGrey
|
|
3372
|
+
|
|
3373
|
+
final class LoginUITests: XCTestCase {
|
|
3374
|
+
func testLogin() {
|
|
3375
|
+
EarlGrey.selectElement(with: grey_accessibilityID("login")).perform(grey_tap())
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
`;
|
|
3379
|
+
const nativeXcuiTest = `
|
|
3380
|
+
import XCTest
|
|
3381
|
+
|
|
3382
|
+
final class LoginUITests: XCTestCase {
|
|
3383
|
+
func testLogin() {
|
|
3384
|
+
let app = XCUIApplication()
|
|
3385
|
+
app.buttons["login"].tap()
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
`;
|
|
3389
|
+
const ignored = `
|
|
3390
|
+
let text = "import KIF"
|
|
3391
|
+
// EarlGrey.selectElement(with: grey_accessibilityID("login"))
|
|
3392
|
+
`;
|
|
3393
|
+
|
|
3394
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(kifSuite), true);
|
|
3395
|
+
assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(kifSuite), [3, 7]);
|
|
3396
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(earlGreySuite), true);
|
|
3397
|
+
assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(earlGreySuite), [3, 7]);
|
|
3398
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(nativeXcuiTest), false);
|
|
3399
|
+
assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(nativeXcuiTest), []);
|
|
3400
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(ignored), false);
|
|
3401
|
+
});
|
|
3402
|
+
|
|
2782
3403
|
test('hasSwiftXCTestAssertionUsage detecta XCTAssert y XCTFail reales', () => {
|
|
2783
3404
|
const source = `
|
|
2784
3405
|
XCTAssertEqual(value, expected)
|