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.
@@ -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)