pumuki 6.3.227 → 6.3.228
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 +35 -0
- package/core/facts/detectors/text/ios.ts +8 -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 +7 -1
- package/package.json +1 -1
- package/skills.lock.json +26 -2
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
hasSwiftLegacyXCTestImportUsage,
|
|
37
37
|
hasSwiftModernizableXCTestSuiteUsage,
|
|
38
38
|
hasSwiftNonLazyScrollForEachUsage,
|
|
39
|
+
hasSwiftUiForEachConditionalViewCountUsage,
|
|
39
40
|
hasSwiftViewBodyObjectCreationUsage,
|
|
40
41
|
hasSwiftUiImageDataDecodingUsage,
|
|
41
42
|
hasSwiftUiInlineActionLogicUsage,
|
|
@@ -165,6 +166,40 @@ struct FeedView: View {
|
|
|
165
166
|
assert.equal(hasSwiftNonLazyScrollForEachUsage(safe), false);
|
|
166
167
|
});
|
|
167
168
|
|
|
169
|
+
test('hasSwiftUiForEachConditionalViewCountUsage detecta branching condicional dentro de ForEach', () => {
|
|
170
|
+
const source = `
|
|
171
|
+
struct FeedView: View {
|
|
172
|
+
let items: [Item]
|
|
173
|
+
|
|
174
|
+
var body: some View {
|
|
175
|
+
ForEach(items) { item in
|
|
176
|
+
if item.isPromoted {
|
|
177
|
+
PromotedRow(item: item)
|
|
178
|
+
} else {
|
|
179
|
+
RegularRow(item: item)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
const safe = `
|
|
186
|
+
struct FeedView: View {
|
|
187
|
+
let items: [Item]
|
|
188
|
+
|
|
189
|
+
var body: some View {
|
|
190
|
+
ForEach(items) { item in
|
|
191
|
+
FeedRow(item: item)
|
|
192
|
+
}
|
|
193
|
+
let sample = "ForEach(items) { item in if item.isPromoted { PromotedRow(item: item) } }"
|
|
194
|
+
// ForEach(items) { item in if item.isPromoted { PromotedRow(item: item) } }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
assert.equal(hasSwiftUiForEachConditionalViewCountUsage(source), true);
|
|
200
|
+
assert.equal(hasSwiftUiForEachConditionalViewCountUsage(safe), false);
|
|
201
|
+
});
|
|
202
|
+
|
|
168
203
|
test('hasSwiftViewBodyObjectCreationUsage detecta formatter creado en body y preserva dependencia externa', () => {
|
|
169
204
|
const source = `
|
|
170
205
|
struct PriceView: View {
|
|
@@ -388,6 +388,14 @@ export const hasSwiftNonLazyScrollForEachUsage = (source: string): boolean => {
|
|
|
388
388
|
return nonLazyScrollableCollectionPattern.test(swiftSource);
|
|
389
389
|
};
|
|
390
390
|
|
|
391
|
+
export const hasSwiftUiForEachConditionalViewCountUsage = (source: string): boolean => {
|
|
392
|
+
const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
|
|
393
|
+
const conditionalForEachPattern =
|
|
394
|
+
/\bForEach\s*\([^)]*\)\s*\{[\s\S]{0,1600}\b(?:if|switch)\b[\s\S]{0,1600}\}/;
|
|
395
|
+
|
|
396
|
+
return conditionalForEachPattern.test(swiftSource);
|
|
397
|
+
};
|
|
398
|
+
|
|
391
399
|
export const hasSwiftViewBodyObjectCreationUsage = (source: string): boolean => {
|
|
392
400
|
const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
|
|
393
401
|
const viewBodyObjectCreationPattern =
|
|
@@ -679,6 +679,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
679
679
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPassedValueStateWrapperUsage, ruleId: 'heuristics.ios.passed-value-state-wrapper.ast', code: 'HEURISTICS_IOS_PASSED_VALUE_STATE_WRAPPER_AST', message: 'AST heuristic detected a passed value stored as @State/@StateObject via init wrapper ownership.' },
|
|
680
680
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachIndicesUsage, ruleId: 'heuristics.ios.foreach-indices.ast', code: 'HEURISTICS_IOS_FOREACH_INDICES_AST', message: 'AST heuristic detected ForEach(...indices...) usage where stable element identity may be preferred.' },
|
|
681
681
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineForEachTransformUsage, ruleId: 'heuristics.ios.swiftui.inline-foreach-transform.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST', message: 'AST heuristic detected inline filter/map/sort work inside ForEach; prefiltered or cached collections remain the preferred baseline.' },
|
|
682
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiForEachConditionalViewCountUsage, ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST', message: 'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.' },
|
|
682
683
|
{ platform: 'ios', pathCheck: isIOSApplicationOrPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftContainsUserFilterUsage, ruleId: 'heuristics.ios.contains-user-filter.ast', code: 'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST', message: 'AST heuristic detected contains() in a user-facing filter where localizedStandardContains() may be preferred.' },
|
|
683
684
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftGeometryReaderUsage, ruleId: 'heuristics.ios.geometryreader.ast', code: 'HEURISTICS_IOS_GEOMETRYREADER_AST', message: 'AST heuristic detected GeometryReader usage that may be replaceable with modern layout APIs.' },
|
|
684
685
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFontWeightBoldUsage, ruleId: 'heuristics.ios.font-weight-bold.ast', code: 'HEURISTICS_IOS_FONT_WEIGHT_BOLD_AST', message: 'AST heuristic detected fontWeight(.bold) usage where bold() may be preferred.' },
|
|
@@ -803,6 +803,25 @@ export const iosRules: RuleSet = [
|
|
|
803
803
|
code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST',
|
|
804
804
|
},
|
|
805
805
|
},
|
|
806
|
+
{
|
|
807
|
+
id: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
|
|
808
|
+
description: 'Detects conditional view counts inside SwiftUI ForEach rows.',
|
|
809
|
+
severity: 'WARN',
|
|
810
|
+
platform: 'ios',
|
|
811
|
+
locked: true,
|
|
812
|
+
when: {
|
|
813
|
+
kind: 'Heuristic',
|
|
814
|
+
where: {
|
|
815
|
+
ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
then: {
|
|
819
|
+
kind: 'Finding',
|
|
820
|
+
message:
|
|
821
|
+
'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.',
|
|
822
|
+
code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST',
|
|
823
|
+
},
|
|
824
|
+
},
|
|
806
825
|
{
|
|
807
826
|
id: 'heuristics.ios.contains-user-filter.ast',
|
|
808
827
|
description: 'Detects contains() usage in user-facing filter flows where localizedStandardContains() may be preferred.',
|
|
@@ -233,6 +233,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
233
233
|
heuristicDetector('ios.swiftui.inline-foreach-transform', [
|
|
234
234
|
'heuristics.ios.swiftui.inline-foreach-transform.ast',
|
|
235
235
|
]),
|
|
236
|
+
'skills.ios.guideline.ios-swiftui-expert.ensure-constant-number-of-views-per-foreach-element':
|
|
237
|
+
heuristicDetector('ios.swiftui.foreach-conditional-view-count', [
|
|
238
|
+
'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
|
|
239
|
+
]),
|
|
236
240
|
'skills.ios.no-contains-user-filter': heuristicDetector('ios.contains-user-filter', [
|
|
237
241
|
'heuristics.ios.contains-user-filter.ast',
|
|
238
242
|
]),
|
|
@@ -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|should)\b/i;
|
|
19
|
+
/\b(always|siempre|prefer|use|usar|avoid|evitar|ensure|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
|
|
@@ -316,6 +316,12 @@ const normalizeKnownRuleTarget = (
|
|
|
316
316
|
) {
|
|
317
317
|
return 'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache';
|
|
318
318
|
}
|
|
319
|
+
if (
|
|
320
|
+
includes('constant number of views per foreach element') ||
|
|
321
|
+
(includes('foreach') && includes('constant number of views'))
|
|
322
|
+
) {
|
|
323
|
+
return 'skills.ios.guideline.ios-swiftui-expert.ensure-constant-number-of-views-per-foreach-element';
|
|
324
|
+
}
|
|
319
325
|
if (
|
|
320
326
|
includes('localizedstandardcontains') ||
|
|
321
327
|
includes('localized standard contains') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.228",
|
|
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:36:55.011Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8632,7 +8632,7 @@
|
|
|
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": "87916172b71811a09b147336b1625bce94571b1f9c66885bd136cfac27079b0d",
|
|
8636
8636
|
"rules": [
|
|
8637
8637
|
{
|
|
8638
8638
|
"id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
|
|
@@ -8682,6 +8682,30 @@
|
|
|
8682
8682
|
"evaluationMode": "AUTO",
|
|
8683
8683
|
"origin": "core"
|
|
8684
8684
|
},
|
|
8685
|
+
{
|
|
8686
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.ensure-constant-number-of-views-per-foreach-element",
|
|
8687
|
+
"description": "Ensure constant number of views per ForEach element",
|
|
8688
|
+
"severity": "WARN",
|
|
8689
|
+
"platform": "ios",
|
|
8690
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8691
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8692
|
+
"confidence": "MEDIUM",
|
|
8693
|
+
"locked": true,
|
|
8694
|
+
"evaluationMode": "AUTO",
|
|
8695
|
+
"origin": "core"
|
|
8696
|
+
},
|
|
8697
|
+
{
|
|
8698
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.ensure-foreach-uses-stable-identity-see-references-list-patterns-md",
|
|
8699
|
+
"description": "Ensure ForEach uses stable identity (see references/list-patterns.md)",
|
|
8700
|
+
"severity": "WARN",
|
|
8701
|
+
"platform": "ios",
|
|
8702
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8703
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8704
|
+
"confidence": "MEDIUM",
|
|
8705
|
+
"locked": true,
|
|
8706
|
+
"evaluationMode": "DECLARATIVE",
|
|
8707
|
+
"origin": "core"
|
|
8708
|
+
},
|
|
8685
8709
|
{
|
|
8686
8710
|
"id": "skills.ios.guideline.ios-swiftui-expert.keep-view-body-simple-and-pure-no-side-effects-or-complex-logic",
|
|
8687
8711
|
"description": "Keep view body simple and pure (no side effects or complex logic)",
|