pumuki 6.3.226 → 6.3.227
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/core/facts/detectors/text/ios.test.ts +36 -0
- package/core/facts/detectors/text/ios.ts +10 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.ts +19 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/config/skillsMarkdownRules.ts +8 -1
- package/package.json +1 -1
- package/skills.lock.json +38 -2
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
hasSwiftNonLazyScrollForEachUsage,
|
|
39
39
|
hasSwiftViewBodyObjectCreationUsage,
|
|
40
40
|
hasSwiftUiImageDataDecodingUsage,
|
|
41
|
+
hasSwiftUiInlineActionLogicUsage,
|
|
41
42
|
hasSwiftAssumeIsolatedUsage,
|
|
42
43
|
hasSwiftCoreDataLayerLeakUsage,
|
|
43
44
|
hasSwiftSwiftDataLayerLeakUsage,
|
|
@@ -220,6 +221,41 @@ struct AvatarView: View {
|
|
|
220
221
|
assert.equal(hasSwiftUiImageDataDecodingUsage(safe), false);
|
|
221
222
|
});
|
|
222
223
|
|
|
224
|
+
test('hasSwiftUiInlineActionLogicUsage detecta lógica inline en Button y preserva método referenciado', () => {
|
|
225
|
+
const source = `
|
|
226
|
+
struct CheckoutView: View {
|
|
227
|
+
@State private var isLoading = false
|
|
228
|
+
|
|
229
|
+
var body: some View {
|
|
230
|
+
Button {
|
|
231
|
+
if isLoading {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
Task {
|
|
235
|
+
await submit()
|
|
236
|
+
}
|
|
237
|
+
} label: {
|
|
238
|
+
Text("Pay")
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
const safe = `
|
|
244
|
+
struct CheckoutView: View {
|
|
245
|
+
var body: some View {
|
|
246
|
+
Button(action: submit) {
|
|
247
|
+
Text("Pay")
|
|
248
|
+
}
|
|
249
|
+
let sample = "Button { if loading { return } } label:"
|
|
250
|
+
// Button { if loading { return } } label:
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
|
|
255
|
+
assert.equal(hasSwiftUiInlineActionLogicUsage(source), true);
|
|
256
|
+
assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
|
|
257
|
+
});
|
|
258
|
+
|
|
223
259
|
test('hasSwiftForceTryUsage detecta try! y descarta try?', () => {
|
|
224
260
|
const positive = `
|
|
225
261
|
func load() {
|
|
@@ -401,6 +401,16 @@ export const hasSwiftUiImageDataDecodingUsage = (source: string): boolean => {
|
|
|
401
401
|
return /\bUIImage\s*\(\s*data\s*:/.test(swiftSource);
|
|
402
402
|
};
|
|
403
403
|
|
|
404
|
+
export const hasSwiftUiInlineActionLogicUsage = (source: string): boolean => {
|
|
405
|
+
const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
|
|
406
|
+
const inlineButtonActionPattern =
|
|
407
|
+
/\bButton\s*\{[\s\S]{0,900}\b(?:if|guard|switch|for|while|Task)\b[\s\S]{0,900}\}\s*label\s*:/;
|
|
408
|
+
const inlineActionParameterPattern =
|
|
409
|
+
/\bButton\s*\([^)]*\baction\s*:\s*\{[\s\S]{0,900}\b(?:if|guard|switch|for|while|Task)\b[\s\S]{0,900}\}/;
|
|
410
|
+
|
|
411
|
+
return inlineButtonActionPattern.test(swiftSource) || inlineActionParameterPattern.test(swiftSource);
|
|
412
|
+
};
|
|
413
|
+
|
|
404
414
|
export const hasSwiftDispatchQueueUsage = (source: string): boolean => {
|
|
405
415
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
406
416
|
if (current !== 'D' || !hasIdentifierAt(swiftSource, index, 'DispatchQueue')) {
|
|
@@ -688,6 +688,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
688
688
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonLazyScrollForEachUsage, ruleId: 'heuristics.ios.swiftui.non-lazy-scroll-foreach.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_LAZY_SCROLL_FOREACH_AST', message: 'AST heuristic detected ScrollView with a non-lazy stack feeding ForEach; LazyVStack/LazyHStack remain the preferred baseline for large scrollable collections.' },
|
|
689
689
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftViewBodyObjectCreationUsage, ruleId: 'heuristics.ios.swiftui.body-object-creation.ast', code: 'HEURISTICS_IOS_SWIFTUI_BODY_OBJECT_CREATION_AST', message: 'AST heuristic detected formatter object creation inside SwiftUI body; keep body simple and move expensive objects out of render paths.' },
|
|
690
690
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiImageDataDecodingUsage, ruleId: 'heuristics.ios.swiftui.image-data-decoding.ast', code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST', message: 'AST heuristic detected UIImage(data:) in SwiftUI presentation; downsample image data before rendering large images.' },
|
|
691
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiInlineActionLogicUsage, ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST', message: 'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.' },
|
|
691
692
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNavigationViewUsage, ruleId: 'heuristics.ios.navigation-view.ast', code: 'HEURISTICS_IOS_NAVIGATION_VIEW_AST', message: 'AST heuristic detected NavigationView usage.' },
|
|
692
693
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForegroundColorUsage, ruleId: 'heuristics.ios.foreground-color.ast', code: 'HEURISTICS_IOS_FOREGROUND_COLOR_AST', message: 'AST heuristic detected foregroundColor usage.' },
|
|
693
694
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCornerRadiusUsage, ruleId: 'heuristics.ios.corner-radius.ast', code: 'HEURISTICS_IOS_CORNER_RADIUS_AST', message: 'AST heuristic detected cornerRadius usage.' },
|
|
@@ -968,6 +968,25 @@ export const iosRules: RuleSet = [
|
|
|
968
968
|
code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST',
|
|
969
969
|
},
|
|
970
970
|
},
|
|
971
|
+
{
|
|
972
|
+
id: 'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
973
|
+
description: 'Detects inline control-flow logic inside SwiftUI action handlers.',
|
|
974
|
+
severity: 'WARN',
|
|
975
|
+
platform: 'ios',
|
|
976
|
+
locked: true,
|
|
977
|
+
when: {
|
|
978
|
+
kind: 'Heuristic',
|
|
979
|
+
where: {
|
|
980
|
+
ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
then: {
|
|
984
|
+
kind: 'Finding',
|
|
985
|
+
message:
|
|
986
|
+
'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.',
|
|
987
|
+
code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST',
|
|
988
|
+
},
|
|
989
|
+
},
|
|
971
990
|
{
|
|
972
991
|
id: 'heuristics.ios.navigation-view.ast',
|
|
973
992
|
description: 'Detects NavigationView usage in iOS production code.',
|
|
@@ -266,6 +266,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
266
266
|
heuristicDetector('ios.swiftui.image-data-decoding', [
|
|
267
267
|
'heuristics.ios.swiftui.image-data-decoding.ast',
|
|
268
268
|
]),
|
|
269
|
+
'skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic':
|
|
270
|
+
heuristicDetector('ios.swiftui.inline-action-logic', [
|
|
271
|
+
'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
272
|
+
]),
|
|
269
273
|
'skills.ios.no-scrollview-shows-indicators': heuristicDetector(
|
|
270
274
|
'ios.scrollview-shows-indicators',
|
|
271
275
|
['heuristics.ios.scrollview-shows-indicators.ast']
|
|
@@ -16,7 +16,7 @@ const MARKDOWN_BOLD_PATTERN = /[*_]{1,3}/g;
|
|
|
16
16
|
const MULTISPACE_PATTERN = /\s+/g;
|
|
17
17
|
const AST_NODE_ID_PATTERN = /\bheuristics\.[a-z0-9._-]+\.ast\b/gi;
|
|
18
18
|
const RULE_KEYWORDS =
|
|
19
|
-
/\b(always|siempre|prefer|use|usar|avoid|evitar|never|nunca|must|obligatorio|required|disallow|do not|no|suggest)\b/i;
|
|
19
|
+
/\b(always|siempre|prefer|use|usar|avoid|evitar|never|nunca|must|obligatorio|required|disallow|do not|no|suggest|should)\b/i;
|
|
20
20
|
|
|
21
21
|
const normalizeForLookup = (value: string): string => {
|
|
22
22
|
return value
|
|
@@ -381,6 +381,13 @@ const normalizeKnownRuleTarget = (
|
|
|
381
381
|
) {
|
|
382
382
|
return 'skills.ios.guideline.ios-swiftui-expert.suggest-image-downsampling-when-uiimage-data-is-encountered';
|
|
383
383
|
}
|
|
384
|
+
if (
|
|
385
|
+
(includes('action handlers') && includes('reference methods')) ||
|
|
386
|
+
(includes('action handlers') && includes('inline logic')) ||
|
|
387
|
+
(includes('handlers should reference methods') && includes('inline logic'))
|
|
388
|
+
) {
|
|
389
|
+
return 'skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic';
|
|
390
|
+
}
|
|
384
391
|
if (
|
|
385
392
|
includes('scrollindicators hidden') ||
|
|
386
393
|
includes('scroll indicators hidden') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.227",
|
|
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": {
|
package/skills.lock.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"compilerVersion": "1.0.0",
|
|
4
|
-
"generatedAt": "2026-05-13T16:
|
|
4
|
+
"generatedAt": "2026-05-13T16:27:11.446Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8632,8 +8632,20 @@
|
|
|
8632
8632
|
"name": "ios-swiftui-expert-guidelines",
|
|
8633
8633
|
"version": "1.0.0",
|
|
8634
8634
|
"source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8635
|
-
"hash": "
|
|
8635
|
+
"hash": "a69126588892428f136f55666d721346015d4684ded6dfa07509045db95f7f48",
|
|
8636
8636
|
"rules": [
|
|
8637
|
+
{
|
|
8638
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
|
|
8639
|
+
"description": "Action handlers should reference methods, not contain inline logic",
|
|
8640
|
+
"severity": "WARN",
|
|
8641
|
+
"platform": "ios",
|
|
8642
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8643
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8644
|
+
"confidence": "MEDIUM",
|
|
8645
|
+
"locked": true,
|
|
8646
|
+
"evaluationMode": "AUTO",
|
|
8647
|
+
"origin": "core"
|
|
8648
|
+
},
|
|
8637
8649
|
{
|
|
8638
8650
|
"id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
|
|
8639
8651
|
"description": "Always mark @State and @StateObject as private (makes dependencies clear)",
|
|
@@ -8742,6 +8754,18 @@
|
|
|
8742
8754
|
"evaluationMode": "DECLARATIVE",
|
|
8743
8755
|
"origin": "core"
|
|
8744
8756
|
},
|
|
8757
|
+
{
|
|
8758
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.sheets-should-own-their-actions-and-call-dismiss-internally",
|
|
8759
|
+
"description": "Sheets should own their actions and call dismiss() internally",
|
|
8760
|
+
"severity": "WARN",
|
|
8761
|
+
"platform": "ios",
|
|
8762
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8763
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8764
|
+
"confidence": "MEDIUM",
|
|
8765
|
+
"locked": true,
|
|
8766
|
+
"evaluationMode": "DECLARATIVE",
|
|
8767
|
+
"origin": "core"
|
|
8768
|
+
},
|
|
8745
8769
|
{
|
|
8746
8770
|
"id": "skills.ios.guideline.ios-swiftui-expert.suggest-image-downsampling-when-uiimage-data-is-encountered",
|
|
8747
8771
|
"description": "Suggest image downsampling when UIImage(data:) is used (as optional optimization, see references/image-optimization.md)",
|
|
@@ -8946,6 +8970,18 @@
|
|
|
8946
8970
|
"evaluationMode": "DECLARATIVE",
|
|
8947
8971
|
"origin": "core"
|
|
8948
8972
|
},
|
|
8973
|
+
{
|
|
8974
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.views-should-work-in-any-context-don-t-assume-screen-size-or-presentat",
|
|
8975
|
+
"description": "Views should work in any context (don't assume screen size or presentation style)",
|
|
8976
|
+
"severity": "WARN",
|
|
8977
|
+
"platform": "ios",
|
|
8978
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8979
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8980
|
+
"confidence": "MEDIUM",
|
|
8981
|
+
"locked": true,
|
|
8982
|
+
"evaluationMode": "DECLARATIVE",
|
|
8983
|
+
"origin": "core"
|
|
8984
|
+
},
|
|
8949
8985
|
{
|
|
8950
8986
|
"id": "skills.ios.no-anyview",
|
|
8951
8987
|
"description": "Avoid AnyView in list rows",
|