pumuki 6.3.285 → 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 +83 -0
- package/core/facts/detectors/text/ios.ts +119 -0
- package/core/facts/detectors/typescript/index.test.ts +555 -0
- package/core/facts/detectors/typescript/index.ts +476 -0
- package/core/facts/extractHeuristicFacts.ts +25 -11
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +19 -0
- package/core/rules/presets/heuristics/typescript.test.ts +49 -1
- package/core/rules/presets/heuristics/typescript.ts +144 -0
- package/integrations/config/skillsDetectorRegistry.ts +49 -0
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
6.3.286
|
|
@@ -46,12 +46,23 @@ import {
|
|
|
46
46
|
hasSwiftHardcodedSensitiveStringUsage,
|
|
47
47
|
hasSwiftUnlocalizedDateFormatterUsage,
|
|
48
48
|
hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage,
|
|
49
|
+
collectSwiftIconOnlyControlWithoutAccessibilityLabelLines,
|
|
49
50
|
collectSwiftInteractiveControlWithoutAccessibilityIdentifierLines,
|
|
50
51
|
hasSwiftInteractiveControlWithoutAccessibilityIdentifierUsage,
|
|
51
52
|
collectSwiftBindableMissingForObservableBindingUsageLines,
|
|
52
53
|
collectSwiftExplicitColorStaticMemberLines,
|
|
53
54
|
collectSwiftFixedFontSizeLines,
|
|
54
55
|
collectSwiftHardcodedSensitiveStringLines,
|
|
56
|
+
collectSwiftHardcodedUiStringLines,
|
|
57
|
+
collectSwiftNonLazyScrollForEachLines,
|
|
58
|
+
collectSwiftUiForEachConditionalViewCountLines,
|
|
59
|
+
collectSwiftUiInlineActionLogicLines,
|
|
60
|
+
collectSwiftForEachIndicesLines,
|
|
61
|
+
collectSwiftForEachSelfIdentityLines,
|
|
62
|
+
collectSwiftInlineForEachTransformLines,
|
|
63
|
+
collectSwiftUiConditionalSameViewIdentityLines,
|
|
64
|
+
collectSwiftForegroundColorLines,
|
|
65
|
+
collectSwiftCornerRadiusLines,
|
|
55
66
|
collectSwiftMagicNumberLayoutLines,
|
|
56
67
|
hasSwiftBindableMissingForObservableBindingUsage,
|
|
57
68
|
hasSwiftLooseAssetResourceUsage,
|
|
@@ -111,6 +122,7 @@ import {
|
|
|
111
122
|
hasSwiftExplicitColorStaticMemberUsage,
|
|
112
123
|
hasSwiftClosureBasedViewBuilderContentUsage,
|
|
113
124
|
collectSwiftForceUnwrapLines,
|
|
125
|
+
collectSwiftWarningSuppressionLines,
|
|
114
126
|
hasSwiftLargeConfigContextViewPropertyUsage,
|
|
115
127
|
hasSwiftUiConditionalSameViewIdentityUsage,
|
|
116
128
|
hasSwiftUiParentOwnedSheetActionUsage,
|
|
@@ -124,6 +136,7 @@ import {
|
|
|
124
136
|
hasSwiftTaskDetachedUsage,
|
|
125
137
|
hasSwiftUnownedSelfCaptureUsage,
|
|
126
138
|
hasSwiftWaitForExpectationsUsage,
|
|
139
|
+
hasSwiftWarningSuppressionUsage,
|
|
127
140
|
hasSwiftUIScreenMainBoundsUsage,
|
|
128
141
|
hasSwiftXCTestAssertionUsage,
|
|
129
142
|
hasSwiftXCTUnwrapUsage,
|
|
@@ -164,6 +177,28 @@ if waitersByKey[key] != nil {
|
|
|
164
177
|
assert.deepEqual(collectSwiftForceUnwrapLines(source), []);
|
|
165
178
|
});
|
|
166
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
|
+
|
|
167
202
|
test('hasSwiftUnownedSelfCaptureUsage detecta captures unowned y preserva weak', () => {
|
|
168
203
|
const source = `
|
|
169
204
|
final class ProfileViewModel {
|
|
@@ -507,7 +542,9 @@ struct CheckoutView: View {
|
|
|
507
542
|
`;
|
|
508
543
|
|
|
509
544
|
assert.equal(hasSwiftUiInlineActionLogicUsage(source), true);
|
|
545
|
+
assert.deepEqual(collectSwiftUiInlineActionLogicLines(source), [6]);
|
|
510
546
|
assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
|
|
547
|
+
assert.deepEqual(collectSwiftUiInlineActionLogicLines(safe), []);
|
|
511
548
|
});
|
|
512
549
|
|
|
513
550
|
test('hasSwiftForEachSelfIdentityUsage detecta id self y preserva ids estables', () => {
|
|
@@ -1206,7 +1243,9 @@ struct OrdersView: View {
|
|
|
1206
1243
|
`;
|
|
1207
1244
|
|
|
1208
1245
|
assert.equal(hasSwiftHardcodedUiStringUsage(source), true);
|
|
1246
|
+
assert.deepEqual(collectSwiftHardcodedUiStringLines(source), [5, 6, 7, 8, 9]);
|
|
1209
1247
|
assert.equal(hasSwiftHardcodedUiStringUsage(ignored), false);
|
|
1248
|
+
assert.deepEqual(collectSwiftHardcodedUiStringLines(ignored), []);
|
|
1210
1249
|
});
|
|
1211
1250
|
|
|
1212
1251
|
test('detector iOS de assets detecta recursos sueltos sin confundir asset catalogs', () => {
|
|
@@ -1342,7 +1381,9 @@ struct ToolbarView: View {
|
|
|
1342
1381
|
`;
|
|
1343
1382
|
|
|
1344
1383
|
assert.equal(hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(source), true);
|
|
1384
|
+
assert.deepEqual(collectSwiftIconOnlyControlWithoutAccessibilityLabelLines(source), [4]);
|
|
1345
1385
|
assert.equal(hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(ignored), false);
|
|
1386
|
+
assert.deepEqual(collectSwiftIconOnlyControlWithoutAccessibilityLabelLines(ignored), []);
|
|
1346
1387
|
});
|
|
1347
1388
|
|
|
1348
1389
|
test('detector iOS de accesibilidad detecta controles interactivos sin accessibilityIdentifier', () => {
|
|
@@ -1652,6 +1693,48 @@ let ignored = "Color.green"
|
|
|
1652
1693
|
assert.deepEqual(collectSwiftExplicitColorStaticMemberLines(safe), []);
|
|
1653
1694
|
});
|
|
1654
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]);
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1655
1738
|
test('hasSwiftClosureBasedViewBuilderContentUsage detecta content closure y preserva @ViewBuilder let content', () => {
|
|
1656
1739
|
const source = `
|
|
1657
1740
|
struct Card<Content: View>: View {
|
|
@@ -109,6 +109,26 @@ 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
|
|
|
@@ -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')) {
|
|
@@ -989,6 +1038,32 @@ export const hasSwiftHardcodedUiStringUsage = (source: string): boolean => {
|
|
|
989
1038
|
});
|
|
990
1039
|
};
|
|
991
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
|
+
|
|
992
1067
|
export const hasSwiftLooseAssetResourceUsage = (source: string): boolean => {
|
|
993
1068
|
const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
|
|
994
1069
|
return withoutBlockComments.split(/\r?\n/).some((line) => {
|
|
@@ -1089,6 +1164,15 @@ export const hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage = (source: st
|
|
|
1089
1164
|
return false;
|
|
1090
1165
|
};
|
|
1091
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
|
+
|
|
1092
1176
|
export const collectSwiftInteractiveControlWithoutAccessibilityIdentifierLines = (
|
|
1093
1177
|
source: string
|
|
1094
1178
|
): readonly number[] => {
|
|
@@ -1248,10 +1332,21 @@ export const hasSwiftForEachIndicesUsage = (source: string): boolean => {
|
|
|
1248
1332
|
);
|
|
1249
1333
|
};
|
|
1250
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
|
+
|
|
1251
1342
|
export const hasSwiftForEachSelfIdentityUsage = (source: string): boolean => {
|
|
1252
1343
|
return hasSwiftSanitizedRegexMatch(source, /\bForEach\s*\([^)]*\bid\s*:\s*\\\.self\b/g);
|
|
1253
1344
|
};
|
|
1254
1345
|
|
|
1346
|
+
export const collectSwiftForEachSelfIdentityLines = (source: string): readonly number[] => {
|
|
1347
|
+
return collectSwiftRegexLines(source, /\bForEach\s*\([^)]*\bid\s*:\s*\\\.self\b/);
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1255
1350
|
export const hasSwiftSelfPrintChangesUsage = (source: string): boolean => {
|
|
1256
1351
|
return hasSwiftSanitizedRegexMatch(source, /\bSelf\s*\.\s*_printChanges\s*\(/g);
|
|
1257
1352
|
};
|
|
@@ -1263,6 +1358,13 @@ export const hasSwiftInlineForEachTransformUsage = (source: string): boolean =>
|
|
|
1263
1358
|
);
|
|
1264
1359
|
};
|
|
1265
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
|
+
|
|
1266
1368
|
const isUserSearchIdentifier = (value: string): boolean => {
|
|
1267
1369
|
return /^(?:query|search(?:Text|Term|Query|Value)?|filter(?:Text|Value)?|text|term|input)$/i.test(
|
|
1268
1370
|
value
|
|
@@ -1387,6 +1489,15 @@ export const hasSwiftUiConditionalSameViewIdentityUsage = (source: string): bool
|
|
|
1387
1489
|
return false;
|
|
1388
1490
|
};
|
|
1389
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
|
+
|
|
1390
1501
|
export const hasSwiftUiParentOwnedSheetActionUsage = (source: string): boolean => {
|
|
1391
1502
|
const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
|
|
1392
1503
|
const swiftUIViewBodyPattern =
|
|
@@ -1543,10 +1654,18 @@ export const hasSwiftForegroundColorUsage = (source: string): boolean => {
|
|
|
1543
1654
|
return hasSwiftUiModernizationSnapshotMatch(source, 'foreground-color');
|
|
1544
1655
|
};
|
|
1545
1656
|
|
|
1657
|
+
export const collectSwiftForegroundColorLines = (source: string): readonly number[] => {
|
|
1658
|
+
return collectSwiftRegexLines(source, /\.\s*foregroundColor\s*\(/);
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1546
1661
|
export const hasSwiftCornerRadiusUsage = (source: string): boolean => {
|
|
1547
1662
|
return hasSwiftUiModernizationSnapshotMatch(source, 'corner-radius');
|
|
1548
1663
|
};
|
|
1549
1664
|
|
|
1665
|
+
export const collectSwiftCornerRadiusLines = (source: string): readonly number[] => {
|
|
1666
|
+
return collectSwiftRegexLines(source, /\.\s*cornerRadius\s*\(/);
|
|
1667
|
+
};
|
|
1668
|
+
|
|
1550
1669
|
export const hasSwiftTabItemUsage = (source: string): boolean => {
|
|
1551
1670
|
return hasSwiftUiModernizationSnapshotMatch(source, 'tab-item');
|
|
1552
1671
|
};
|