@pobammer-ts/small-rules 1.0.0 → 2.0.0
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/README.md +35 -0
- package/dist/index.d.ts +8 -15
- package/dist/index.js +111 -127
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# `@pobammer-ts/small-rules`
|
|
2
|
+
|
|
3
|
+
A collection of [Oxlint](https://oxc.rs)-native rules for linting [roblox-ts](https://roblox-ts.com/) projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
ni -D @pobammer-ts/small-rules
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This package is an [Oxlint plugin](https://oxc.rs/docs/guide/usage/plugins) and must be used with [Oxlint](https://oxc.rs) v1.69.0 or later. It also requires TypeScript 6 and Node.js `^20.19.0` / `>=22.12.0`.
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Register the plugin in your `.oxlintrc.json` and enable the rules you want:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"plugins": ["@pobammer-ts/small-rules"],
|
|
20
|
+
"rules": {
|
|
21
|
+
"small-rules/no-print": "error",
|
|
22
|
+
"small-rules/no-warn": "error"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
All rules are namespaced under the `small-rules/` prefix. Pick the subset that fits your project — there is no bulk opt-in.
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
Full rule documentation is forthcoming alongside the project docs site.
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
MIT © HowManySmall
|
package/dist/index.d.ts
CHANGED
|
@@ -680,13 +680,20 @@ declare const smallRules: import("oxlint-plugin-utilities").Plugin<{
|
|
|
680
680
|
readonly type: "object";
|
|
681
681
|
}]>;
|
|
682
682
|
"prefer-expect-assertions": import("oxlint-plugin-utilities").CreateRule<readonly [{
|
|
683
|
+
readonly additionalAssertionFunctions?: readonly string[];
|
|
683
684
|
readonly additionalExpectCallNames?: readonly string[];
|
|
684
685
|
readonly onlyFunctionsWithAsyncKeyword?: boolean;
|
|
685
686
|
readonly onlyFunctionsWithExpectInCallback?: boolean;
|
|
686
687
|
readonly onlyFunctionsWithExpectInLoop?: boolean;
|
|
687
|
-
}], "assertionsRequiresNumberArgument" | "assertionsRequiresOneArgument" | "hasAssertionsTakesNoArguments" | "haveExpectAssertions" | "suggestAddingAssertions" | "suggestAddingHasAssertions" | "wrongAssertionCount", readonly [{
|
|
688
|
+
}], "assertionsRequiresNumberArgument" | "assertionsRequiresOneArgument" | "hasAssertionsTakesNoArguments" | "haveExpectAssertions" | "preferAssertionsCount" | "suggestAddingAssertions" | "suggestAddingHasAssertions" | "wrongAssertionCount", readonly [{
|
|
688
689
|
readonly additionalProperties: false;
|
|
689
690
|
readonly properties: {
|
|
691
|
+
readonly additionalAssertionFunctions: {
|
|
692
|
+
readonly items: {
|
|
693
|
+
readonly type: "string";
|
|
694
|
+
};
|
|
695
|
+
readonly type: "array";
|
|
696
|
+
};
|
|
690
697
|
readonly additionalExpectCallNames: {
|
|
691
698
|
readonly items: {
|
|
692
699
|
readonly type: "string";
|
|
@@ -705,20 +712,6 @@ declare const smallRules: import("oxlint-plugin-utilities").Plugin<{
|
|
|
705
712
|
};
|
|
706
713
|
readonly type: "object";
|
|
707
714
|
}]>;
|
|
708
|
-
"prefer-expect-assertions-count": import("oxlint-plugin-utilities").CreateRule<readonly [{
|
|
709
|
-
readonly additionalAssertionFunctions?: readonly string[];
|
|
710
|
-
}], "preferAssertionsCount", readonly [{
|
|
711
|
-
readonly additionalProperties: false;
|
|
712
|
-
readonly properties: {
|
|
713
|
-
readonly additionalAssertionFunctions: {
|
|
714
|
-
readonly items: {
|
|
715
|
-
readonly type: "string";
|
|
716
|
-
};
|
|
717
|
-
readonly type: "array";
|
|
718
|
-
};
|
|
719
|
-
};
|
|
720
|
-
readonly type: "object";
|
|
721
|
-
}]>;
|
|
722
715
|
"prefer-hoisted-jsx-elements": import("oxlint-plugin-utilities").CreateRule<readonly [{
|
|
723
716
|
readonly additionalHoistableComponents?: readonly string[];
|
|
724
717
|
readonly additionalStaticFactories?: readonly string[];
|
package/dist/index.js
CHANGED
|
@@ -1230,92 +1230,25 @@ function addScore(current, addition, config, ceiling) {
|
|
|
1230
1230
|
if (!config.performanceMode) return nextScore;
|
|
1231
1231
|
return Math.min(nextScore, ceiling);
|
|
1232
1232
|
}
|
|
1233
|
+
function addStructuralScore(current, node, depth, config, cache, depthMultiplierCache, ceiling, bonus = 0) {
|
|
1234
|
+
return addScore(current, calculateStructuralComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) + bonus, config, ceiling);
|
|
1235
|
+
}
|
|
1233
1236
|
function addNestedTypeAnnotationScores(current, members, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1234
1237
|
let score = current;
|
|
1235
1238
|
for (const member of members) {
|
|
1236
1239
|
const typeAnnotation = getNestedTypeAnnotation(member);
|
|
1237
1240
|
if (typeAnnotation === void 0) continue;
|
|
1238
|
-
score =
|
|
1241
|
+
score = addStructuralScore(score, typeAnnotation, depth, config, cache, depthMultiplierCache, ceiling);
|
|
1239
1242
|
}
|
|
1240
1243
|
return score;
|
|
1241
1244
|
}
|
|
1242
1245
|
function getDepthMultiplier(depth, cache) {
|
|
1243
1246
|
const cached = cache.get(depth);
|
|
1244
1247
|
if (cached !== void 0) return cached;
|
|
1245
|
-
const computed = Math.
|
|
1248
|
+
const computed = Math.log2(depth + 1);
|
|
1246
1249
|
cache.set(depth, computed);
|
|
1247
1250
|
return computed;
|
|
1248
1251
|
}
|
|
1249
|
-
function calculateChildComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1250
|
-
return calculateStructuralComplexity(node, depth, config, cache, depthMultiplierCache, ceiling);
|
|
1251
|
-
}
|
|
1252
|
-
function addChildComplexity(score, node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1253
|
-
return addScore(score, calculateChildComplexity(node, depth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1254
|
-
}
|
|
1255
|
-
function getDefinedNodes(nodes) {
|
|
1256
|
-
return nodes.filter((node) => node !== void 0);
|
|
1257
|
-
}
|
|
1258
|
-
function addChildComplexities(score, nodes, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1259
|
-
let nextScore = score;
|
|
1260
|
-
for (const child of nodes) nextScore = addChildComplexity(nextScore, child, depth, config, cache, depthMultiplierCache, ceiling);
|
|
1261
|
-
return nextScore;
|
|
1262
|
-
}
|
|
1263
|
-
function calculateArrayTypeComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1264
|
-
if (!isRecord$1(node)) return 1;
|
|
1265
|
-
const elementType = getNodeValue(node, "elementType");
|
|
1266
|
-
if (elementType === void 0) return 1;
|
|
1267
|
-
return addScore(calculateChildComplexity(elementType, depth, config, cache, depthMultiplierCache, ceiling), 1, config, ceiling);
|
|
1268
|
-
}
|
|
1269
|
-
function calculateConditionalTypeComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1270
|
-
if (!isRecord$1(node)) return 3;
|
|
1271
|
-
return addChildComplexities(3, getDefinedNodes([
|
|
1272
|
-
getNodeValue(node, "checkType"),
|
|
1273
|
-
getNodeValue(node, "extendsType"),
|
|
1274
|
-
getNodeValue(node, "trueType"),
|
|
1275
|
-
getNodeValue(node, "falseType")
|
|
1276
|
-
]), depth, config, cache, depthMultiplierCache, ceiling);
|
|
1277
|
-
}
|
|
1278
|
-
function calculateFunctionTypeComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1279
|
-
if (!isRecord$1(node)) return 2;
|
|
1280
|
-
return addChildComplexities(2, getDefinedNodes([...getNodeArrayValue(node, "params")?.map(getNestedTypeAnnotation) ?? [], getReturnTypeAnnotation$1(node)]), depth, config, cache, depthMultiplierCache, ceiling);
|
|
1281
|
-
}
|
|
1282
|
-
function calculateInterfaceComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1283
|
-
if (!isRecord$1(node)) return config.interfacePenalty;
|
|
1284
|
-
const extendsLength = getNodeArrayValue(node, "extends")?.length ?? 0;
|
|
1285
|
-
const body = getNodeValue(node, "body");
|
|
1286
|
-
const members = body !== void 0 && isRecord$1(body) ? getNodeArrayValue(body, "body") ?? [] : [];
|
|
1287
|
-
let score = config.interfacePenalty;
|
|
1288
|
-
if (extendsLength > 0) score = addScore(score, extendsLength * 5, config, ceiling);
|
|
1289
|
-
score = addScore(score, members.length * 2, config, ceiling);
|
|
1290
|
-
return addNestedTypeAnnotationScores(score, members, depth, config, cache, depthMultiplierCache, ceiling);
|
|
1291
|
-
}
|
|
1292
|
-
function calculateTypeListComplexity(node, depth, typePenalty, config, cache, depthMultiplierCache, ceiling) {
|
|
1293
|
-
if (!isRecord$1(node)) return 0;
|
|
1294
|
-
const types = getNodeArrayValue(node, "types") ?? [];
|
|
1295
|
-
return addScore(addChildComplexities(0, types, depth, config, cache, depthMultiplierCache, ceiling), typePenalty(types.length), config, ceiling);
|
|
1296
|
-
}
|
|
1297
|
-
function calculateMappedTypeComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1298
|
-
if (!isRecord$1(node)) return 5;
|
|
1299
|
-
return addChildComplexities(5, getDefinedNodes([getNodeValue(node, "constraint"), getNodeValue(node, "typeAnnotation")]), depth, config, cache, depthMultiplierCache, ceiling);
|
|
1300
|
-
}
|
|
1301
|
-
function calculateTupleTypeComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1302
|
-
if (!isRecord$1(node)) return 0;
|
|
1303
|
-
const elementTypes = getNodeArrayValue(node, "elementTypes") ?? [];
|
|
1304
|
-
return addScore(addChildComplexities(1, elementTypes.filter((element) => element.type !== "TSRestType" && element.type !== "TSOptionalType"), depth, config, cache, depthMultiplierCache, ceiling), 1.5 * elementTypes.length, config, ceiling);
|
|
1305
|
-
}
|
|
1306
|
-
function calculateTypeLiteralComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1307
|
-
if (!isRecord$1(node)) return 0;
|
|
1308
|
-
const members = getNodeArrayValue(node, "members") ?? [];
|
|
1309
|
-
return addNestedTypeAnnotationScores(2 + members.length * .5, members, depth, config, cache, depthMultiplierCache, ceiling);
|
|
1310
|
-
}
|
|
1311
|
-
function calculateTypeReferenceComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1312
|
-
if (!isRecord$1(node)) return 2;
|
|
1313
|
-
const typeArguments = getNodeValue(node, "typeArguments");
|
|
1314
|
-
const parameters = typeArguments !== void 0 && isRecord$1(typeArguments) ? getNodeArrayValue(typeArguments, "params") ?? [] : [];
|
|
1315
|
-
let score = 2;
|
|
1316
|
-
for (const parameter of parameters) score = addScore(score, calculateChildComplexity(parameter, depth, config, cache, depthMultiplierCache, ceiling) + 2, config, ceiling);
|
|
1317
|
-
return score;
|
|
1318
|
-
}
|
|
1319
1252
|
function calculateStructuralComplexity(node, depth, config, cache, depthMultiplierCache, ceiling) {
|
|
1320
1253
|
const cached = cache.nodeCache.get(node);
|
|
1321
1254
|
if (cached !== void 0) return cached;
|
|
@@ -1328,7 +1261,14 @@ function calculateStructuralComplexity(node, depth, config, cache, depthMultipli
|
|
|
1328
1261
|
case "TSNeverKeyword":
|
|
1329
1262
|
case "TSUnknownKeyword": break;
|
|
1330
1263
|
case "TSArrayType":
|
|
1331
|
-
|
|
1264
|
+
if (isRecord$1(node)) {
|
|
1265
|
+
const elementType = getNodeValue(node, "elementType");
|
|
1266
|
+
if (elementType !== void 0) {
|
|
1267
|
+
score = addScore(calculateStructuralComplexity(elementType, nextDepth, config, cache, depthMultiplierCache, ceiling), 1, config, ceiling);
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
score = 1;
|
|
1332
1272
|
break;
|
|
1333
1273
|
case "TSBigIntKeyword":
|
|
1334
1274
|
case "TSBooleanKeyword":
|
|
@@ -1341,32 +1281,91 @@ function calculateStructuralComplexity(node, depth, config, cache, depthMultipli
|
|
|
1341
1281
|
score = 1;
|
|
1342
1282
|
break;
|
|
1343
1283
|
case "TSConditionalType":
|
|
1344
|
-
score =
|
|
1284
|
+
score = 3;
|
|
1285
|
+
if (isRecord$1(node)) {
|
|
1286
|
+
const checkType = getNodeValue(node, "checkType");
|
|
1287
|
+
const extendsType = getNodeValue(node, "extendsType");
|
|
1288
|
+
const trueType = getNodeValue(node, "trueType");
|
|
1289
|
+
const falseType = getNodeValue(node, "falseType");
|
|
1290
|
+
if (checkType !== void 0) score = addScore(score, calculateStructuralComplexity(checkType, nextDepth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1291
|
+
if (extendsType !== void 0) score = addScore(score, calculateStructuralComplexity(extendsType, nextDepth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1292
|
+
if (trueType !== void 0) score = addScore(score, calculateStructuralComplexity(trueType, nextDepth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1293
|
+
if (falseType !== void 0) score = addScore(score, calculateStructuralComplexity(falseType, nextDepth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1294
|
+
}
|
|
1345
1295
|
break;
|
|
1346
1296
|
case "TSFunctionType":
|
|
1347
1297
|
case "TSMethodSignature":
|
|
1348
|
-
score =
|
|
1298
|
+
score = 2;
|
|
1299
|
+
if (isRecord$1(node)) {
|
|
1300
|
+
const parameters = getNodeArrayValue(node, "params") ?? [];
|
|
1301
|
+
for (const parameter of parameters) {
|
|
1302
|
+
const typeAnnotation = getNestedTypeAnnotation(parameter);
|
|
1303
|
+
if (typeAnnotation === void 0) continue;
|
|
1304
|
+
score = addScore(score, calculateStructuralComplexity(typeAnnotation, nextDepth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1305
|
+
}
|
|
1306
|
+
const returnType = getReturnTypeAnnotation$1(node);
|
|
1307
|
+
if (returnType !== void 0) score = addScore(score, calculateStructuralComplexity(returnType, nextDepth, config, cache, depthMultiplierCache, ceiling), config, ceiling);
|
|
1308
|
+
}
|
|
1349
1309
|
break;
|
|
1350
1310
|
case "TSInterfaceDeclaration":
|
|
1351
|
-
score =
|
|
1311
|
+
score = config.interfacePenalty;
|
|
1312
|
+
if (isRecord$1(node)) {
|
|
1313
|
+
const extendsLength = getNodeArrayValue(node, "extends")?.length;
|
|
1314
|
+
if (extendsLength !== void 0 && extendsLength > 0) score = addScore(score, extendsLength * 5, config, ceiling);
|
|
1315
|
+
const body = getNodeValue(node, "body");
|
|
1316
|
+
const members = body !== void 0 && isRecord$1(body) ? getNodeArrayValue(body, "body") ?? [] : [];
|
|
1317
|
+
score = addScore(score, members.length * 2, config, ceiling);
|
|
1318
|
+
score = addNestedTypeAnnotationScores(score, members, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1319
|
+
}
|
|
1352
1320
|
break;
|
|
1353
1321
|
case "TSIntersectionType":
|
|
1354
|
-
|
|
1322
|
+
if (isRecord$1(node)) {
|
|
1323
|
+
const types = getNodeArrayValue(node, "types") ?? [];
|
|
1324
|
+
for (const type of types) score = addStructuralScore(score, type, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1325
|
+
score = addScore(score, 3 * types.length, config, ceiling);
|
|
1326
|
+
}
|
|
1355
1327
|
break;
|
|
1356
1328
|
case "TSMappedType":
|
|
1357
|
-
score =
|
|
1329
|
+
score = 5;
|
|
1330
|
+
if (isRecord$1(node)) {
|
|
1331
|
+
const constraint = getNodeValue(node, "constraint");
|
|
1332
|
+
if (constraint !== void 0) score = addStructuralScore(score, constraint, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1333
|
+
const typeAnnotation = getNodeValue(node, "typeAnnotation");
|
|
1334
|
+
if (typeAnnotation !== void 0) score = addStructuralScore(score, typeAnnotation, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1335
|
+
}
|
|
1358
1336
|
break;
|
|
1359
1337
|
case "TSTupleType":
|
|
1360
|
-
|
|
1338
|
+
if (isRecord$1(node)) {
|
|
1339
|
+
const elementTypes = getNodeArrayValue(node, "elementTypes") ?? [];
|
|
1340
|
+
score = 1;
|
|
1341
|
+
for (const element of elementTypes) {
|
|
1342
|
+
if (element.type === "TSRestType" || element.type === "TSOptionalType") continue;
|
|
1343
|
+
score = addStructuralScore(score, element, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1344
|
+
}
|
|
1345
|
+
score = addScore(score, 1.5 * elementTypes.length, config, ceiling);
|
|
1346
|
+
}
|
|
1361
1347
|
break;
|
|
1362
1348
|
case "TSTypeLiteral":
|
|
1363
|
-
|
|
1349
|
+
if (isRecord$1(node)) {
|
|
1350
|
+
const members = getNodeArrayValue(node, "members") ?? [];
|
|
1351
|
+
score = 2 + members.length * .5;
|
|
1352
|
+
score = addNestedTypeAnnotationScores(score, members, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1353
|
+
}
|
|
1364
1354
|
break;
|
|
1365
1355
|
case "TSTypeReference":
|
|
1366
|
-
score =
|
|
1356
|
+
score = 2;
|
|
1357
|
+
if (isRecord$1(node)) {
|
|
1358
|
+
const typeArguments = getNodeValue(node, "typeArguments");
|
|
1359
|
+
const parameters = typeArguments !== void 0 && isRecord$1(typeArguments) ? getNodeArrayValue(typeArguments, "params") ?? [] : [];
|
|
1360
|
+
for (const parameter of parameters) score = addStructuralScore(score, parameter, nextDepth, config, cache, depthMultiplierCache, ceiling, 2);
|
|
1361
|
+
}
|
|
1367
1362
|
break;
|
|
1368
1363
|
case "TSUnionType":
|
|
1369
|
-
|
|
1364
|
+
if (isRecord$1(node)) {
|
|
1365
|
+
const types = getNodeArrayValue(node, "types") ?? [];
|
|
1366
|
+
for (const type of types) score = addStructuralScore(score, type, nextDepth, config, cache, depthMultiplierCache, ceiling);
|
|
1367
|
+
score = addScore(score, 2 * (types.length - 1), config, ceiling);
|
|
1368
|
+
}
|
|
1370
1369
|
break;
|
|
1371
1370
|
default: score = 1;
|
|
1372
1371
|
}
|
|
@@ -20281,7 +20280,7 @@ function getExpectContext(currentParent, root) {
|
|
|
20281
20280
|
};
|
|
20282
20281
|
const ownContext = {
|
|
20283
20282
|
hasCallback: isFunction(currentParent),
|
|
20284
|
-
hasIndeterminate: isFunction(currentParent) || isIndeterminateLoopNode(currentParent) || currentParent.type === "ConditionalExpression" || currentParent.type === "IfStatement" || currentParent.type === "SwitchCase" || currentParent.type === "TryStatement",
|
|
20283
|
+
hasIndeterminate: isFunction(currentParent) || isIndeterminateLoopNode(currentParent) || currentParent.type === "ConditionalExpression" || currentParent.type === "IfStatement" || currentParent.type === "SwitchCase" || currentParent.type === "TryStatement" && currentParent.handler !== null,
|
|
20285
20284
|
hasLoop: isIndeterminateLoopNode(currentParent)
|
|
20286
20285
|
};
|
|
20287
20286
|
if (currentParent.parent === null) return ownContext;
|
|
@@ -20335,20 +20334,29 @@ function countExpectCalls(body, additionalAssertionFunctions = []) {
|
|
|
20335
20334
|
}
|
|
20336
20335
|
//#endregion
|
|
20337
20336
|
//#region src/rules/prefer-expect-assertions.ts
|
|
20337
|
+
function parseStringArray(value) {
|
|
20338
|
+
if (!Array.isArray(value)) return [];
|
|
20339
|
+
return value.filter((item) => typeof item === "string");
|
|
20340
|
+
}
|
|
20338
20341
|
function parseOptions$1(rawOptions) {
|
|
20339
20342
|
if (!isRecord$2(rawOptions)) return {
|
|
20343
|
+
additionalAssertionFunctions: [],
|
|
20340
20344
|
additionalExpectCallNames: [],
|
|
20341
20345
|
onlyFunctionsWithAsyncKeyword: false,
|
|
20342
20346
|
onlyFunctionsWithExpectInCallback: false,
|
|
20343
20347
|
onlyFunctionsWithExpectInLoop: false
|
|
20344
20348
|
};
|
|
20345
20349
|
return {
|
|
20346
|
-
|
|
20350
|
+
additionalAssertionFunctions: parseStringArray(rawOptions.additionalAssertionFunctions),
|
|
20351
|
+
additionalExpectCallNames: parseStringArray(rawOptions.additionalExpectCallNames),
|
|
20347
20352
|
onlyFunctionsWithAsyncKeyword: rawOptions.onlyFunctionsWithAsyncKeyword === true,
|
|
20348
20353
|
onlyFunctionsWithExpectInCallback: rawOptions.onlyFunctionsWithExpectInCallback === true,
|
|
20349
20354
|
onlyFunctionsWithExpectInLoop: rawOptions.onlyFunctionsWithExpectInLoop === true
|
|
20350
20355
|
};
|
|
20351
20356
|
}
|
|
20357
|
+
function getAdditionalExpectCallNames(options) {
|
|
20358
|
+
return [...new Set([...options.additionalExpectCallNames, ...options.additionalAssertionFunctions])];
|
|
20359
|
+
}
|
|
20352
20360
|
function hasEnabledFilter(options) {
|
|
20353
20361
|
return options.onlyFunctionsWithAsyncKeyword || options.onlyFunctionsWithExpectInCallback || options.onlyFunctionsWithExpectInLoop;
|
|
20354
20362
|
}
|
|
@@ -20449,11 +20457,22 @@ const preferExpectAssertions = defineRule({
|
|
|
20449
20457
|
if (callback === void 0) return;
|
|
20450
20458
|
const body = getCallbackBody(callback);
|
|
20451
20459
|
if (body === void 0) return;
|
|
20452
|
-
const { deterministic, indeterminate, hasIndeterminate, hasExpectInCallback, hasExpectInLoop } = countExpectCalls(body, options
|
|
20460
|
+
const { deterministic, indeterminate, hasIndeterminate, hasExpectInCallback, hasExpectInLoop } = countExpectCalls(body, getAdditionalExpectCallNames(options));
|
|
20453
20461
|
if (!shouldCheckTest(callback, options, deterministic, indeterminate, hasExpectInCallback, hasExpectInLoop)) return;
|
|
20454
20462
|
const assertionCall = getFirstStatementCall(callback);
|
|
20455
20463
|
if (assertionCall !== void 0 && (isExpectAssertionsCall(assertionCall) || isExpectHasAssertionsCall(assertionCall))) {
|
|
20456
20464
|
validateAssertionCall(context, assertionCall, deterministic, hasIndeterminate);
|
|
20465
|
+
if (isExpectHasAssertionsCall(assertionCall) && assertionCall.arguments.length === 0 && deterministic > 0 && !hasIndeterminate) {
|
|
20466
|
+
const [firstStatement] = getCallbackBlockBody(callback)?.body ?? [];
|
|
20467
|
+
if (firstStatement !== void 0) context.report({
|
|
20468
|
+
data: { count: String(deterministic) },
|
|
20469
|
+
fix(fixer) {
|
|
20470
|
+
return fixer.replaceText(firstStatement, `expect.assertions(${deterministic});`);
|
|
20471
|
+
},
|
|
20472
|
+
messageId: "preferAssertionsCount",
|
|
20473
|
+
node: assertionCall
|
|
20474
|
+
});
|
|
20475
|
+
}
|
|
20457
20476
|
return;
|
|
20458
20477
|
}
|
|
20459
20478
|
reportMissingAssertions(context, callback, node, deterministic, hasIndeterminate);
|
|
@@ -20461,15 +20480,17 @@ const preferExpectAssertions = defineRule({
|
|
|
20461
20480
|
},
|
|
20462
20481
|
meta: {
|
|
20463
20482
|
docs: {
|
|
20464
|
-
description: "Enforce expect assertion guards in Jest tests.",
|
|
20483
|
+
description: "Enforce expect assertion guards in Jest tests and prefer expect.assertions(n) over expect.hasAssertions() when the count is known.",
|
|
20465
20484
|
recommended: true
|
|
20466
20485
|
},
|
|
20486
|
+
fixable: "code",
|
|
20467
20487
|
hasSuggestions: true,
|
|
20468
20488
|
messages: {
|
|
20469
20489
|
assertionsRequiresNumberArgument: "This argument should be a number",
|
|
20470
20490
|
assertionsRequiresOneArgument: "`expect.assertions` expects a single argument of type number",
|
|
20471
20491
|
hasAssertionsTakesNoArguments: "`expect.hasAssertions` expects no arguments",
|
|
20472
20492
|
haveExpectAssertions: "Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression",
|
|
20493
|
+
preferAssertionsCount: "Use `expect.assertions({{count}})` instead of `expect.hasAssertions()` when the count is known",
|
|
20473
20494
|
suggestAddingAssertions: "Add `expect.assertions({{count}})`",
|
|
20474
20495
|
suggestAddingHasAssertions: "Add `expect.hasAssertions()`",
|
|
20475
20496
|
wrongAssertionCount: "Expected {{expected}} assertions, but test has {{actual}} expect calls"
|
|
@@ -20477,6 +20498,10 @@ const preferExpectAssertions = defineRule({
|
|
|
20477
20498
|
schema: [{
|
|
20478
20499
|
additionalProperties: false,
|
|
20479
20500
|
properties: {
|
|
20501
|
+
additionalAssertionFunctions: {
|
|
20502
|
+
items: { type: "string" },
|
|
20503
|
+
type: "array"
|
|
20504
|
+
},
|
|
20480
20505
|
additionalExpectCallNames: {
|
|
20481
20506
|
items: { type: "string" },
|
|
20482
20507
|
type: "array"
|
|
@@ -20491,46 +20516,6 @@ const preferExpectAssertions = defineRule({
|
|
|
20491
20516
|
}
|
|
20492
20517
|
});
|
|
20493
20518
|
//#endregion
|
|
20494
|
-
//#region src/rules/prefer-expect-assertions-count.ts
|
|
20495
|
-
const preferExpectAssertionsCount = defineRule({
|
|
20496
|
-
create(context) {
|
|
20497
|
-
const additionalAssertionFunctions = context.options[0]?.additionalAssertionFunctions ?? [];
|
|
20498
|
-
return { CallExpression(node) {
|
|
20499
|
-
if (!isTestCaseCall(node)) return;
|
|
20500
|
-
const callback = getTestCallback(node);
|
|
20501
|
-
if (callback?.body?.type !== "BlockStatement") return;
|
|
20502
|
-
const [firstStatement] = callback.body.body;
|
|
20503
|
-
if (firstStatement?.type !== "ExpressionStatement" || firstStatement.expression.type !== "CallExpression" || !isExpectHasAssertionsCall(firstStatement.expression)) return;
|
|
20504
|
-
const { deterministic, indeterminate, hasIndeterminate } = countExpectCalls(callback.body, additionalAssertionFunctions);
|
|
20505
|
-
if (hasIndeterminate || indeterminate > 0 || deterministic === 0) return;
|
|
20506
|
-
context.report({
|
|
20507
|
-
data: { count: String(deterministic) },
|
|
20508
|
-
fix(fixer) {
|
|
20509
|
-
return fixer.replaceText(firstStatement, `expect.assertions(${deterministic});`);
|
|
20510
|
-
},
|
|
20511
|
-
messageId: "preferAssertionsCount",
|
|
20512
|
-
node: firstStatement.expression
|
|
20513
|
-
});
|
|
20514
|
-
} };
|
|
20515
|
-
},
|
|
20516
|
-
meta: {
|
|
20517
|
-
docs: {
|
|
20518
|
-
description: "Prefer expect.assertions(n) over expect.hasAssertions() when the assertion count is known.",
|
|
20519
|
-
recommended: true
|
|
20520
|
-
},
|
|
20521
|
-
fixable: "code",
|
|
20522
|
-
messages: { preferAssertionsCount: "Use `expect.assertions({{count}})` instead of `expect.hasAssertions()` when the count is known" },
|
|
20523
|
-
schema: [{
|
|
20524
|
-
additionalProperties: false,
|
|
20525
|
-
properties: { additionalAssertionFunctions: {
|
|
20526
|
-
items: { type: "string" },
|
|
20527
|
-
type: "array"
|
|
20528
|
-
} },
|
|
20529
|
-
type: "object"
|
|
20530
|
-
}]
|
|
20531
|
-
}
|
|
20532
|
-
});
|
|
20533
|
-
//#endregion
|
|
20534
20519
|
//#region src/rules/prefer-hoisted-jsx-elements.ts
|
|
20535
20520
|
function normalizeAdditionalHoistableComponents(rawOptions) {
|
|
20536
20521
|
if (!(isRecord$2(rawOptions) && "additionalHoistableComponents" in rawOptions)) return /* @__PURE__ */ new Set();
|
|
@@ -26043,7 +26028,6 @@ const smallRules = definePlugin({
|
|
|
26043
26028
|
"prefer-context-stack": preferContextStack,
|
|
26044
26029
|
"prefer-early-return": preferEarlyReturn,
|
|
26045
26030
|
"prefer-expect-assertions": preferExpectAssertions,
|
|
26046
|
-
"prefer-expect-assertions-count": preferExpectAssertionsCount,
|
|
26047
26031
|
"prefer-hoisted-jsx-elements": preferHoistedJsxElements,
|
|
26048
26032
|
"prefer-hoisted-jsx-object-properties": preferHoistedJsxObjectProperties,
|
|
26049
26033
|
"prefer-idiv": preferIdiv,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pobammer-ts/small-rules",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Various Oxlint-native rules for linting roblox-ts projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lint",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"jiti": "2.7.0",
|
|
107
107
|
"jscpd": "5.0.7",
|
|
108
108
|
"knip": "6.16.1",
|
|
109
|
-
"oxfmt": "0.
|
|
109
|
+
"oxfmt": "0.55.0",
|
|
110
110
|
"oxlint": "1.69.0",
|
|
111
111
|
"oxlint-tsgolint": "0.23.0",
|
|
112
112
|
"package-manager-detector": "1.6.0",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"vitiate": "0.3.0"
|
|
119
119
|
},
|
|
120
120
|
"peerDependencies": {
|
|
121
|
-
"typescript": "
|
|
121
|
+
"typescript": ">=5 <8"
|
|
122
122
|
},
|
|
123
123
|
"engines": {
|
|
124
124
|
"node": "^20.19.0 || >=22.12.0"
|