pumuki 6.3.339 → 6.3.341

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.
@@ -1129,6 +1129,53 @@ struct FeedView: View {
1129
1129
  assert.equal(hasSwiftUiForEachConditionalViewCountUsage(safe), false);
1130
1130
  });
1131
1131
 
1132
+ test('hasSwiftUiForEachConditionalViewCountUsage preserva filas ForEach extraidas con branching interno', () => {
1133
+ const source = `
1134
+ struct OnboardingCarouselView: View {
1135
+ let pages: [OnboardingPage]
1136
+ let indicators: [OnboardingIndicator]
1137
+
1138
+ var body: some View {
1139
+ LazyVStack {
1140
+ ForEach(pages) { page in
1141
+ OnboardingPageView(page: page)
1142
+ }
1143
+ LazyHStack {
1144
+ ForEach(indicators) { indicator in
1145
+ OnboardingPageIndicatorView(indicator: indicator)
1146
+ }
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+
1152
+ private struct OnboardingPageView: View {
1153
+ let page: OnboardingPage
1154
+
1155
+ var body: some View {
1156
+ switch page.kind {
1157
+ case .hero:
1158
+ OnboardingHeroStepView(page: page)
1159
+ case .feature:
1160
+ OnboardingFeatureStepView(page: page)
1161
+ }
1162
+ }
1163
+ }
1164
+
1165
+ private struct OnboardingPageIndicatorView: View {
1166
+ let indicator: OnboardingIndicator
1167
+
1168
+ var body: some View {
1169
+ Capsule()
1170
+ .fill(indicator.isSelected ? Color.primary : Color.secondary)
1171
+ }
1172
+ }
1173
+ `;
1174
+
1175
+ assert.equal(hasSwiftUiForEachConditionalViewCountUsage(source), false);
1176
+ assert.deepEqual(collectSwiftUiForEachConditionalViewCountLines(source), []);
1177
+ });
1178
+
1132
1179
  test('hasSwiftViewBodyObjectCreationUsage detecta formatter creado en body y preserva dependencia externa', () => {
1133
1180
  const source = `
1134
1181
  struct PriceView: View {
@@ -1979,12 +2026,21 @@ final class Credentials {
1979
2026
  let label = "public title"
1980
2027
  // let apiKey = "sk_live_123456789"
1981
2028
  }
2029
+ `;
2030
+ const emptyFormDefaults = `
2031
+ @MainActor
2032
+ public final class BuyerAppViewModel {
2033
+ public var authEmail: String = ""
2034
+ public var authPassword: String = ""
2035
+ }
1982
2036
  `;
1983
2037
 
1984
2038
  assert.equal(hasSwiftHardcodedSensitiveStringUsage(source), true);
1985
2039
  assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(source), [3, 4]);
1986
2040
  assert.equal(hasSwiftHardcodedSensitiveStringUsage(safe), false);
1987
2041
  assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(safe), []);
2042
+ assert.equal(hasSwiftHardcodedSensitiveStringUsage(emptyFormDefaults), false);
2043
+ assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(emptyFormDefaults), []);
1988
2044
  });
1989
2045
 
1990
2046
  test('hasSwiftUnlocalizedDateFormatterUsage detecta dateFormat fijo sin locale explicito', () => {
@@ -2772,7 +2828,7 @@ struct StoresView: View {
2772
2828
  assert.deepEqual(collectSwiftNonLazyScrollForEachLines(source), [4, 6, 11]);
2773
2829
  assert.deepEqual(collectSwiftForEachIndicesLines(source), [6]);
2774
2830
  assert.deepEqual(collectSwiftForEachSelfIdentityLines(source), [6]);
2775
- assert.deepEqual(collectSwiftUiForEachConditionalViewCountLines(source), [6, 7, 11, 14, 20]);
2831
+ assert.deepEqual(collectSwiftUiForEachConditionalViewCountLines(source), [6, 7]);
2776
2832
  assert.deepEqual(collectSwiftInlineForEachTransformLines(source), [11]);
2777
2833
  assert.deepEqual(collectSwiftUiConditionalSameViewIdentityLines(source), [7, 14, 20]);
2778
2834
  assert.deepEqual(collectSwiftUiInlineActionLogicLines(source), [19]);
@@ -660,24 +660,99 @@ export const collectSwiftNonLazyScrollForEachLines = (source: string): readonly
660
660
  ]);
661
661
  };
