pumuki 6.3.284 → 6.3.286

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.284
1
+ 6.3.286
@@ -46,9 +46,24 @@ import {
46
46
  hasSwiftHardcodedSensitiveStringUsage,
47
47
  hasSwiftUnlocalizedDateFormatterUsage,
48
48
  hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage,
49
+ collectSwiftIconOnlyControlWithoutAccessibilityLabelLines,
49
50
  collectSwiftInteractiveControlWithoutAccessibilityIdentifierLines,
50
51
  hasSwiftInteractiveControlWithoutAccessibilityIdentifierUsage,
51
52
  collectSwiftBindableMissingForObservableBindingUsageLines,
53
+ collectSwiftExplicitColorStaticMemberLines,
54
+ collectSwiftFixedFontSizeLines,
55
+ collectSwiftHardcodedSensitiveStringLines,
56
+ collectSwiftHardcodedUiStringLines,
57
+ collectSwiftNonLazyScrollForEachLines,
58
+ collectSwiftUiForEachConditionalViewCountLines,
59
+ collectSwiftUiInlineActionLogicLines,
60
+ collectSwiftForEachIndicesLines,
61
+ collectSwiftForEachSelfIdentityLines,
62
+ collectSwiftInlineForEachTransformLines,
63
+ collectSwiftUiConditionalSameViewIdentityLines,
64
+ collectSwiftForegroundColorLines,
65
+ collectSwiftCornerRadiusLines,
66
+ collectSwiftMagicNumberLayoutLines,
52
67
  hasSwiftBindableMissingForObservableBindingUsage,
53
68
  hasSwiftLooseAssetResourceUsage,
54
69
  hasSwiftLegacyOnChangeUsage,
@@ -107,6 +122,7 @@ import {
107
122
  hasSwiftExplicitColorStaticMemberUsage,
108
123
  hasSwiftClosureBasedViewBuilderContentUsage,
109
124
  collectSwiftForceUnwrapLines,
125
+ collectSwiftWarningSuppressionLines,
110
126
  hasSwiftLargeConfigContextViewPropertyUsage,
111
127
  hasSwiftUiConditionalSameViewIdentityUsage,
112
128
  hasSwiftUiParentOwnedSheetActionUsage,
@@ -120,6 +136,7 @@ import {
120
136
  hasSwiftTaskDetachedUsage,
121
137
  hasSwiftUnownedSelfCaptureUsage,
122
138
  hasSwiftWaitForExpectationsUsage,
139
+ hasSwiftWarningSuppressionUsage,
123
140
  hasSwiftUIScreenMainBoundsUsage,
124
141
  hasSwiftXCTestAssertionUsage,
125
142
  hasSwiftXCTUnwrapUsage,
@@ -160,6 +177,28 @@ if waitersByKey[key] != nil {
160
177
  assert.deepEqual(collectSwiftForceUnwrapLines(source), []);
161
178
  });
162
179
 
180
+ test('collectSwiftWarningSuppressionLines localiza supresiones de warnings Swift accionables', () => {
181
+ const source = [
182
+ 'struct CheckoutView {',
183
+ ' // swiftlint:disable force_unwrapping',
184
+ ' let title = "swiftlint:disable is just copy"',
185
+ ' #warning("Remove temporary checkout branch")',
186
+ ' // swiftformat:disable:next redundantSelf',
187
+ '}',
188
+ ].join('\n');
189
+ const safe = [
190
+ 'struct CheckoutView {',
191
+ ' let title = "No warnings are suppressed"',
192
+ ' // regular design note without disabling tooling',
193
+ '}',
194
+ ].join('\n');
195
+
196
+ assert.equal(hasSwiftWarningSuppressionUsage(source), true);
197
+ assert.deepEqual(collectSwiftWarningSuppressionLines(source), [2, 4, 5]);
198
+ assert.equal(hasSwiftWarningSuppressionUsage(safe), false);
199
+ assert.deepEqual(collectSwiftWarningSuppressionLines(safe), []);
200
+ });
201
+
163
202
  test('hasSwiftUnownedSelfCaptureUsage detecta captures unowned y preserva weak', () => {
164
203
  const source = `
165
204
  final class ProfileViewModel {
@@ -237,12 +276,15 @@ send()
237
276
  assert.equal(hasSwiftProductionCommentUsage(safe), false);
238
277
  });
239
278
 
240
- test('hasSwiftCombineSinkWithoutStoreUsage detecta sink sin store y preserva cancellables', () => {
279
+ test('hasSwiftCombineSinkWithoutStoreUsage detecta sink/assign sin store y preserva cancellables', () => {
241
280
  const source = `
242
281
  publisher
243
282
  .sink { value in
244
283
  render(value)
245
284
  }
285
+
286
+ publisher
287
+ .assign(to: \\Model.title, on: model)
246
288
  `;
247
289
  const safe = `
248
290
  publisher
@@ -250,7 +292,11 @@ publisher
250
292
  render(value)
251
293
  }
252
294
  .store(in: &cancellables)
295
+ publisher
296
+ .assign(to: \\Model.title, on: model)
297
+ .store(in: &cancellables)
253
298
  let ignored = ".sink { value in }"
299
+ let ignoredAssign = ".assign(to: \\\\Model.title, on: model)"
254
300
  // publisher.sink { value in }
255
301
  `;
256
302
 
@@ -496,7 +542,9 @@ struct CheckoutView: View {
496
542
  `;
497
543
 
498
544
  assert.equal(hasSwiftUiInlineActionLogicUsage(source), true);
545
+ assert.deepEqual(collectSwiftUiInlineActionLogicLines(source), [6]);
499
546
  assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
547
+ assert.deepEqual(collectSwiftUiInlineActionLogicLines(safe), []);
500
548
  });
501
549
 
502
550
  test('hasSwiftForEachSelfIdentityUsage detecta id self y preserva ids estables', () => {
@@ -1011,7 +1059,9 @@ struct ProfileView: View {
1011
1059
  `;
1012
1060
 
1013
1061
  assert.equal(hasSwiftMagicNumberLayoutUsage(source), true);
1062
+ assert.deepEqual(collectSwiftMagicNumberLayoutLines(source), [4, 6, 7]);
1014
1063
  assert.equal(hasSwiftMagicNumberLayoutUsage(constants), false);
1064
+ assert.deepEqual(collectSwiftMagicNumberLayoutLines(constants), []);
1015
1065
  });
1016
1066
 
1017
1067
  test('detectores de logging iOS detectan logs ad-hoc y PII en produccion', () => {
@@ -1054,7 +1104,9 @@ final class Credentials {
1054
1104
  `;
1055
1105
 
1056
1106
  assert.equal(hasSwiftHardcodedSensitiveStringUsage(source), true);
1107
+ assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(source), [3, 4]);
1057
1108
  assert.equal(hasSwiftHardcodedSensitiveStringUsage(safe), false);
1109
+ assert.deepEqual(collectSwiftHardcodedSensitiveStringLines(safe), []);
1058
1110
  });
1059
1111
 
1060
1112
  test('hasSwiftUnlocalizedDateFormatterUsage detecta dateFormat fijo sin locale explicito', () => {
@@ -1191,7 +1243,9 @@ struct OrdersView: View {
1191
1243
  `;
1192
1244
 
1193
1245
  assert.equal(hasSwiftHardcodedUiStringUsage(source), true);
1246
+ assert.deepEqual(collectSwiftHardcodedUiStringLines(source), [5, 6, 7, 8, 9]);
1194
1247
  assert.equal(hasSwiftHardcodedUiStringUsage(ignored), false);
1248
+ assert.deepEqual(collectSwiftHardcodedUiStringLines(ignored), []);
1195
1249
  });
1196
1250
 
1197
1251
  test('detector iOS de assets detecta recursos sueltos sin confundir asset catalogs', () => {
@@ -1225,7 +1279,9 @@ let text = "UIFont.systemFont(ofSize: 16)"
1225
1279
  `;
1226
1280
 
1227
1281
  assert.equal(hasSwiftFixedFontSizeUsage(source), true);
1282
+ assert.deepEqual(collectSwiftFixedFontSizeLines(source), [2, 3, 4]);
1228
1283
  assert.equal(hasSwiftFixedFontSizeUsage(ignored), false);
1284
+ assert.deepEqual(collectSwiftFixedFontSizeLines(ignored), []);
1229
1285
  });
1230
1286
 
1231
1287
  test('detector iOS de localización detecta alineación física sin confundir leading/trailing', () => {
@@ -1325,7 +1381,9 @@ struct ToolbarView: View {
1325
1381
  `;
1326
1382
 
1327
1383
  assert.equal(hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(source), true);
1384
+ assert.deepEqual(collectSwiftIconOnlyControlWithoutAccessibilityLabelLines(source), [4]);
1328
1385
  assert.equal(hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(ignored), false);
1386
+ assert.deepEqual(collectSwiftIconOnlyControlWithoutAccessibilityLabelLines(ignored), []);
1329
1387
  });
1330
1388
 
1331
1389
  test('detector iOS de accesibilidad detecta controles interactivos sin accessibilityIdentifier', () => {
@@ -1630,7 +1688,51 @@ let ignored = "Color.green"
1630
1688
  `;
1631
1689
 
1632
1690
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(source), true);
1691
+ assert.deepEqual(collectSwiftExplicitColorStaticMemberLines(source), [4, 5]);
1633
1692
  assert.equal(hasSwiftExplicitColorStaticMemberUsage(safe), false);
1693
+ assert.deepEqual(collectSwiftExplicitColorStaticMemberLines(safe), []);
1694
+ });
1695
+
1696
+ test('collectors iOS de INC-152 devuelven lineas accionables para reglas SwiftUI bloqueantes', () => {
1697
+ const source = `
1698
+ struct StoresView: View {
1699
+ var body: some View {
1700
+ ScrollView {
1701
+ VStack {
1702
+ ForEach(items.indices, id: \\.self) { index in
1703
+ if items[index].isVisible {
1704
+ Text(items[index].title).foregroundColor(.blue)
1705
+ }
1706
+ }
1707
+ ForEach(items.filter { $0.isVisible }) { item in
1708
+ Text(item.title)
1709
+ }
1710
+ if isLoading {
1711
+ Text("Loading")
1712
+ } else {
1713
+ Text("Ready")
1714
+ }
1715
+ Button {
1716
+ if isLoading { return }
1717
+ } label: {
1718
+ Text("Retry")
1719
+ }
1720
+ Image("hero").cornerRadius(12)
1721
+ }
1722
+ }
1723
+ }
1724
+ }
1725
+ `;
1726
+
1727
+ assert.deepEqual(collectSwiftNonLazyScrollForEachLines(source), [4, 6, 11]);
1728
+ assert.deepEqual(collectSwiftForEachIndicesLines(source), [6]);
1729
+ assert.deepEqual(collectSwiftForEachSelfIdentityLines(source), [6]);
1730
+ assert.deepEqual(collectSwiftUiForEachConditionalViewCountLines(source), [6, 7, 11, 14, 20]);
1731
+ assert.deepEqual(collectSwiftInlineForEachTransformLines(source), [11]);
1732
+ assert.deepEqual(collectSwiftUiConditionalSameViewIdentityLines(source), [7, 14, 20]);
1733
+ assert.deepEqual(collectSwiftUiInlineActionLogicLines(source), [19]);
1734
+ assert.deepEqual(collectSwiftForegroundColorLines(source), [8]);
1735
+ assert.deepEqual(collectSwiftCornerRadiusLines(source), [24]);
1634
1736
  });
1635
1737
 
1636
1738
  test('hasSwiftClosureBasedViewBuilderContentUsage detecta content closure y preserva @ViewBuilder let content', () => {
@@ -109,12 +109,32 @@ export const hasSwiftProductionCommentUsage = (source: string): boolean => {
109
109
  });
110
110
  };
111
111
 
112
+ export const collectSwiftWarningSuppressionLines = (source: string): readonly number[] => {
113
+ const lines: number[] = [];
114
+
115
+ source.split(/\r?\n/).forEach((rawLine, index) => {
116
+ const line = stripSwiftStringLiterals(rawLine);
117
+ if (
118
+ /^\s*#warning\s*\(/.test(line) ||
119
+ /\/\/\s*(?:swiftlint|swiftformat|periphery)\s*:\s*disable(?::|\b)/i.test(line)
120
+ ) {
121
+ lines.push(index + 1);
122
+ }
123
+ });
124
+
125
+ return sortedUniqueLines(lines);
126
+ };
127
+
128
+ export const hasSwiftWarningSuppressionUsage = (source: string): boolean => {
129
+ return collectSwiftWarningSuppressionLines(source).length > 0;
130
+ };
131
+
112
132
  export const hasSwiftCombineSinkWithoutStoreUsage = (source: string): boolean => {
113
133
  const lines = source.split(/\r?\n/);
114
134
 
115
135
  for (let index = 0; index < lines.length; index += 1) {
116
136
  const line = stripSwiftLineForSemanticScan(lines[index] ?? '');
117
- if (!/\.sink\s*(?:\(|\{)/.test(line)) {
137
+ if (!/\.(?:sink|assign)\s*(?:\(|\{)/.test(line)) {
118
138
  continue;
119
139
  }
120
140
 
@@ -502,6 +522,16 @@ export const hasSwiftNonLazyScrollForEachUsage = (source: string): boolean => {
502
522
  return nonLazyScrollableCollectionPattern.test(swiftSource);
503
523
  };
504
524
 
525
+ export const collectSwiftNonLazyScrollForEachLines = (source: string): readonly number[] => {
526
+ if (!hasSwiftNonLazyScrollForEachUsage(source)) {
527
+ return [];
528
+ }
529
+ return sortedUniqueLines([
530
+ ...collectSwiftRegexLines(source, /\bScrollView\s*(?:\([^)]*\))?\s*\{/),
531
+ ...collectSwiftRegexLines(source, /\bForEach\s*\(/),
532
+ ]);
533
+ };
534
+
505
535
  export const hasSwiftUiForEachConditionalViewCountUsage = (source: string): boolean => {
506
536
  const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
507
537
  const conditionalForEachPattern =
@@ -510,6 +540,18 @@ export const hasSwiftUiForEachConditionalViewCountUsage = (source: string): bool
510
540
  return conditionalForEachPattern.test(swiftSource);
511
541
  };
512
542
 
543
+ export const collectSwiftUiForEachConditionalViewCountLines = (
544
+ source: string
545
+ ): readonly number[] => {
546
+ if (!hasSwiftUiForEachConditionalViewCountUsage(source)) {
547
+ return [];
548
+ }
549
+ return sortedUniqueLines([
550
+ ...collectSwiftRegexLines(source, /\bForEach\s*\(/),
551
+ ...collectSwiftRegexLines(source, /\b(?:if|switch)\b/),
552
+ ]);
553
+ };
554
+
513
555
  export const hasSwiftViewBodyObjectCreationUsage = (source: string): boolean => {
514
556
  const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
515
557
  const viewBodyObjectCreationPattern =
@@ -533,6 +575,13 @@ export const hasSwiftUiInlineActionLogicUsage = (source: string): boolean => {
533
575
  return inlineButtonActionPattern.test(swiftSource) || inlineActionParameterPattern.test(swiftSource);
534
576
  };
535
577
 
578
+ export const collectSwiftUiInlineActionLogicLines = (source: string): readonly number[] => {
579
+ if (!hasSwiftUiInlineActionLogicUsage(source)) {
580
+ return [];
581
+ }
582
+ return collectSwiftRegexLines(source, /\bButton\s*(?:\(|\{)/);
583
+ };
584
+
536
585
  export const hasSwiftDispatchQueueUsage = (source: string): boolean => {
537
586
  return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
538
587
  if (current !== 'D' || !hasIdentifierAt(swiftSource, index, 'DispatchQueue')) {
@@ -831,6 +880,13 @@ export const hasSwiftMagicNumberLayoutUsage = (source: string): boolean => {
831
880
  return collectSwiftRegexLines(source, swiftUiLayoutNumberPattern).length > 0;
832
881
  };
833
882
 
883
+ export const collectSwiftMagicNumberLayoutLines = (source: string): readonly number[] => {
884
+ const swiftUiLayoutNumberPattern =
885
+ /(?:\b(?:VStack|HStack|ZStack|LazyVStack|LazyHStack)\s*\([^)]*\bspacing\s*:\s*|\.(?:padding|frame|offset|position|shadow|blur)\s*\([^)]*(?:\b(?:width|height|spacing|radius|x|y)\s*:\s*)?)\b(?:[3-9]|[1-9][0-9]+)(?:\.[0-9]+)?\b/;
886
+
887
+ return collectSwiftRegexLines(source, swiftUiLayoutNumberPattern);
888
+ };
889
+
834
890
  export const hasSwiftAdHocLoggingUsage = (source: string): boolean => {
835
891
  return collectSwiftRegexLines(
836
892
  source,
@@ -865,6 +921,13 @@ export const hasSwiftHardcodedSensitiveStringUsage = (source: string): boolean =
865
921
  ).length > 0;
866
922
  };
867
923
 
924
+ export const collectSwiftHardcodedSensitiveStringLines = (source: string): readonly number[] => {
925
+ return collectSwiftRegexLines(
926
+ source,
927
+ /\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
928
+ );
929
+ };
930
+
868
931
  export const hasSwiftUnlocalizedDateFormatterUsage = (source: string): boolean => {
869
932
  const sanitizedSource = sanitizeSwiftSourceForMultilineRegex(source);
870
933
  const formatterDeclarations = sanitizedSource.matchAll(
@@ -975,6 +1038,32 @@ export const hasSwiftHardcodedUiStringUsage = (source: string): boolean => {
975
1038
  });
976
1039
  };
977
1040
 
1041
+ export const collectSwiftHardcodedUiStringLines = (source: string): readonly number[] => {
1042
+ const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
1043
+ const matches: number[] = [];
1044
+ withoutBlockComments.split(/\r?\n/).forEach((line, index) => {
1045
+ if (/^\s*\/\//.test(line)) {
1046
+ return;
1047
+ }
1048
+ const withoutInlineComment = line.replace(/\/\/.*$/, '');
1049
+ const hasHardcodedUiLiteral = swiftUiLiteralTextPatterns.some((pattern) => {
1050
+ const match = withoutInlineComment.match(pattern);
1051
+ if (!match) {
1052
+ return false;
1053
+ }
1054
+ const literal = match[1]?.trim() ?? '';
1055
+ if (literal.length === 0) {
1056
+ return false;
1057
+ }
1058
+ return !looksLikeLocalizationKey(literal);
1059
+ });
1060
+ if (hasHardcodedUiLiteral) {
1061
+ matches.push(index + 1);
1062
+ }
1063
+ });
1064
+ return matches;
1065
+ };
1066
+
978
1067
  export const hasSwiftLooseAssetResourceUsage = (source: string): boolean => {
979
1068
  const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
980
1069
  return withoutBlockComments.split(/\r?\n/).some((line) => {
@@ -1009,6 +1098,27 @@ export const hasSwiftFixedFontSizeUsage = (source: string): boolean => {
1009
1098
  });
1010
1099
  };
1011
1100
 
1101
+ export const collectSwiftFixedFontSizeLines = (source: string): readonly number[] => {
1102
+ const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
1103
+ const matches: number[] = [];
1104
+ withoutBlockComments.split(/\r?\n/).forEach((line, index) => {
1105
+ if (/^\s*\/\//.test(line)) {
1106
+ return;
1107
+ }
1108
+ const sanitized = stripSwiftLineForSemanticScan(line);
1109
+ if (
1110
+ /\.\s*font\s*\(\s*\.\s*system\s*\(\s*size\s*:/.test(sanitized) ||
1111
+ /\bFont\s*\.\s*system\s*\(\s*size\s*:/.test(sanitized) ||
1112
+ /\bUIFont\s*\.\s*(?:systemFont|boldSystemFont|italicSystemFont)\s*\(\s*ofSize\s*:/.test(
1113
+ sanitized
1114
+ )
1115
+ ) {
1116
+ matches.push(index + 1);
1117
+ }
1118
+ });
1119
+ return matches;
1120
+ };
1121
+
1012
1122
  export const hasSwiftPhysicalTextAlignmentUsage = (source: string): boolean => {
1013
1123
  const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
1014
1124
  return withoutBlockComments.split(/\r?\n/).some((line) => {
@@ -1054,6 +1164,15 @@ export const hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage = (source: st
1054
1164
  return false;
1055
1165
  };
1056
1166
 
1167
+ export const collectSwiftIconOnlyControlWithoutAccessibilityLabelLines = (
1168
+ source: string
1169
+ ): readonly number[] => {
1170
+ if (!hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(source)) {
1171
+ return [];
1172
+ }
1173
+ return collectSwiftRegexLines(source, /\bButton\s*(?:\(|\{)/);
1174
+ };
1175
+
1057
1176
  export const collectSwiftInteractiveControlWithoutAccessibilityIdentifierLines = (
1058
1177
  source: string
1059
1178
  ): readonly number[] => {
@@ -1213,10 +1332,21 @@ export const hasSwiftForEachIndicesUsage = (source: string): boolean => {
1213
1332
  );
1214
1333
  };
1215
1334
 
1335
+ export const collectSwiftForEachIndicesLines = (source: string): readonly number[] => {
1336
+ return collectSwiftRegexLines(
1337
+ source,
1338
+ /\bForEach\s*\(\s*(?:Array\s*\(\s*)?[A-Za-z_][A-Za-z0-9_.]*\.indices\b/
1339
+ );
1340
+ };
1341
+
1216
1342
  export const hasSwiftForEachSelfIdentityUsage = (source: string): boolean => {
1217
1343
  return hasSwiftSanitizedRegexMatch(source, /\bForEach\s*\([^)]*\bid\s*:\s*\\\.self\b/g);
1218
1344
  };
1219
1345
 
1346
+ export const collectSwiftForEachSelfIdentityLines = (source: string): readonly number[] => {
1347
+ return collectSwiftRegexLines(source, /\bForEach\s*\([^)]*\bid\s*:\s*\\\.self\b/);
1348
+ };
1349
+
1220
1350
  export const hasSwiftSelfPrintChangesUsage = (source: string): boolean => {
1221
1351
  return hasSwiftSanitizedRegexMatch(source, /\bSelf\s*\.\s*_printChanges\s*\(/g);
1222
1352
  };
@@ -1228,6 +1358,13 @@ export const hasSwiftInlineForEachTransformUsage = (source: string): boolean =>
1228
1358
  );
1229
1359
  };
1230
1360
 
1361
+ export const collectSwiftInlineForEachTransformLines = (source: string): readonly number[] => {
1362
+ return collectSwiftRegexLines(
1363
+ source,
1364
+ /\bForEach\s*\(\s*(?:Array\s*\(\s*)?[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\s*\.\s*(?:filter|map|compactMap|sorted)\s*(?:\{|\()/
1365
+ );
1366
+ };
1367
+
1231
1368
  const isUserSearchIdentifier = (value: string): boolean => {
1232
1369
  return /^(?:query|search(?:Text|Term|Query|Value)?|filter(?:Text|Value)?|text|term|input)$/i.test(
1233
1370
  value
@@ -1281,6 +1418,13 @@ export const hasSwiftExplicitColorStaticMemberUsage = (source: string): boolean
1281
1418
  );
1282
1419
  };
1283
1420
 
1421
+ export const collectSwiftExplicitColorStaticMemberLines = (source: string): readonly number[] => {
1422
+ return collectSwiftRegexLines(
1423
+ source,
1424
+ /\bColor\s*\.\s*(?:accentColor|black|blue|brown|clear|cyan|gray|green|indigo|mint|orange|pink|primary|purple|red|secondary|teal|white|yellow)\b/g
1425
+ );
1426
+ };
1427
+
1284
1428
  export const hasSwiftClosureBasedViewBuilderContentUsage = (source: string): boolean => {
1285
1429
  return hasSwiftSanitizedRegexMatch(
1286
1430
  source,
@@ -1345,6 +1489,15 @@ export const hasSwiftUiConditionalSameViewIdentityUsage = (source: string): bool
1345
1489
  return false;
1346
1490
  };
1347
1491
 
1492
+ export const collectSwiftUiConditionalSameViewIdentityLines = (
1493
+ source: string
1494
+ ): readonly number[] => {
1495
+ if (!hasSwiftUiConditionalSameViewIdentityUsage(source)) {
1496
+ return [];
1497
+ }
1498
+ return collectSwiftRegexLines(source, /\bif\s+[^{}]+\{/);
1499
+ };
1500
+
1348
1501
  export const hasSwiftUiParentOwnedSheetActionUsage = (source: string): boolean => {
1349
1502
  const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
1350
1503
  const swiftUIViewBodyPattern =
@@ -1501,10 +1654,18 @@ export const hasSwiftForegroundColorUsage = (source: string): boolean => {
1501
1654
  return hasSwiftUiModernizationSnapshotMatch(source, 'foreground-color');
1502
1655
  };
1503
1656
 
1657
+ export const collectSwiftForegroundColorLines = (source: string): readonly number[] => {
1658
+ return collectSwiftRegexLines(source, /\.\s*foregroundColor\s*\(/);
1659
+ };
1660
+
1504
1661
  export const hasSwiftCornerRadiusUsage = (source: string): boolean => {
1505
1662
  return hasSwiftUiModernizationSnapshotMatch(source, 'corner-radius');
1506
1663
  };
1507
1664
 
1665
+ export const collectSwiftCornerRadiusLines = (source: string): readonly number[] => {
1666
+ return collectSwiftRegexLines(source, /\.\s*cornerRadius\s*\(/);
1667
+ };
1668
+
1508
1669
  export const hasSwiftTabItemUsage = (source: string): boolean => {
1509
1670
  return hasSwiftUiModernizationSnapshotMatch(source, 'tab-item');
1510
1671
  };