pumuki 6.3.307 → 6.3.308
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 +459 -0
- package/core/facts/detectors/text/ios.ts +484 -0
- package/core/facts/extractHeuristicFacts.ts +17 -4
- package/core/rules/presets/heuristics/ios.test.ts +58 -1
- package/core/rules/presets/heuristics/ios.ts +239 -0
- package/integrations/config/skillsDetectorRegistry.ts +94 -0
- package/integrations/evidence/buildEvidence.ts +46 -0
- 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 +2 -1
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +76 -2
- package/scripts/framework-menu-system-notifications-payloads.ts +1 -0
- package/skills.lock.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [6.3.308] - 2026-05-20
|
|
4
|
+
|
|
5
|
+
- `R_GO`: PRE_WRITE deja de exigir `skills.ios.critical-test-quality` cuando no hay cambios efectivos de test iOS en el scope actual aunque la evidencia previa arrastre `platforms.ios`. Evita el bloqueo falso `EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING` sobre `.ai_evidence.json` en repos limpios/no-code.
|
|
6
|
+
- iOS AST skills: añadidos detectores nodales con lineas y metadata accionable para `clean architecture layer direction`, `third-party UI test framework` y `excessive public API`.
|
|
7
|
+
- Canary multi-plataforma: endurecido para exigir bloqueo real, diagnostico `violations-detected`, rule id esperado/equivalente y finding asociado al archivo canario.
|
|
8
|
+
|
|
3
9
|
## [6.3.301] - 2026-05-19
|
|
4
10
|
|
|
5
11
|
- `PUMUKI-INC-157`: PRE_WRITE no longer hard-blocks when evidence reports `ai_gate.status=BLOCKED` but exposes no real blocking causes and only non-blocking WARN signals such as worktree hygiene warnings. Explicit AST/skills findings with `blocking=true` still block, even when their inherited severity is WARN.
|
|
@@ -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,20 @@ 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,
|
|
45
61
|
hasSwiftForEachIndicesUsage,
|
|
46
62
|
hasSwiftForEachSelfIdentityUsage,
|
|
47
63
|
collectSwiftForceCastLines,
|
|
@@ -70,6 +86,8 @@ import {
|
|
|
70
86
|
collectSwiftAnimationWithoutReduceMotionLines,
|
|
71
87
|
collectSwiftUiInlineActionLogicLines,
|
|
72
88
|
collectSwiftOnTapGestureWithoutButtonTraitLines,
|
|
89
|
+
collectSwiftGlassInteractiveOnStaticElementLines,
|
|
90
|
+
collectSwiftGlassEffectIDWithoutNamespaceLines,
|
|
73
91
|
collectSwiftForEachIndicesLines,
|
|
74
92
|
collectSwiftForEachSelfIdentityLines,
|
|
75
93
|
collectSwiftInlineForEachTransformLines,
|
|
@@ -86,6 +104,7 @@ import {
|
|
|
86
104
|
collectSwiftEnvironmentObjectLines,
|
|
87
105
|
hasSwiftEnvironmentObjectUsage,
|
|
88
106
|
hasSwiftLowContrastStaticColorPairUsage,
|
|
107
|
+
hasSwiftLongAsyncOperationWithoutCancellationCheckUsage,
|
|
89
108
|
hasSwiftMainThreadBlockingSleepUsage,
|
|
90
109
|
hasSwiftDirectSUTInstantiationWithoutMakeSUTUsage,
|
|
91
110
|
hasSwiftMakeSUTWithoutMemoryTrackingUsage,
|
|
@@ -125,6 +144,8 @@ import {
|
|
|
125
144
|
collectSwiftOnAppearTaskLines,
|
|
126
145
|
hasSwiftOnTapGestureUsage,
|
|
127
146
|
hasSwiftOnTapGestureWithoutButtonTraitUsage,
|
|
147
|
+
hasSwiftGlassInteractiveOnStaticElementUsage,
|
|
148
|
+
hasSwiftGlassEffectIDWithoutNamespaceUsage,
|
|
128
149
|
hasSwiftOperationQueueUsage,
|
|
129
150
|
hasSwiftContainsUserFilterUsage,
|
|
130
151
|
hasSwiftCustomSingletonUsage,
|
|
@@ -132,8 +153,11 @@ import {
|
|
|
132
153
|
hasSwiftPhysicalTextAlignmentUsage,
|
|
133
154
|
hasSwiftPreconcurrencyUsage,
|
|
134
155
|
hasSwiftProductionCommentUsage,
|
|
156
|
+
collectSwiftThreadCentricDebuggingLines,
|
|
157
|
+
hasSwiftThreadCentricDebuggingUsage,
|
|
135
158
|
hasSwiftProductionTestDoubleUsage,
|
|
136
159
|
hasSwiftQuickNimbleUsage,
|
|
160
|
+
hasSwiftThirdPartyUiTestFrameworkUsage,
|
|
137
161
|
hasSwiftTestDoubleWithoutProtocolConformanceUsage,
|
|
138
162
|
hasSwiftSheetIsPresentedUsage,
|
|
139
163
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
@@ -164,6 +188,8 @@ import {
|
|
|
164
188
|
hasSwiftManualMemoryManagementUsage,
|
|
165
189
|
collectSwiftNonPascalCaseTypeDeclarationLines,
|
|
166
190
|
hasSwiftNonPascalCaseTypeDeclarationUsage,
|
|
191
|
+
collectSwiftEndpointEnumLines,
|
|
192
|
+
hasSwiftEndpointEnumUsage,
|
|
167
193
|
collectSwiftCellCreationWithoutReuseLines,
|
|
168
194
|
hasSwiftCellCreationWithoutReuseUsage,
|
|
169
195
|
hasSwiftWaitForExpectationsUsage,
|
|
@@ -256,6 +282,46 @@ let ignored = "[unowned self]"
|
|
|
256
282
|
assert.equal(hasSwiftUnownedSelfCaptureUsage(safe), false);
|
|
257
283
|
});
|
|
258
284
|
|
|
285
|
+
test('hasSwiftLongAsyncOperationWithoutCancellationCheckUsage detecta bucles async sin cancelacion cooperativa', () => {
|
|
286
|
+
const source = `
|
|
287
|
+
final class SyncService {
|
|
288
|
+
func syncAll(items: [Item]) async throws {
|
|
289
|
+
for item in items {
|
|
290
|
+
try await upload(item)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func poll() async {
|
|
295
|
+
while true {
|
|
296
|
+
await refresh()
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
`;
|
|
301
|
+
const safe = `
|
|
302
|
+
final class SyncService {
|
|
303
|
+
func syncAll(items: [Item]) async throws {
|
|
304
|
+
for item in items {
|
|
305
|
+
if Task.isCancelled { return }
|
|
306
|
+
try await upload(item)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
func poll() async throws {
|
|
311
|
+
while isRunning {
|
|
312
|
+
try Task.checkCancellation()
|
|
313
|
+
await refresh()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
`;
|
|
318
|
+
|
|
319
|
+
assert.equal(hasSwiftLongAsyncOperationWithoutCancellationCheckUsage(source), true);
|
|
320
|
+
assert.deepEqual(collectSwiftLongAsyncOperationWithoutCancellationCheckLines(source), [4, 10]);
|
|
321
|
+
assert.equal(hasSwiftLongAsyncOperationWithoutCancellationCheckUsage(safe), false);
|
|
322
|
+
assert.deepEqual(collectSwiftLongAsyncOperationWithoutCancellationCheckLines(safe), []);
|
|
323
|
+
});
|
|
324
|
+
|
|
259
325
|
test('hasSwiftManualMemoryManagementUsage detecta bypass manual de ARC', () => {
|
|
260
326
|
const source = `
|
|
261
327
|
final class LegacyBridge {
|
|
@@ -307,6 +373,36 @@ let sample = "struct user_profile {}"
|
|
|
307
373
|
assert.deepEqual(collectSwiftNonPascalCaseTypeDeclarationLines(safe), []);
|
|
308
374
|
});
|
|
309
375
|
|
|
376
|
+
test('hasSwiftEndpointEnumUsage detecta endpoints iOS como enum central no OCP', () => {
|
|
377
|
+
const source = `
|
|
378
|
+
enum APIEndpoint {
|
|
379
|
+
case login
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
enum OrdersEndpoint: String {
|
|
383
|
+
case list = "/orders"
|
|
384
|
+
}
|
|
385
|
+
`;
|
|
386
|
+
const safe = `
|
|
387
|
+
struct APIEndpoint: Sendable {
|
|
388
|
+
let path: String
|
|
389
|
+
let method: HTTPMethod
|
|
390
|
+
|
|
391
|
+
static func login() -> APIEndpoint {
|
|
392
|
+
APIEndpoint(path: "/login", method: .post)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let sample = "enum APIEndpoint { case login }"
|
|
397
|
+
// enum OrdersEndpoint { case list }
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
assert.equal(hasSwiftEndpointEnumUsage(source), true);
|
|
401
|
+
assert.deepEqual(collectSwiftEndpointEnumLines(source), [2, 6]);
|
|
402
|
+
assert.equal(hasSwiftEndpointEnumUsage(safe), false);
|
|
403
|
+
assert.deepEqual(collectSwiftEndpointEnumLines(safe), []);
|
|
404
|
+
});
|
|
405
|
+
|
|
310
406
|
test('hasSwiftCellCreationWithoutReuseUsage detecta celdas UIKit sin reutilizacion', () => {
|
|
311
407
|
const source = `
|
|
312
408
|
final class OrdersDataSource: NSObject, UITableViewDataSource {
|
|
@@ -596,6 +692,198 @@ buildSettings = {
|
|
|
596
692
|
assert.deepEqual(collectSwiftStrictConcurrencyBelowCompleteLines(safe), []);
|
|
597
693
|
});
|
|
598
694
|
|
|
695
|
+
test('detectores SwiftPM de concurrencia detectan aislamiento y strict concurrency inseguros', () => {
|
|
696
|
+
const source = `
|
|
697
|
+
let package = Package(
|
|
698
|
+
name: "App",
|
|
699
|
+
swiftSettings: [
|
|
700
|
+
.defaultIsolation(nonisolated),
|
|
701
|
+
.enableExperimentalFeature("StrictConcurrency=targeted"),
|
|
702
|
+
.enableUpcomingFeature("StrictConcurrency=minimal")
|
|
703
|
+
]
|
|
704
|
+
)
|
|
705
|
+
`;
|
|
706
|
+
const safe = `
|
|
707
|
+
let package = Package(
|
|
708
|
+
name: "App",
|
|
709
|
+
swiftSettings: [
|
|
710
|
+
.defaultIsolation(MainActor.self),
|
|
711
|
+
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
|
|
712
|
+
]
|
|
713
|
+
)
|
|
714
|
+
let sample = ".defaultIsolation(nonisolated)"
|
|
715
|
+
// .enableExperimentalFeature("StrictConcurrency=targeted")
|
|
716
|
+
`;
|
|
717
|
+
|
|
718
|
+
assert.equal(hasSwiftPackageDefaultIsolationNotMainActorUsage(source), true);
|
|
719
|
+
assert.deepEqual(collectSwiftPackageDefaultIsolationNotMainActorLines(source), [5]);
|
|
720
|
+
assert.equal(hasSwiftPackageStrictConcurrencyBelowCompleteUsage(source), true);
|
|
721
|
+
assert.deepEqual(collectSwiftPackageStrictConcurrencyBelowCompleteLines(source), [6, 7]);
|
|
722
|
+
assert.equal(hasSwiftPackageDefaultIsolationNotMainActorUsage(safe), false);
|
|
723
|
+
assert.deepEqual(collectSwiftPackageDefaultIsolationNotMainActorLines(safe), []);
|
|
724
|
+
assert.equal(hasSwiftPackageStrictConcurrencyBelowCompleteUsage(safe), false);
|
|
725
|
+
assert.deepEqual(collectSwiftPackageStrictConcurrencyBelowCompleteLines(safe), []);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test('hasSwiftCrossFeatureImportUsage detecta imports entre bounded contexts iOS', () => {
|
|
729
|
+
const source = `
|
|
730
|
+
import SwiftUI
|
|
731
|
+
import SharedKernel
|
|
732
|
+
import Orders
|
|
733
|
+
import BuyerCommerce
|
|
734
|
+
`;
|
|
735
|
+
const path = 'apps/ios/Presentation/Features/BuyerCommerce/LoginView.swift';
|
|
736
|
+
const safe = `
|
|
737
|
+
import SwiftUI
|
|
738
|
+
import SharedKernel
|
|
739
|
+
import BuyerCommerce
|
|
740
|
+
// import Orders
|
|
741
|
+
let sample = "import Orders"
|
|
742
|
+
`;
|
|
743
|
+
|
|
744
|
+
assert.equal(hasSwiftCrossFeatureImportUsage(source, path), true);
|
|
745
|
+
assert.deepEqual(collectSwiftCrossFeatureImportLines(source, path), [4]);
|
|
746
|
+
assert.equal(hasSwiftCrossFeatureImportUsage(safe, path), false);
|
|
747
|
+
assert.deepEqual(collectSwiftCrossFeatureImportLines(safe, path), []);
|
|
748
|
+
assert.equal(hasSwiftCrossFeatureImportUsage(source, 'apps/ios/Presentation/LoginView.swift'), false);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test('hasSwiftLayerDirectionViolationUsage detecta imports prohibidos por capa Clean Architecture', () => {
|
|
752
|
+
const domainSource = `
|
|
753
|
+
import Foundation
|
|
754
|
+
import SwiftUI
|
|
755
|
+
import CoreData
|
|
756
|
+
import CheckoutApplication
|
|
757
|
+
import Combine
|
|
758
|
+
`;
|
|
759
|
+
const applicationSource = `
|
|
760
|
+
import Foundation
|
|
761
|
+
import UIKit
|
|
762
|
+
import Alamofire
|
|
763
|
+
import AuthInfrastructure
|
|
764
|
+
`;
|
|
765
|
+
const presentationSource = `
|
|
766
|
+
import SwiftUI
|
|
767
|
+
import CoreData
|
|
768
|
+
import DesignSystem
|
|
769
|
+
import AuthInfrastructure
|
|
770
|
+
`;
|
|
771
|
+
const safeDomain = `
|
|
772
|
+
import Foundation
|
|
773
|
+
import SharedKernel
|
|
774
|
+
`;
|
|
775
|
+
|
|
776
|
+
assert.equal(
|
|
777
|
+
hasSwiftLayerDirectionViolationUsage(
|
|
778
|
+
domainSource,
|
|
779
|
+
'apps/ios/Features/Orders/Domain/Entities/Order.swift'
|
|
780
|
+
),
|
|
781
|
+
true
|
|
782
|
+
);
|
|
783
|
+
assert.deepEqual(
|
|
784
|
+
collectSwiftLayerDirectionViolationLines(
|
|
785
|
+
domainSource,
|
|
786
|
+
'apps/ios/Features/Orders/Domain/Entities/Order.swift'
|
|
787
|
+
),
|
|
788
|
+
[3, 4, 5, 6]
|
|
789
|
+
);
|
|
790
|
+
assert.deepEqual(
|
|
791
|
+
collectSwiftLayerDirectionViolationLines(
|
|
792
|
+
applicationSource,
|
|
793
|
+
'apps/ios/Features/Orders/Application/UseCases/LoadOrders.swift'
|
|
794
|
+
),
|
|
795
|
+
[3, 4, 5]
|
|
796
|
+
);
|
|
797
|
+
assert.deepEqual(
|
|
798
|
+
collectSwiftLayerDirectionViolationLines(
|
|
799
|
+
presentationSource,
|
|
800
|
+
'apps/ios/Features/Orders/Presentation/OrdersView.swift'
|
|
801
|
+
),
|
|
802
|
+
[3, 5]
|
|
803
|
+
);
|
|
804
|
+
assert.equal(
|
|
805
|
+
hasSwiftLayerDirectionViolationUsage(
|
|
806
|
+
safeDomain,
|
|
807
|
+
'apps/ios/Features/Orders/Domain/Entities/Order.swift'
|
|
808
|
+
),
|
|
809
|
+
false
|
|
810
|
+
);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
test('hasSwiftExcessivePublicApiUsage detecta public/open excesivo en app iOS', () => {
|
|
814
|
+
const appSource = `
|
|
815
|
+
import SwiftUI
|
|
816
|
+
|
|
817
|
+
public final class BuyerShellViewModel {
|
|
818
|
+
public var selectedTab: Tab = .home
|
|
819
|
+
public init() {}
|
|
820
|
+
public func openCheckout() {}
|
|
821
|
+
}
|
|
822
|
+
`;
|
|
823
|
+
const minimalPublicSource = `
|
|
824
|
+
public struct CheckoutRoute {}
|
|
825
|
+
internal struct CheckoutState {}
|
|
826
|
+
private struct CheckoutReducer {}
|
|
827
|
+
`;
|
|
828
|
+
const packageSource = `
|
|
829
|
+
public struct PublicSDKSurface {}
|
|
830
|
+
public func makeClient() {}
|
|
831
|
+
public let version = "1"
|
|
832
|
+
`;
|
|
833
|
+
|
|
834
|
+
assert.equal(
|
|
835
|
+
hasSwiftExcessivePublicApiUsage(
|
|
836
|
+
appSource,
|
|
837
|
+
'apps/ios/Presentation/Features/BuyerCommerce/BuyerShellViewModel.swift'
|
|
838
|
+
),
|
|
839
|
+
true
|
|
840
|
+
);
|
|
841
|
+
assert.deepEqual(
|
|
842
|
+
collectSwiftExcessivePublicApiLines(
|
|
843
|
+
appSource,
|
|
844
|
+
'apps/ios/Presentation/Features/BuyerCommerce/BuyerShellViewModel.swift'
|
|
845
|
+
),
|
|
846
|
+
[4, 5, 6, 7]
|
|
847
|
+
);
|
|
848
|
+
assert.equal(
|
|
849
|
+
hasSwiftExcessivePublicApiUsage(
|
|
850
|
+
minimalPublicSource,
|
|
851
|
+
'apps/ios/Presentation/Features/BuyerCommerce/CheckoutRoute.swift'
|
|
852
|
+
),
|
|
853
|
+
false
|
|
854
|
+
);
|
|
855
|
+
assert.deepEqual(
|
|
856
|
+
collectSwiftExcessivePublicApiLines(
|
|
857
|
+
packageSource,
|
|
858
|
+
'apps/ios/Sources/PublicSDK/PublicSDK.swift'
|
|
859
|
+
),
|
|
860
|
+
[]
|
|
861
|
+
);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test('hasSwiftDefaultActorIsolationNotMainActorUsage detecta aislamiento por defecto distinto de MainActor', () => {
|
|
865
|
+
const source = `
|
|
866
|
+
buildSettings = {
|
|
867
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
|
868
|
+
OTHER_SWIFT_FLAGS = "$(inherited)";
|
|
869
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = SomeCustomActor;
|
|
870
|
+
};
|
|
871
|
+
`;
|
|
872
|
+
const safe = `
|
|
873
|
+
buildSettings = {
|
|
874
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
|
875
|
+
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor.self;
|
|
876
|
+
let sample = "SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;"
|
|
877
|
+
// SWIFT_DEFAULT_ACTOR_ISOLATION = SomeCustomActor;
|
|
878
|
+
};
|
|
879
|
+
`;
|
|
880
|
+
|
|
881
|
+
assert.equal(hasSwiftDefaultActorIsolationNotMainActorUsage(source), true);
|
|
882
|
+
assert.deepEqual(collectSwiftDefaultActorIsolationNotMainActorLines(source), [3, 5]);
|
|
883
|
+
assert.equal(hasSwiftDefaultActorIsolationNotMainActorUsage(safe), false);
|
|
884
|
+
assert.deepEqual(collectSwiftDefaultActorIsolationNotMainActorLines(safe), []);
|
|
885
|
+
});
|
|
886
|
+
|
|
599
887
|
test('hasSwiftNonLazyScrollForEachUsage detecta ScrollView con stack no lazy y preserva LazyVStack', () => {
|
|
600
888
|
const source = `
|
|
601
889
|
struct FeedView: View {
|
|
@@ -1733,6 +2021,30 @@ func wait() async throws {
|
|
|
1733
2021
|
assert.equal(hasSwiftMainThreadBlockingSleepUsage(ignored), false);
|
|
1734
2022
|
});
|
|
1735
2023
|
|
|
2024
|
+
test('detector iOS de concurrencia detecta depuracion centrada en hilos', () => {
|
|
2025
|
+
const source = `
|
|
2026
|
+
func traceCurrentExecution() {
|
|
2027
|
+
print(Thread.current)
|
|
2028
|
+
if Thread.isMainThread { recordMainThread() }
|
|
2029
|
+
let pthread = pthread_self()
|
|
2030
|
+
let mach = pthread_mach_thread_np(pthread)
|
|
2031
|
+
}
|
|
2032
|
+
`;
|
|
2033
|
+
const ignored = `
|
|
2034
|
+
@MainActor
|
|
2035
|
+
func updateUI() {
|
|
2036
|
+
let description = "Thread.current"
|
|
2037
|
+
// Thread.isMainThread
|
|
2038
|
+
Task { await reload() }
|
|
2039
|
+
}
|
|
2040
|
+
`;
|
|
2041
|
+
|
|
2042
|
+
assert.equal(hasSwiftThreadCentricDebuggingUsage(source), true);
|
|
2043
|
+
assert.deepEqual(collectSwiftThreadCentricDebuggingLines(source), [3, 4, 5, 6]);
|
|
2044
|
+
assert.equal(hasSwiftThreadCentricDebuggingUsage(ignored), false);
|
|
2045
|
+
assert.deepEqual(collectSwiftThreadCentricDebuggingLines(ignored), []);
|
|
2046
|
+
});
|
|
2047
|
+
|
|
1736
2048
|
test('detector iOS de concurrencia detecta async privado sin await y evita boundaries publicos', () => {
|
|
1737
2049
|
const source = `
|
|
1738
2050
|
final class ProfileLoader {
|
|
@@ -1761,6 +2073,33 @@ final class ProfileLoader: RemoteLoader {
|
|
|
1761
2073
|
assert.equal(hasSwiftAsyncWithoutAwaitUsage(ignored), false);
|
|
1762
2074
|
});
|
|
1763
2075
|
|
|
2076
|
+
test('detector iOS de concurrencia detecta dummy awaits usados como workaround', () => {
|
|
2077
|
+
const source = `
|
|
2078
|
+
final class Loader {
|
|
2079
|
+
func load() async {
|
|
2080
|
+
await Task.yield()
|
|
2081
|
+
try? await Task.sleep(nanoseconds: 0)
|
|
2082
|
+
try? await Task.sleep(for: .zero)
|
|
2083
|
+
try? await Task.sleep(for: .seconds(0))
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
`;
|
|
2087
|
+
const ignored = `
|
|
2088
|
+
final class Loader {
|
|
2089
|
+
func load() async throws {
|
|
2090
|
+
try await Task.sleep(for: .seconds(1))
|
|
2091
|
+
let message = "await Task.yield()"
|
|
2092
|
+
// await Task.yield()
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
`;
|
|
2096
|
+
|
|
2097
|
+
assert.equal(hasSwiftDummyAwaitUsage(source), true);
|
|
2098
|
+
assert.deepEqual(collectSwiftDummyAwaitLines(source), [4, 5, 6, 7]);
|
|
2099
|
+
assert.equal(hasSwiftDummyAwaitUsage(ignored), false);
|
|
2100
|
+
assert.deepEqual(collectSwiftDummyAwaitLines(ignored), []);
|
|
2101
|
+
});
|
|
2102
|
+
|
|
1764
2103
|
test('detector iOS de accesibilidad detecta botones icon-only sin label explicita', () => {
|
|
1765
2104
|
const source = `
|
|
1766
2105
|
struct ToolbarView: View {
|
|
@@ -2005,6 +2344,81 @@ struct RowView: View {
|
|
|
2005
2344
|
assert.deepEqual(collectSwiftOnTapGestureWithoutButtonTraitLines(safe), []);
|
|
2006
2345
|
});
|
|
2007
2346
|
|
|
2347
|
+
test('hasSwiftGlassInteractiveOnStaticElementUsage detecta Liquid Glass interactivo en contenido estatico', () => {
|
|
2348
|
+
const source = `
|
|
2349
|
+
struct CardView: View {
|
|
2350
|
+
var body: some View {
|
|
2351
|
+
Text("Estado")
|
|
2352
|
+
.padding()
|
|
2353
|
+
.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
`;
|
|
2357
|
+
const safe = `
|
|
2358
|
+
struct CardView: View {
|
|
2359
|
+
var body: some View {
|
|
2360
|
+
Button("Continuar") { submit() }
|
|
2361
|
+
.glassEffect(.regular.interactive(), in: .capsule)
|
|
2362
|
+
|
|
2363
|
+
Text("Mas informacion")
|
|
2364
|
+
.onTapGesture { showDetails() }
|
|
2365
|
+
.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))
|
|
2366
|
+
|
|
2367
|
+
Text("decorativo")
|
|
2368
|
+
.glassEffect(.regular, in: .rect(cornerRadius: 16))
|
|
2369
|
+
|
|
2370
|
+
let sample = ".glassEffect(.regular.interactive())"
|
|
2371
|
+
// .glassEffect(.regular.interactive())
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
`;
|
|
2375
|
+
|
|
2376
|
+
assert.equal(hasSwiftGlassInteractiveOnStaticElementUsage(source), true);
|
|
2377
|
+
assert.deepEqual(collectSwiftGlassInteractiveOnStaticElementLines(source), [6]);
|
|
2378
|
+
assert.equal(hasSwiftGlassInteractiveOnStaticElementUsage(safe), false);
|
|
2379
|
+
assert.deepEqual(collectSwiftGlassInteractiveOnStaticElementLines(safe), []);
|
|
2380
|
+
});
|
|
2381
|
+
|
|
2382
|
+
test('hasSwiftGlassEffectIDWithoutNamespaceUsage detecta glassEffectID sin Namespace real', () => {
|
|
2383
|
+
const source = `
|
|
2384
|
+
struct CardView: View {
|
|
2385
|
+
var body: some View {
|
|
2386
|
+
Text("Card")
|
|
2387
|
+
.glassEffect()
|
|
2388
|
+
.glassEffectID("card", in: animation)
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
`;
|
|
2392
|
+
const safe = `
|
|
2393
|
+
struct CardView: View {
|
|
2394
|
+
@Namespace private var animation
|
|
2395
|
+
|
|
2396
|
+
var body: some View {
|
|
2397
|
+
Text("Card")
|
|
2398
|
+
.glassEffect()
|
|
2399
|
+
.glassEffectID("card", in: animation)
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
struct ChildCard: View {
|
|
2404
|
+
let animation: Namespace.ID
|
|
2405
|
+
|
|
2406
|
+
var body: some View {
|
|
2407
|
+
Text("Card")
|
|
2408
|
+
.glassEffectID("card", in: animation)
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
let sample = ".glassEffectID(\\"card\\", in: animation)"
|
|
2413
|
+
// .glassEffectID("card", in: animation)
|
|
2414
|
+
`;
|
|
2415
|
+
|
|
2416
|
+
assert.equal(hasSwiftGlassEffectIDWithoutNamespaceUsage(source), true);
|
|
2417
|
+
assert.deepEqual(collectSwiftGlassEffectIDWithoutNamespaceLines(source), [6]);
|
|
2418
|
+
assert.equal(hasSwiftGlassEffectIDWithoutNamespaceUsage(safe), false);
|
|
2419
|
+
assert.deepEqual(collectSwiftGlassEffectIDWithoutNamespaceLines(safe), []);
|
|
2420
|
+
});
|
|
2421
|
+
|
|
2008
2422
|
test('detectores legacy ignoran strings y comentarios', () => {
|
|
2009
2423
|
const source = `
|
|
2010
2424
|
// Task.detached { }
|
|
@@ -2779,6 +3193,51 @@ struct LoginTests {
|
|
|
2779
3193
|
assert.deepEqual(collectSwiftQuickNimbleLines(swiftTesting), []);
|
|
2780
3194
|
});
|
|
2781
3195
|
|
|
3196
|
+
test('hasSwiftThirdPartyUiTestFrameworkUsage detecta frameworks UI test no nativos', () => {
|
|
3197
|
+
const kifSuite = `
|
|
3198
|
+
import XCTest
|
|
3199
|
+
import KIF
|
|
3200
|
+
|
|
3201
|
+
final class CheckoutUITests: XCTestCase {
|
|
3202
|
+
func testCheckout() {
|
|
3203
|
+
tester().tapView(withAccessibilityLabel: "Pay")
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
`;
|
|
3207
|
+
const earlGreySuite = `
|
|
3208
|
+
import XCTest
|
|
3209
|
+
import EarlGrey
|
|
3210
|
+
|
|
3211
|
+
final class LoginUITests: XCTestCase {
|
|
3212
|
+
func testLogin() {
|
|
3213
|
+
EarlGrey.selectElement(with: grey_accessibilityID("login")).perform(grey_tap())
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
`;
|
|
3217
|
+
const nativeXcuiTest = `
|
|
3218
|
+
import XCTest
|
|
3219
|
+
|
|
3220
|
+
final class LoginUITests: XCTestCase {
|
|
3221
|
+
func testLogin() {
|
|
3222
|
+
let app = XCUIApplication()
|
|
3223
|
+
app.buttons["login"].tap()
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
`;
|
|
3227
|
+
const ignored = `
|
|
3228
|
+
let text = "import KIF"
|
|
3229
|
+
// EarlGrey.selectElement(with: grey_accessibilityID("login"))
|
|
3230
|
+
`;
|
|
3231
|
+
|
|
3232
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(kifSuite), true);
|
|
3233
|
+
assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(kifSuite), [3, 7]);
|
|
3234
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(earlGreySuite), true);
|
|
3235
|
+
assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(earlGreySuite), [3, 7]);
|
|
3236
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(nativeXcuiTest), false);
|
|
3237
|
+
assert.deepEqual(collectSwiftThirdPartyUiTestFrameworkLines(nativeXcuiTest), []);
|
|
3238
|
+
assert.equal(hasSwiftThirdPartyUiTestFrameworkUsage(ignored), false);
|
|
3239
|
+
});
|
|
3240
|
+
|
|
2782
3241
|
test('hasSwiftXCTestAssertionUsage detecta XCTAssert y XCTFail reales', () => {
|
|
2783
3242
|
const source = `
|
|
2784
3243
|
XCTAssertEqual(value, expected)
|