662
662
 
663
- export const hasSwiftUiForEachConditionalViewCountUsage = (source: string): boolean => {
664
- const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
665
- const conditionalForEachPattern =
666
- /\bForEach\s*\([^)]*\)\s*\{[\s\S]{0,1600}\b(?:if|switch)\b[\s\S]{0,1600}\}/;
663
+ const findMatchingSwiftParen = (source: string, openParenIndex: number): number => {
664
+ let depth = 0;
665
+ for (let index = openParenIndex; index < source.length; index += 1) {
666
+ const current = source[index];
667
+ if (current === '(') {
668
+ depth += 1;
669
+ } else if (current === ')') {
670
+ depth -= 1;
671
+ if (depth === 0) {
672
+ return index;
673
+ }
674
+ }
675
+ }
676
+ return -1;
677
+ };
667
678
 
668
- return conditionalForEachPattern.test(swiftSource);
679
+ const getLineNumberAtIndex = (source: string, targetIndex: number): number => {
680
+ let lineNumber = 1;
681
+ for (let index = 0; index < targetIndex && index < source.length; index += 1) {
682
+ if (source[index] === '\n') {
683
+ lineNumber += 1;
684
+ }
685
+ }
686
+ return lineNumber;
687
+ };
688
+
689
+ const collectSwiftUiForEachDirectConditionalLines = (source: string): readonly number[] => {
690
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
691
+ const lines: number[] = [];
692
+ const forEachPattern = /\bForEach\s*\(/g;
693
+
694
+ for (const match of sanitized.matchAll(forEachPattern)) {
695
+ const matchIndex = match.index ?? 0;
696
+ const openParenIndex = sanitized.indexOf('(', matchIndex);
697
+ if (openParenIndex < 0) {
698
+ continue;
699
+ }
700
+
701
+ const closeParenIndex = findMatchingSwiftParen(sanitized, openParenIndex);
702
+ if (closeParenIndex < 0) {
703
+ continue;
704
+ }
705
+
706
+ const openBraceIndex = sanitized.indexOf('{', closeParenIndex);
707
+ if (openBraceIndex < 0) {
708
+ continue;
709
+ }
710
+
711
+ const closeBraceIndex = findMatchingSwiftBrace(sanitized, openBraceIndex);
712
+ if (closeBraceIndex < 0) {
713
+ continue;
714
+ }
715
+
716
+ const body = sanitized.slice(openBraceIndex + 1, closeBraceIndex);
717
+ let depth = 0;
718
+ let hasDirectConditional = false;
719
+ const conditionalPattern = /\b(?:if|switch)\b/g;
720
+
721
+ for (const conditionalMatch of body.matchAll(conditionalPattern)) {
722
+ const conditionalIndex = conditionalMatch.index ?? 0;
723
+ for (let index = 0; index < conditionalIndex; index += 1) {
724
+ const current = body[index];
725
+ if (current === '{') {
726
+ depth += 1;
727
+ } else if (current === '}') {
728
+ depth = Math.max(0, depth - 1);
729
+ }
730
+ }
731
+ if (depth === 0) {
732
+ lines.push(getLineNumberAtIndex(sanitized, matchIndex));
733
+ lines.push(getLineNumberAtIndex(sanitized, openBraceIndex + 1 + conditionalIndex));
734
+ hasDirectConditional = true;
735
+ break;
736
+ }
737
+ depth = 0;
738
+ }
739
+
740
+ if (!hasDirectConditional) {
741
+ continue;
742
+ }
743
+ }
744
+
745
+ return sortedUniqueLines(lines);
746
+ };
747
+
748
+ export const hasSwiftUiForEachConditionalViewCountUsage = (source: string): boolean => {
749
+ return collectSwiftUiForEachDirectConditionalLines(source).length > 0;
669
750
  };
670
751
 
671
752
  export const collectSwiftUiForEachConditionalViewCountLines = (
672
753
  source: string
673
754
  ): readonly number[] => {
674
- if (!hasSwiftUiForEachConditionalViewCountUsage(source)) {
675
- return [];
676
- }
677
- return sortedUniqueLines([
678
- ...collectSwiftRegexLines(source, /\bForEach\s*\(/),
679
- ...collectSwiftRegexLines(source, /\b(?:if|switch)\b/),
680
- ]);
755
+ return collectSwiftUiForEachDirectConditionalLines(source);
681
756
  };
682
757
 
683
758
  export const hasSwiftViewBodyObjectCreationUsage = (source: string): boolean => {
@@ -1718,18 +1793,26 @@ export const collectSwiftSensitiveLoggingLines = (source: string): readonly numb
1718
1793
  return sortedUniqueLines(lines);
1719
1794
  };
1720
1795
 
1721
- export const hasSwiftHardcodedSensitiveStringUsage = (source: string): boolean => {
1722
- return collectSwiftRegexLines(
1723
- source,
1724
- /\b(?:(?:private|fileprivate|internal|public|open|static|class|final|lazy)\s+)*(?:let|var)\s+(?=[A-Za-z_])[A-Za-z0-9_]*(?:token|secret|password|apikey|clientsecret|privatekey|sessionid)[A-Za-z0-9_]*\s*(?::\s*String\s*)?=\s*""/i
1725
- ).length > 0;
1726
- };
1796
+ const swiftHardcodedSensitiveStringPattern =
1797
+ /\b(?:(?:private|fileprivate|internal|public|open|static|class|final|lazy)\s+)*(?:let|var)\s+(?=[A-Za-z_])[A-Za-z0-9_]*(?:token|secret|password|apikey|clientsecret|privatekey|sessionid)[A-Za-z0-9_]*\s*(?::\s*String\s*)?=\s*"((?:\\.|[^"\\])*)"/i;
1727
1798
 
