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 +1 -1
- package/core/facts/detectors/text/ios.test.ts +103 -1
- package/core/facts/detectors/text/ios.ts +162 -1
- package/core/facts/detectors/typescript/index.test.ts +555 -0
- package/core/facts/detectors/typescript/index.ts +476 -0
- package/core/facts/extractHeuristicFacts.ts +29 -15
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +21 -2
- package/core/rules/presets/heuristics/typescript.test.ts +49 -1
- package/core/rules/presets/heuristics/typescript.ts +144 -0
- package/integrations/config/skillsDetectorRegistry.ts +57 -0
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
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
|
};
|