1728
1799
  export const collectSwiftHardcodedSensitiveStringLines = (source: string): readonly number[] => {
1729
- return collectSwiftRegexLines(
1730
- source,
1731
- /\b(?:(?:private|fileprivate|internal|public|open|static|class|final|lazy)\s+)*(?:let|var)\s+(?=[A-Za-z_])[A-Za-z0-9_]*(?:token|secret|password|apikey|clientsecret|privatekey|sessionid)[A-Za-z0-9_]*\s*(?::\s*String\s*)?=\s*""/i
1732
- );
1800
+ const matches: number[] = [];
1801
+
1802
+ source.split(/\r?\n/).forEach((rawLine, index) => {
1803
+ const line = rawLine.replace(/\/\/.*$/, '');
1804
+ const match = swiftHardcodedSensitiveStringPattern.exec(line);
1805
+ const literalValue = match?.[1] ?? '';
1806
+ if (literalValue.length > 0) {
1807
+ matches.push(index + 1);
1808
+ }
1809
+ });
1810
+
1811
+ return matches;
1812
+ };
1813
+
1814
+ export const hasSwiftHardcodedSensitiveStringUsage = (source: string): boolean => {
1815
+ return collectSwiftHardcodedSensitiveStringLines(source).length > 0;
1733
1816
  };
1734
1817
 
1735
1818
  export const hasSwiftUnlocalizedDateFormatterUsage = (source: string): boolean => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.339",
3
+ "version": "6.3.341",
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": {