eslint-plugin-rxjs-x 1.0.2 → 1.0.3
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 +2 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +416 -426
- package/package.json +28 -24
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x/blob/master/LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/eslint-plugin-rxjs-x)
|
|
5
5
|
[](https://scorecard.dev/viewer/?uri=github.com/JasonWeinzierl/eslint-plugin-rxjs-x)
|
|
6
|
+
[](https://www.bestpractices.dev/projects/12936)
|
|
6
7
|
|
|
7
8
|
> [!NOTE]
|
|
8
9
|
> Forked from [`eslint-plugin-rxjs`](https://github.com/cartant/eslint-plugin-rxjs)
|
|
@@ -49,7 +50,7 @@ Additionally, consider if the `rxjsX.configs.strict` shared config is right for
|
|
|
49
50
|
2. The namespace of this plugin is `rxjs-x` instead of `rxjs`.
|
|
50
51
|
- e.g. if your ESLint config had `"rxjs/no-subject-value": "error"`, replace it with `"rxjs-x/no-subject-value": "error"`.
|
|
51
52
|
- e.g. if your project had inline comments like `// eslint-disable-next-line rxjs/no-async-subscribe`, replace them with `// eslint-disable-next-line rxjs-x/no-async-subscribe`.
|
|
52
|
-
3. `rxjs/no-ignored-observable` is replaced with `rxjs-x/no-floating-
|
|
53
|
+
3. `rxjs/no-ignored-observable` is replaced with `rxjs-x/no-floating-observables`.
|
|
53
54
|
|
|
54
55
|
> [!TIP]
|
|
55
56
|
> A complete description of all changes from `eslint-plugin-rxjs` are documented in the [CHANGELOG](CHANGELOG.md) file.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { TSESLint } from "@typescript-eslint/utils";
|
|
2
2
|
import { ESLint, Rule } from "eslint";
|
|
3
|
-
|
|
4
3
|
//#region src/index.d.ts
|
|
5
4
|
declare const allRules: {
|
|
6
5
|
'ban-observables': TSESLint.RuleModule<"forbidden", readonly [Record<string, string | boolean>], {
|
|
@@ -347,8 +346,9 @@ declare const rxjsX: {
|
|
|
347
346
|
name: string;
|
|
348
347
|
version: string;
|
|
349
348
|
namespace: string;
|
|
350
|
-
};
|
|
351
|
-
|
|
349
|
+
};
|
|
350
|
+
/** Compatibility with `defineConfig` until https://github.com/typescript-eslint/typescript-eslint/issues/11543 is addressed. */
|
|
351
|
+
rules: { [K in keyof typeof allRules]: (typeof allRules)[K] & Rule.RuleModule; };
|
|
352
352
|
};
|
|
353
353
|
//#endregion
|
|
354
354
|
export { rxjsX as default };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getFunctionHeadLocation, isFunction } from "@typescript-eslint/utils/as
|
|
|
5
5
|
import decamelize from "decamelize";
|
|
6
6
|
//#region package.json
|
|
7
7
|
var name = "eslint-plugin-rxjs-x";
|
|
8
|
-
var version = "1.0.
|
|
8
|
+
var version = "1.0.3";
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/configs/recommended.ts
|
|
11
11
|
const createRecommendedConfig = (plugin) => ({
|
|
@@ -564,15 +564,13 @@ const finnishRule = ruleCreator({
|
|
|
564
564
|
checkNode(node);
|
|
565
565
|
},
|
|
566
566
|
"ArrowFunctionExpression > Identifier": (node) => {
|
|
567
|
-
if (config.parameters)
|
|
568
|
-
|
|
569
|
-
}
|
|
567
|
+
if (!config.parameters) return;
|
|
568
|
+
if (node !== node.parent.body) checkNode(node);
|
|
570
569
|
},
|
|
571
570
|
"PropertyDefinition[computed=false]": (node) => {
|
|
572
|
-
if (config.properties)
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
571
|
+
if (!config.properties) return;
|
|
572
|
+
if (node.override) return;
|
|
573
|
+
checkNode(node.key);
|
|
576
574
|
},
|
|
577
575
|
"FunctionDeclaration > Identifier": (node) => {
|
|
578
576
|
const parent = node.parent;
|
|
@@ -587,22 +585,19 @@ const finnishRule = ruleCreator({
|
|
|
587
585
|
} else if (config.parameters) checkNode(node);
|
|
588
586
|
},
|
|
589
587
|
"MethodDefinition[kind='get'][computed=false]": (node) => {
|
|
590
|
-
if (config.properties)
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
588
|
+
if (!config.properties) return;
|
|
589
|
+
if (node.override) return;
|
|
590
|
+
checkNode(node.key, node);
|
|
594
591
|
},
|
|
595
592
|
"MethodDefinition[kind='method'][computed=false]": (node) => {
|
|
596
|
-
if (config.methods)
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
593
|
+
if (!config.methods) return;
|
|
594
|
+
if (node.override) return;
|
|
595
|
+
checkNode(node.key, node);
|
|
600
596
|
},
|
|
601
597
|
"MethodDefinition[kind='set'][computed=false]": (node) => {
|
|
602
|
-
if (config.properties)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
598
|
+
if (!config.properties) return;
|
|
599
|
+
if (node.override) return;
|
|
600
|
+
checkNode(node.key, node);
|
|
606
601
|
},
|
|
607
602
|
"TSAbstractMethodDefinition[computed=false]": (node) => {
|
|
608
603
|
if (node.override) return;
|
|
@@ -747,7 +742,7 @@ const noConnectableRule = ruleCreator({
|
|
|
747
742
|
});
|
|
748
743
|
},
|
|
749
744
|
"CallExpression[callee.name=/^(publish|publishBehavior|publishLast|publishReplay)$/]": (node) => {
|
|
750
|
-
if (
|
|
745
|
+
if (node.arguments.every((arg) => !couldBeFunction(arg))) context.report({
|
|
751
746
|
messageId: "forbidden",
|
|
752
747
|
node: node.callee
|
|
753
748
|
});
|
|
@@ -1001,40 +996,37 @@ const noExposedSubjectsRule = ruleCreator({
|
|
|
1001
996
|
}
|
|
1002
997
|
return {
|
|
1003
998
|
[`PropertyDefinition[accessibility!=${accessibilityRexExp}]`]: (node) => {
|
|
1004
|
-
if (isSubject(node))
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
}
|
|
999
|
+
if (!isSubject(node)) return;
|
|
1000
|
+
const { key } = node;
|
|
1001
|
+
if (isIdentifier(key)) context.report({
|
|
1002
|
+
messageId,
|
|
1003
|
+
node: key,
|
|
1004
|
+
data: { subject: key.name }
|
|
1005
|
+
});
|
|
1012
1006
|
},
|
|
1013
1007
|
[`MethodDefinition[kind='constructor'] > FunctionExpression > TSParameterProperty[accessibility!=${accessibilityRexExp}] > Identifier`]: (node) => {
|
|
1014
|
-
if (isSubject(node))
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1008
|
+
if (!isSubject(node)) return;
|
|
1009
|
+
const { loc } = node;
|
|
1010
|
+
context.report({
|
|
1011
|
+
messageId,
|
|
1012
|
+
loc: {
|
|
1013
|
+
...loc,
|
|
1014
|
+
end: {
|
|
1015
|
+
...loc.start,
|
|
1016
|
+
column: loc.start.column + node.name.length
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
data: { subject: node.name }
|
|
1020
|
+
});
|
|
1028
1021
|
},
|
|
1029
1022
|
[`MethodDefinition[accessibility!=${accessibilityRexExp}][kind=/^(get|set)$/]`]: (node) => {
|
|
1030
|
-
if (isSubject(node))
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1023
|
+
if (!isSubject(node)) return;
|
|
1024
|
+
const key = node.key;
|
|
1025
|
+
context.report({
|
|
1026
|
+
messageId,
|
|
1027
|
+
node: key,
|
|
1028
|
+
data: { subject: key.name }
|
|
1029
|
+
});
|
|
1038
1030
|
},
|
|
1039
1031
|
[`MethodDefinition[accessibility!=${accessibilityRexExp}][kind='method']`]: (node) => {
|
|
1040
1032
|
const functionExpression = node.value;
|
|
@@ -1068,13 +1060,12 @@ const noFinnishRule = ruleCreator({
|
|
|
1068
1060
|
const { esTreeNodeToTSNodeMap } = ESLintUtils.getParserServices(context);
|
|
1069
1061
|
const { couldBeObservable, couldReturnObservable } = getTypeServices(context);
|
|
1070
1062
|
function checkNode(nameNode, typeNode) {
|
|
1071
|
-
if (couldBeObservable(typeNode ?? nameNode)
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1063
|
+
if (!couldBeObservable(typeNode ?? nameNode) && !couldReturnObservable(typeNode ?? nameNode)) return;
|
|
1064
|
+
const tsNode = esTreeNodeToTSNodeMap.get(nameNode);
|
|
1065
|
+
if (/\$+$/.test(tsNode.getText())) context.report({
|
|
1066
|
+
loc: getLoc(tsNode),
|
|
1067
|
+
messageId: "forbidden"
|
|
1068
|
+
});
|
|
1078
1069
|
}
|
|
1079
1070
|
return {
|
|
1080
1071
|
"ArrayPattern > Identifier[name=/[$]+$/]": (node) => {
|
|
@@ -1208,8 +1199,11 @@ const noIgnoredDefaultValueRule = ruleCreator({
|
|
|
1208
1199
|
create: (context) => {
|
|
1209
1200
|
const { getTypeAtLocation } = ESLintUtils.getParserServices(context);
|
|
1210
1201
|
const { couldBeObservable, couldBeType } = getTypeServices(context);
|
|
1202
|
+
function isDefaultValueProperty(property) {
|
|
1203
|
+
return isProperty(property) && isIdentifier(property.key) && property.key.name === "defaultValue";
|
|
1204
|
+
}
|
|
1211
1205
|
function checkConfigObj(configArg) {
|
|
1212
|
-
if (
|
|
1206
|
+
if (configArg.properties.every((property) => !isDefaultValueProperty(property))) context.report({
|
|
1213
1207
|
messageId: "forbidden",
|
|
1214
1208
|
node: configArg
|
|
1215
1209
|
});
|
|
@@ -1296,8 +1290,11 @@ const noIgnoredErrorRule = ruleCreator({
|
|
|
1296
1290
|
if (callExpression.arguments.length >= 2) return false;
|
|
1297
1291
|
return couldBeFunction(callExpression.arguments[0]);
|
|
1298
1292
|
}
|
|
1293
|
+
function isErrorProperty(property) {
|
|
1294
|
+
return isProperty(property) && isIdentifier(property.key) && property.key.name === "error";
|
|
1295
|
+
}
|
|
1299
1296
|
function isObjMissingError(arg) {
|
|
1300
|
-
return
|
|
1297
|
+
return arg.properties.every((property) => !isErrorProperty(property));
|
|
1301
1298
|
}
|
|
1302
1299
|
function isTypeMissingError(arg) {
|
|
1303
1300
|
return !getTypeAtLocation(arg)?.getProperties().some((p) => p.name === "error");
|
|
@@ -1343,20 +1340,19 @@ const noIgnoredNotifierRule = ruleCreator({
|
|
|
1343
1340
|
}
|
|
1344
1341
|
return {
|
|
1345
1342
|
"CallExpression[callee.name=/^(repeatWhen|retryWhen)$/]": (node) => {
|
|
1346
|
-
if (couldBeMonoTypeOperatorFunction(node))
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
}
|
|
1343
|
+
if (!couldBeMonoTypeOperatorFunction(node)) return;
|
|
1344
|
+
const [arg] = node.arguments;
|
|
1345
|
+
if (isArrowFunctionExpression(arg) || isFunctionExpression(arg)) {
|
|
1346
|
+
const [param] = arg.params;
|
|
1347
|
+
if (param) entries.push({
|
|
1348
|
+
node,
|
|
1349
|
+
param,
|
|
1350
|
+
sightings: 0
|
|
1351
|
+
});
|
|
1352
|
+
else context.report({
|
|
1353
|
+
messageId: "forbidden",
|
|
1354
|
+
node: node.callee
|
|
1355
|
+
});
|
|
1360
1356
|
}
|
|
1361
1357
|
},
|
|
1362
1358
|
"CallExpression[callee.name=/^(repeatWhen|retryWhen)$/]:exit": (node) => {
|
|
@@ -1392,8 +1388,11 @@ const noIgnoredReplayBufferRule = ruleCreator({
|
|
|
1392
1388
|
},
|
|
1393
1389
|
name: "no-ignored-replay-buffer",
|
|
1394
1390
|
create: (context) => {
|
|
1391
|
+
function isBufferSizeProperty(property) {
|
|
1392
|
+
return isProperty(property) && isIdentifier(property.key) && property.key.name === "bufferSize";
|
|
1393
|
+
}
|
|
1395
1394
|
function checkShareReplayConfig(node, shareReplayConfigArg) {
|
|
1396
|
-
if (
|
|
1395
|
+
if (shareReplayConfigArg.properties.every((p) => !isBufferSizeProperty(p))) context.report({
|
|
1397
1396
|
messageId: "forbidden",
|
|
1398
1397
|
node
|
|
1399
1398
|
});
|
|
@@ -1505,15 +1504,14 @@ The \`postCompleters\` property is an array containing the names of the operator
|
|
|
1505
1504
|
return false;
|
|
1506
1505
|
}
|
|
1507
1506
|
return { "ExpressionStatement > CallExpression > MemberExpression[property.name='subscribe']": (node) => {
|
|
1508
|
-
if (couldBeObservable(node.object))
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1507
|
+
if (!couldBeObservable(node.object)) return;
|
|
1508
|
+
const callExpression = node.parent;
|
|
1509
|
+
if (callExpression.arguments.length === 1 && couldBeType(callExpression.arguments[0], "Subscriber")) return;
|
|
1510
|
+
if (hasAllowedOperator(node.object)) return;
|
|
1511
|
+
context.report({
|
|
1512
|
+
messageId: "forbidden",
|
|
1513
|
+
node: node.property
|
|
1514
|
+
});
|
|
1517
1515
|
} };
|
|
1518
1516
|
}
|
|
1519
1517
|
});
|
|
@@ -1622,62 +1620,61 @@ const noImplicitAnyCatchRule = ruleCreator({
|
|
|
1622
1620
|
};
|
|
1623
1621
|
}
|
|
1624
1622
|
function checkCallback(callback) {
|
|
1625
|
-
if (isArrowFunctionExpression(callback)
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
context.report({
|
|
1634
|
-
messageId: "explicitAny",
|
|
1635
|
-
node: param,
|
|
1636
|
-
suggest: [{
|
|
1637
|
-
messageId: "suggestExplicitUnknown",
|
|
1638
|
-
fix: createReplacerFix(typeAnnotation, "unknown")
|
|
1639
|
-
}]
|
|
1640
|
-
});
|
|
1641
|
-
} else if (type !== AST_NODE_TYPES.TSUnknownKeyword) {
|
|
1642
|
-
let isAllowedError = false;
|
|
1643
|
-
if (allowExplicitError && type === AST_NODE_TYPES.TSTypeReference) {
|
|
1644
|
-
const typeRef = typeAnnotation.typeAnnotation;
|
|
1645
|
-
if (isIdentifier(typeRef.typeName)) isAllowedError = typeRef.typeName.name === "Error";
|
|
1646
|
-
}
|
|
1647
|
-
if (!isAllowedError) context.report({
|
|
1648
|
-
messageId: allowExplicitError ? "narrowedAllowError" : "narrowed",
|
|
1649
|
-
node: param,
|
|
1650
|
-
suggest: [
|
|
1651
|
-
{
|
|
1652
|
-
messageId: "suggestExplicitUnknown",
|
|
1653
|
-
fix: createReplacerFix(typeAnnotation, "unknown")
|
|
1654
|
-
},
|
|
1655
|
-
allowExplicitAny ? {
|
|
1656
|
-
messageId: "suggestExplicitAny",
|
|
1657
|
-
fix: createReplacerFix(typeAnnotation, "any")
|
|
1658
|
-
} : null,
|
|
1659
|
-
allowExplicitError && !isAllowedError ? {
|
|
1660
|
-
messageId: "suggestExplicitError",
|
|
1661
|
-
fix: createReplacerFix(typeAnnotation, "Error")
|
|
1662
|
-
} : null
|
|
1663
|
-
].filter((x) => !!x)
|
|
1664
|
-
});
|
|
1665
|
-
}
|
|
1666
|
-
} else {
|
|
1667
|
-
const hasRestParams = restParams.length > 0;
|
|
1623
|
+
if (!isArrowFunctionExpression(callback) && !isFunctionExpression(callback)) return;
|
|
1624
|
+
const [param, ...restParams] = callback.params;
|
|
1625
|
+
if (!param) return;
|
|
1626
|
+
if (hasTypeAnnotation(param)) {
|
|
1627
|
+
const { typeAnnotation } = param;
|
|
1628
|
+
const { typeAnnotation: { type } } = typeAnnotation;
|
|
1629
|
+
if (type === AST_NODE_TYPES.TSAnyKeyword) {
|
|
1630
|
+
if (allowExplicitAny) return;
|
|
1668
1631
|
context.report({
|
|
1669
|
-
|
|
1670
|
-
messageId: "implicitAny",
|
|
1632
|
+
messageId: "explicitAny",
|
|
1671
1633
|
node: param,
|
|
1672
1634
|
suggest: [{
|
|
1673
1635
|
messageId: "suggestExplicitUnknown",
|
|
1674
|
-
fix:
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1636
|
+
fix: createReplacerFix(typeAnnotation, "unknown")
|
|
1637
|
+
}]
|
|
1638
|
+
});
|
|
1639
|
+
} else if (type !== AST_NODE_TYPES.TSUnknownKeyword) {
|
|
1640
|
+
let isAllowedError = false;
|
|
1641
|
+
if (allowExplicitError && type === AST_NODE_TYPES.TSTypeReference) {
|
|
1642
|
+
const typeRef = typeAnnotation.typeAnnotation;
|
|
1643
|
+
if (isIdentifier(typeRef.typeName)) isAllowedError = typeRef.typeName.name === "Error";
|
|
1644
|
+
}
|
|
1645
|
+
if (!isAllowedError) context.report({
|
|
1646
|
+
messageId: allowExplicitError ? "narrowedAllowError" : "narrowed",
|
|
1647
|
+
node: param,
|
|
1648
|
+
suggest: [
|
|
1649
|
+
{
|
|
1650
|
+
messageId: "suggestExplicitUnknown",
|
|
1651
|
+
fix: createReplacerFix(typeAnnotation, "unknown")
|
|
1652
|
+
},
|
|
1653
|
+
allowExplicitAny ? {
|
|
1654
|
+
messageId: "suggestExplicitAny",
|
|
1655
|
+
fix: createReplacerFix(typeAnnotation, "any")
|
|
1656
|
+
} : null,
|
|
1657
|
+
allowExplicitError && !isAllowedError ? {
|
|
1658
|
+
messageId: "suggestExplicitError",
|
|
1659
|
+
fix: createReplacerFix(typeAnnotation, "Error")
|
|
1660
|
+
} : null
|
|
1661
|
+
].filter((x) => !!x)
|
|
1679
1662
|
});
|
|
1680
1663
|
}
|
|
1664
|
+
} else {
|
|
1665
|
+
const hasRestParams = restParams.length > 0;
|
|
1666
|
+
context.report({
|
|
1667
|
+
fix: allowExplicitAny ? createInserterFix(param, "any", { hasRestParams }) : void 0,
|
|
1668
|
+
messageId: "implicitAny",
|
|
1669
|
+
node: param,
|
|
1670
|
+
suggest: [{
|
|
1671
|
+
messageId: "suggestExplicitUnknown",
|
|
1672
|
+
fix: createInserterFix(param, "unknown", { hasRestParams })
|
|
1673
|
+
}, allowExplicitAny ? {
|
|
1674
|
+
messageId: "suggestExplicitAny",
|
|
1675
|
+
fix: createInserterFix(param, "any", { hasRestParams })
|
|
1676
|
+
} : null].filter((x) => !!x)
|
|
1677
|
+
});
|
|
1681
1678
|
}
|
|
1682
1679
|
}
|
|
1683
1680
|
return {
|
|
@@ -1899,7 +1896,8 @@ const noMisusedObservablesRule = ruleCreator({
|
|
|
1899
1896
|
});
|
|
1900
1897
|
} };
|
|
1901
1898
|
function checkArguments(node) {
|
|
1902
|
-
const
|
|
1899
|
+
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
1900
|
+
const voidArgs = voidFunctionArguments(checker, tsNode);
|
|
1903
1901
|
if (!voidArgs.size) return;
|
|
1904
1902
|
for (const [index, argument] of node.arguments.entries()) {
|
|
1905
1903
|
if (!voidArgs.has(index)) continue;
|
|
@@ -1919,7 +1917,8 @@ const noMisusedObservablesRule = ruleCreator({
|
|
|
1919
1917
|
});
|
|
1920
1918
|
}
|
|
1921
1919
|
function checkClassLikeOrInterfaceNode(node) {
|
|
1922
|
-
const
|
|
1920
|
+
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
1921
|
+
const heritageTypes = getHeritageTypes(checker, tsNode);
|
|
1923
1922
|
if (!heritageTypes?.length) return;
|
|
1924
1923
|
for (const element of node.body.body) {
|
|
1925
1924
|
const tsElement = esTreeNodeToTSNodeMap.get(element);
|
|
@@ -1940,7 +1939,8 @@ const noMisusedObservablesRule = ruleCreator({
|
|
|
1940
1939
|
}
|
|
1941
1940
|
}
|
|
1942
1941
|
function checkProperty(node) {
|
|
1943
|
-
const
|
|
1942
|
+
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
1943
|
+
const contextualType = getPropertyContextualType(checker, tsNode);
|
|
1944
1944
|
if (contextualType == null) return;
|
|
1945
1945
|
if (!isVoidReturningFunctionType(contextualType)) return;
|
|
1946
1946
|
if (!couldReturnObservable(node.value)) return;
|
|
@@ -2593,7 +2593,7 @@ const noUnnecessaryCollectionRule = ruleCreator({
|
|
|
2593
2593
|
const firstArg = args[0];
|
|
2594
2594
|
if (isArrayExpression(firstArg)) {
|
|
2595
2595
|
const nonNullElements = firstArg.elements.filter((element) => element !== null);
|
|
2596
|
-
if (nonNullElements.length === 1 && couldBeObservable(nonNullElements[0])) context.report({
|
|
2596
|
+
if (nonNullElements.length === 1 && !isSpreadElement(nonNullElements[0]) && couldBeObservable(nonNullElements[0])) context.report({
|
|
2597
2597
|
messageId: "forbidden",
|
|
2598
2598
|
node: node.callee,
|
|
2599
2599
|
data: {
|
|
@@ -2750,19 +2750,18 @@ const noUnsafeSubjectNext = ruleCreator({
|
|
|
2750
2750
|
const { getTypeAtLocation, program } = ESLintUtils.getParserServices(context);
|
|
2751
2751
|
const typeChecker = program.getTypeChecker();
|
|
2752
2752
|
return { [`CallExpression[callee.property.name='next']`]: (node) => {
|
|
2753
|
-
if (node.arguments.length
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
}
|
|
2753
|
+
if (node.arguments.length !== 0 || !isMemberExpression(node.callee)) return;
|
|
2754
|
+
const type = getTypeAtLocation(node.callee.object);
|
|
2755
|
+
if (tsutils.isTypeReference(type) && couldBeType(type, "Subject")) {
|
|
2756
|
+
const [typeArg] = typeChecker.getTypeArguments(type);
|
|
2757
|
+
if (tsutils.isTypeFlagSet(typeArg, ts.TypeFlags.Any)) return;
|
|
2758
|
+
if (tsutils.isTypeFlagSet(typeArg, ts.TypeFlags.Unknown)) return;
|
|
2759
|
+
if (tsutils.isTypeFlagSet(typeArg, ts.TypeFlags.Void)) return;
|
|
2760
|
+
if (tsutils.isUnionType(typeArg) && typeArg.types.some((t) => tsutils.isTypeFlagSet(t, ts.TypeFlags.Void))) return;
|
|
2761
|
+
context.report({
|
|
2762
|
+
messageId: "forbidden",
|
|
2763
|
+
node: node.callee.property
|
|
2764
|
+
});
|
|
2766
2765
|
}
|
|
2767
2766
|
} };
|
|
2768
2767
|
}
|
|
@@ -2841,7 +2840,7 @@ The \`observable\` property is used to identify the action observables from whic
|
|
|
2841
2840
|
if (isMemberExpression(arg) && isIdentifier(arg.property)) return arg.property.name;
|
|
2842
2841
|
return "";
|
|
2843
2842
|
}).map((name) => decamelize(name));
|
|
2844
|
-
if (allowRegExp) return
|
|
2843
|
+
if (allowRegExp) return names.some((name) => !allowRegExp?.test(name));
|
|
2845
2844
|
if (disallowRegExp) return names.some((name) => disallowRegExp?.test(name));
|
|
2846
2845
|
return false;
|
|
2847
2846
|
}
|
|
@@ -3007,7 +3006,7 @@ const RENAMED_OPERATORS = {
|
|
|
3007
3006
|
race: "raceWith",
|
|
3008
3007
|
zip: "zipWith"
|
|
3009
3008
|
};
|
|
3010
|
-
const DEPRECATED_OPERATORS = new Set(["partition"]);
|
|
3009
|
+
const DEPRECATED_OPERATORS = /* @__PURE__ */ new Set(["partition"]);
|
|
3011
3010
|
const preferRootOperatorsRule = ruleCreator({
|
|
3012
3011
|
meta: {
|
|
3013
3012
|
docs: {
|
|
@@ -3123,310 +3122,301 @@ const preferRootOperatorsRule = ruleCreator({
|
|
|
3123
3122
|
//#endregion
|
|
3124
3123
|
//#region src/rules/suffix-subjects.ts
|
|
3125
3124
|
const baseShouldHaveSuffix = "Subject identifiers must end with \"{{suffix}}\".";
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3125
|
+
//#endregion
|
|
3126
|
+
//#region src/index.ts
|
|
3127
|
+
const allRules = {
|
|
3128
|
+
"ban-observables": banObservablesRule,
|
|
3129
|
+
"ban-operators": banOperatorsRule,
|
|
3130
|
+
"finnish": finnishRule,
|
|
3131
|
+
"just": justRule,
|
|
3132
|
+
"no-async-subscribe": noAsyncSubscribeRule,
|
|
3133
|
+
"no-connectable": noConnectableRule,
|
|
3134
|
+
"no-create": noCreateRule,
|
|
3135
|
+
"no-cyclic-action": noCyclicActionRule,
|
|
3136
|
+
"no-explicit-generics": noExplicitGenericsRule,
|
|
3137
|
+
"no-exposed-subjects": noExposedSubjectsRule,
|
|
3138
|
+
"no-finnish": noFinnishRule,
|
|
3139
|
+
"no-floating-observables": noFloatingObservablesRule,
|
|
3140
|
+
"no-ignored-default-value": noIgnoredDefaultValueRule,
|
|
3141
|
+
"no-ignored-error": noIgnoredErrorRule,
|
|
3142
|
+
"no-ignored-notifier": noIgnoredNotifierRule,
|
|
3143
|
+
"no-ignored-replay-buffer": noIgnoredReplayBufferRule,
|
|
3144
|
+
"no-ignored-subscribe": noIgnoredSubscribeRule,
|
|
3145
|
+
"no-ignored-subscription": noIgnoredSubscriptionRule,
|
|
3146
|
+
"no-ignored-takewhile-value": noIgnoredTakewhileValueRule,
|
|
3147
|
+
"no-implicit-any-catch": noImplicitAnyCatchRule,
|
|
3148
|
+
"no-index": noIndexRule,
|
|
3149
|
+
"no-internal": noInternalRule,
|
|
3150
|
+
"no-misused-observables": noMisusedObservablesRule,
|
|
3151
|
+
"no-nested-subscribe": noNestedSubscribeRule,
|
|
3152
|
+
"no-redundant-notify": noRedundantNotifyRule,
|
|
3153
|
+
"no-sharereplay": noSharereplayRule,
|
|
3154
|
+
"no-sharereplay-before-takeuntil": noSharereplayBeforeTakeuntilRule,
|
|
3155
|
+
"no-subclass": noSubclassRule,
|
|
3156
|
+
"no-subject-unsubscribe": noSubjectUnsubscribeRule,
|
|
3157
|
+
"no-subject-value": noSubjectValueRule,
|
|
3158
|
+
"no-subscribe-handlers": noSubscribeHandlersRule,
|
|
3159
|
+
"no-subscribe-in-pipe": noSubscribeInPipeRule,
|
|
3160
|
+
"no-topromise": noTopromiseRule,
|
|
3161
|
+
"no-unbound-methods": noUnboundMethodsRule,
|
|
3162
|
+
"no-unnecessary-collection": noUnnecessaryCollectionRule,
|
|
3163
|
+
"no-unsafe-catch": noUnsafeCatchRule,
|
|
3164
|
+
"no-unsafe-first": noUnsafeFirstRule,
|
|
3165
|
+
"no-unsafe-subject-next": noUnsafeSubjectNext,
|
|
3166
|
+
"no-unsafe-switchmap": noUnsafeSwitchmapRule,
|
|
3167
|
+
"no-unsafe-takeuntil": noUnsafeTakeuntilRule,
|
|
3168
|
+
"prefer-observer": preferObserverRule,
|
|
3169
|
+
"prefer-root-operators": preferRootOperatorsRule,
|
|
3170
|
+
"suffix-subjects": ruleCreator({
|
|
3171
|
+
meta: {
|
|
3172
|
+
docs: {
|
|
3173
|
+
description: "Enforce the use of a suffix in subject identifiers.",
|
|
3174
|
+
requiresTypeChecking: true
|
|
3175
|
+
},
|
|
3176
|
+
messages: {
|
|
3177
|
+
forbidden: baseShouldHaveSuffix,
|
|
3178
|
+
forbiddenProperty: `${baseShouldHaveSuffix} Add a type annotation, assertion, or 'satisfies' to silence this rule.`
|
|
3179
|
+
},
|
|
3180
|
+
schema: [{
|
|
3142
3181
|
properties: {
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3182
|
+
parameters: {
|
|
3183
|
+
type: "boolean",
|
|
3184
|
+
description: "Require for parameters."
|
|
3185
|
+
},
|
|
3186
|
+
properties: {
|
|
3187
|
+
type: "boolean",
|
|
3188
|
+
description: "Require for properties, except object literal keys (see \"objects\" option)."
|
|
3189
|
+
},
|
|
3190
|
+
objects: {
|
|
3191
|
+
type: "boolean",
|
|
3192
|
+
description: "Require for object literal keys."
|
|
3193
|
+
},
|
|
3194
|
+
suffix: {
|
|
3195
|
+
type: "string",
|
|
3196
|
+
description: "The suffix to enforce."
|
|
3197
|
+
},
|
|
3198
|
+
types: {
|
|
3199
|
+
type: "object",
|
|
3200
|
+
description: "Enforce for specific types. Keys are a RegExp, values are a boolean."
|
|
3201
|
+
},
|
|
3202
|
+
variables: {
|
|
3203
|
+
type: "boolean",
|
|
3204
|
+
description: "Require for variables."
|
|
3205
|
+
}
|
|
3157
3206
|
},
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3207
|
+
type: "object"
|
|
3208
|
+
}],
|
|
3209
|
+
type: "problem",
|
|
3210
|
+
defaultOptions: [{
|
|
3211
|
+
parameters: true,
|
|
3212
|
+
properties: true,
|
|
3213
|
+
objects: true,
|
|
3214
|
+
suffix: "Subject",
|
|
3215
|
+
types: { "^EventEmitter$": false },
|
|
3216
|
+
variables: true
|
|
3217
|
+
}]
|
|
3218
|
+
},
|
|
3219
|
+
name: "suffix-subjects",
|
|
3220
|
+
create: (context) => {
|
|
3221
|
+
const { esTreeNodeToTSNodeMap } = ESLintUtils.getParserServices(context);
|
|
3222
|
+
const { couldBeType } = getTypeServices(context);
|
|
3223
|
+
const [config] = context.options;
|
|
3224
|
+
const types = [];
|
|
3225
|
+
if (config.types) for (const [key, validate] of Object.entries(config.types)) types.push({
|
|
3226
|
+
regExp: new RegExp(key),
|
|
3227
|
+
validate
|
|
3228
|
+
});
|
|
3229
|
+
const { suffix = "Subject" } = config;
|
|
3230
|
+
const suffixRegex = new RegExp(String.raw`${escapeRegExp(suffix)}\$?$`, "i");
|
|
3231
|
+
function checkNode(nameNode, typeNode, shouldMessage = "forbidden") {
|
|
3232
|
+
const tsNode = esTreeNodeToTSNodeMap.get(nameNode);
|
|
3233
|
+
const text = tsNode.getText();
|
|
3234
|
+
if (!suffixRegex.test(text) && couldBeType(typeNode ?? nameNode, "Subject")) {
|
|
3235
|
+
for (const type of types) {
|
|
3236
|
+
const { regExp, validate } = type;
|
|
3237
|
+
if (couldBeType(typeNode ?? nameNode, regExp) && !validate) return;
|
|
3238
|
+
}
|
|
3239
|
+
context.report({
|
|
3240
|
+
data: { suffix },
|
|
3241
|
+
loc: getLoc(tsNode),
|
|
3242
|
+
messageId: shouldMessage
|
|
3243
|
+
});
|
|
3194
3244
|
}
|
|
3195
|
-
context.report({
|
|
3196
|
-
data: { suffix },
|
|
3197
|
-
loc: getLoc(tsNode),
|
|
3198
|
-
messageId: shouldMessage
|
|
3199
|
-
});
|
|
3200
3245
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
if (config.parameters) {
|
|
3246
|
+
return {
|
|
3247
|
+
"ArrayPattern > Identifier": (node) => {
|
|
3248
|
+
const found = findParent(node, "ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression", "VariableDeclarator");
|
|
3249
|
+
if (!found) return;
|
|
3250
|
+
if (!config.variables && isVariableDeclarator(found)) return;
|
|
3251
|
+
if (!config.parameters) return;
|
|
3252
|
+
checkNode(node);
|
|
3253
|
+
},
|
|
3254
|
+
"ArrowFunctionExpression > Identifier": (node) => {
|
|
3255
|
+
if (!config.parameters) return;
|
|
3212
3256
|
if (node !== node.parent.body) checkNode(node);
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
const anyNode = node;
|
|
3217
|
-
if (config.properties) {
|
|
3257
|
+
},
|
|
3258
|
+
"PropertyDefinition[computed=false]": (node) => {
|
|
3259
|
+
if (!config.properties) return;
|
|
3218
3260
|
if (node.override) return;
|
|
3219
|
-
checkNode(
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
if (config.parameters) {
|
|
3261
|
+
checkNode(node.key);
|
|
3262
|
+
},
|
|
3263
|
+
"FunctionDeclaration > Identifier": (node) => {
|
|
3264
|
+
if (!config.parameters) return;
|
|
3224
3265
|
if (node !== node.parent.id) checkNode(node);
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
if (config.parameters) {
|
|
3266
|
+
},
|
|
3267
|
+
"FunctionExpression > Identifier": (node) => {
|
|
3268
|
+
if (!config.parameters) return;
|
|
3229
3269
|
if (node !== node.parent.id) checkNode(node);
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
if (config.properties) {
|
|
3270
|
+
},
|
|
3271
|
+
"MethodDefinition[kind='get'][computed=false]": (node) => {
|
|
3272
|
+
if (!config.properties) return;
|
|
3234
3273
|
if (node.override) return;
|
|
3235
3274
|
checkNode(node.key, node);
|
|
3236
|
-
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
if (config.properties) {
|
|
3275
|
+
},
|
|
3276
|
+
"MethodDefinition[kind='set'][computed=false]": (node) => {
|
|
3277
|
+
if (!config.properties) return;
|
|
3240
3278
|
if (node.override) return;
|
|
3241
3279
|
checkNode(node.key, node);
|
|
3280
|
+
},
|
|
3281
|
+
"TSAbstractMethodDefinition[computed=false]": (node) => {
|
|
3282
|
+
if (node.override) return;
|
|
3283
|
+
if (config.properties && (node.kind === "get" || node.kind === "set")) checkNode(node.key, node);
|
|
3284
|
+
if (config.parameters) for (const param of node.value.params) checkNode(param);
|
|
3285
|
+
},
|
|
3286
|
+
"ObjectExpression > Property[computed=false] > Identifier": (node) => {
|
|
3287
|
+
if (!config.objects) return;
|
|
3288
|
+
if (node !== node.parent.key) return;
|
|
3289
|
+
const found = findParent(node, "CallExpression", "VariableDeclarator", "TSSatisfiesExpression", "TSAsExpression");
|
|
3290
|
+
if (found) {
|
|
3291
|
+
if (isCallExpression(found)) return;
|
|
3292
|
+
else if (isVariableDeclarator(found) && !!found.id.typeAnnotation) return;
|
|
3293
|
+
else if (isTSAsExpression(found) || isTSSatisfiesExpression(found)) return;
|
|
3294
|
+
}
|
|
3295
|
+
checkNode(node, void 0, "forbiddenProperty");
|
|
3296
|
+
},
|
|
3297
|
+
"ObjectPattern > Property > Identifier": (node) => {
|
|
3298
|
+
const found = findParent(node, "ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression", "VariableDeclarator");
|
|
3299
|
+
if (!found) return;
|
|
3300
|
+
if (!config.variables && isVariableDeclarator(found)) return;
|
|
3301
|
+
if (!config.parameters) return;
|
|
3302
|
+
if (node === node.parent.value) checkNode(node);
|
|
3303
|
+
},
|
|
3304
|
+
"TSCallSignatureDeclaration > Identifier": (node) => {
|
|
3305
|
+
if (config.parameters) checkNode(node);
|
|
3306
|
+
},
|
|
3307
|
+
"TSConstructSignatureDeclaration > Identifier": (node) => {
|
|
3308
|
+
if (config.parameters) checkNode(node);
|
|
3309
|
+
},
|
|
3310
|
+
"TSMethodSignature > Identifier": (node) => {
|
|
3311
|
+
if (config.parameters) checkNode(node);
|
|
3312
|
+
},
|
|
3313
|
+
"TSParameterProperty > Identifier": (node) => {
|
|
3314
|
+
if (config.parameters || config.properties) checkNode(node);
|
|
3315
|
+
},
|
|
3316
|
+
"TSPropertySignature[computed=false]": (node) => {
|
|
3317
|
+
if (config.properties) checkNode(node.key);
|
|
3318
|
+
},
|
|
3319
|
+
"VariableDeclarator > Identifier": (node) => {
|
|
3320
|
+
const parent = node.parent;
|
|
3321
|
+
if (config.variables && node === parent.id) checkNode(node);
|
|
3242
3322
|
}
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
}),
|
|
3326
|
+
"throw-error": ruleCreator({
|
|
3327
|
+
meta: {
|
|
3328
|
+
docs: {
|
|
3329
|
+
description: "Enforce passing only `Error` values to `throwError` or `Subject.error`.",
|
|
3330
|
+
recommended: {
|
|
3331
|
+
recommended: true,
|
|
3332
|
+
strict: [{
|
|
3333
|
+
allowThrowingAny: false,
|
|
3334
|
+
allowThrowingUnknown: false
|
|
3335
|
+
}]
|
|
3336
|
+
},
|
|
3337
|
+
requiresTypeChecking: true
|
|
3243
3338
|
},
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
},
|
|
3249
|
-
"ObjectExpression > Property[computed=false] > Identifier": (node) => {
|
|
3250
|
-
if (!config.objects) return;
|
|
3251
|
-
if (node !== node.parent.key) return;
|
|
3252
|
-
const found = findParent(node, "CallExpression", "VariableDeclarator", "TSSatisfiesExpression", "TSAsExpression");
|
|
3253
|
-
if (found) {
|
|
3254
|
-
if (isCallExpression(found)) return;
|
|
3255
|
-
else if (isVariableDeclarator(found) && !!found.id.typeAnnotation) return;
|
|
3256
|
-
else if (isTSAsExpression(found) || isTSSatisfiesExpression(found)) return;
|
|
3257
|
-
}
|
|
3258
|
-
checkNode(node, void 0, "forbiddenProperty");
|
|
3259
|
-
},
|
|
3260
|
-
"ObjectPattern > Property > Identifier": (node) => {
|
|
3261
|
-
const found = findParent(node, "ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression", "VariableDeclarator");
|
|
3262
|
-
if (!found) return;
|
|
3263
|
-
if (!config.variables && isVariableDeclarator(found)) return;
|
|
3264
|
-
if (!config.parameters) return;
|
|
3265
|
-
if (node === node.parent.value) checkNode(node);
|
|
3266
|
-
},
|
|
3267
|
-
"TSCallSignatureDeclaration > Identifier": (node) => {
|
|
3268
|
-
if (config.parameters) checkNode(node);
|
|
3269
|
-
},
|
|
3270
|
-
"TSConstructSignatureDeclaration > Identifier": (node) => {
|
|
3271
|
-
if (config.parameters) checkNode(node);
|
|
3272
|
-
},
|
|
3273
|
-
"TSMethodSignature > Identifier": (node) => {
|
|
3274
|
-
if (config.parameters) checkNode(node);
|
|
3275
|
-
},
|
|
3276
|
-
"TSParameterProperty > Identifier": (node) => {
|
|
3277
|
-
if (config.parameters || config.properties) checkNode(node);
|
|
3278
|
-
},
|
|
3279
|
-
"TSPropertySignature[computed=false]": (node) => {
|
|
3280
|
-
if (config.properties) checkNode(node.key);
|
|
3281
|
-
},
|
|
3282
|
-
"VariableDeclarator > Identifier": (node) => {
|
|
3283
|
-
const parent = node.parent;
|
|
3284
|
-
if (config.variables && node === parent.id) checkNode(node);
|
|
3285
|
-
}
|
|
3286
|
-
};
|
|
3287
|
-
}
|
|
3288
|
-
});
|
|
3289
|
-
//#endregion
|
|
3290
|
-
//#region src/rules/throw-error.ts
|
|
3291
|
-
const throwErrorRule = ruleCreator({
|
|
3292
|
-
meta: {
|
|
3293
|
-
docs: {
|
|
3294
|
-
description: "Enforce passing only `Error` values to `throwError` or `Subject.error`.",
|
|
3295
|
-
recommended: {
|
|
3296
|
-
recommended: true,
|
|
3297
|
-
strict: [{
|
|
3298
|
-
allowThrowingAny: false,
|
|
3299
|
-
allowThrowingUnknown: false
|
|
3300
|
-
}]
|
|
3339
|
+
hasSuggestions: true,
|
|
3340
|
+
messages: {
|
|
3341
|
+
forbidden: "Passing non-Error values is forbidden.",
|
|
3342
|
+
suggestErrorConstructor: "Wrap string in Error constructor."
|
|
3301
3343
|
},
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
type: "boolean",
|
|
3313
|
-
description: "Whether to always allow throwing values typed as `any`."
|
|
3344
|
+
schema: [{
|
|
3345
|
+
properties: {
|
|
3346
|
+
allowThrowingAny: {
|
|
3347
|
+
type: "boolean",
|
|
3348
|
+
description: "Whether to always allow throwing values typed as `any`."
|
|
3349
|
+
},
|
|
3350
|
+
allowThrowingUnknown: {
|
|
3351
|
+
type: "boolean",
|
|
3352
|
+
description: "Whether to always allow throwing values typed as `unknown`."
|
|
3353
|
+
}
|
|
3314
3354
|
},
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3355
|
+
type: "object"
|
|
3356
|
+
}],
|
|
3357
|
+
type: "problem",
|
|
3358
|
+
defaultOptions: [{
|
|
3359
|
+
allowThrowingAny: true,
|
|
3360
|
+
allowThrowingUnknown: true
|
|
3361
|
+
}]
|
|
3362
|
+
},
|
|
3363
|
+
name: "throw-error",
|
|
3364
|
+
create: (context) => {
|
|
3365
|
+
const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils.getParserServices(context);
|
|
3366
|
+
const { couldBeObservable, couldBeSubject } = getTypeServices(context);
|
|
3367
|
+
const [{ allowThrowingAny, allowThrowingUnknown }] = context.options;
|
|
3368
|
+
function checkThrowArgument(node, noFunction = false) {
|
|
3369
|
+
let type = getTypeAtLocation(node);
|
|
3370
|
+
let reportNode = node;
|
|
3371
|
+
if (!noFunction && couldBeFunction(type)) {
|
|
3372
|
+
reportNode = node.body ?? node;
|
|
3373
|
+
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
3374
|
+
const annotation = tsNode.type;
|
|
3375
|
+
const body = tsNode.body;
|
|
3376
|
+
type = program.getTypeChecker().getTypeAtLocation(annotation ?? body);
|
|
3318
3377
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
let type = getTypeAtLocation(node);
|
|
3335
|
-
let reportNode = node;
|
|
3336
|
-
if (!noFunction && couldBeFunction(type)) {
|
|
3337
|
-
reportNode = node.body ?? node;
|
|
3338
|
-
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
3339
|
-
const annotation = tsNode.type;
|
|
3340
|
-
const body = tsNode.body;
|
|
3341
|
-
type = program.getTypeChecker().getTypeAtLocation(annotation ?? body);
|
|
3378
|
+
if (allowThrowingAny && tsutils.isIntrinsicAnyType(type)) return;
|
|
3379
|
+
if (allowThrowingUnknown && tsutils.isIntrinsicUnknownType(type)) return;
|
|
3380
|
+
if (couldBeType(type, /^Error$/)) return;
|
|
3381
|
+
const isString = isTemplateLiteral(reportNode) || isLiteral(reportNode) && typeof reportNode.value === "string";
|
|
3382
|
+
context.report({
|
|
3383
|
+
messageId: "forbidden",
|
|
3384
|
+
node: reportNode,
|
|
3385
|
+
...isString && { suggest: [{
|
|
3386
|
+
messageId: "suggestErrorConstructor",
|
|
3387
|
+
fix: (fixer) => {
|
|
3388
|
+
const nodeText = context.sourceCode.getText(reportNode);
|
|
3389
|
+
return fixer.replaceText(reportNode, `new Error(${nodeText})`);
|
|
3390
|
+
}
|
|
3391
|
+
}] }
|
|
3392
|
+
});
|
|
3342
3393
|
}
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
if (couldBeType(type, /^Error$/)) return;
|
|
3346
|
-
const isString = isTemplateLiteral(reportNode) || isLiteral(reportNode) && typeof reportNode.value === "string";
|
|
3347
|
-
context.report({
|
|
3348
|
-
messageId: "forbidden",
|
|
3349
|
-
node: reportNode,
|
|
3350
|
-
...isString && { suggest: [{
|
|
3351
|
-
messageId: "suggestErrorConstructor",
|
|
3352
|
-
fix: (fixer) => {
|
|
3353
|
-
const nodeText = context.sourceCode.getText(reportNode);
|
|
3354
|
-
return fixer.replaceText(reportNode, `new Error(${nodeText})`);
|
|
3355
|
-
}
|
|
3356
|
-
}] }
|
|
3357
|
-
});
|
|
3358
|
-
}
|
|
3359
|
-
function checkThrowError(node) {
|
|
3360
|
-
if (couldBeObservable(node)) {
|
|
3394
|
+
function checkThrowError(node) {
|
|
3395
|
+
if (!couldBeObservable(node)) return;
|
|
3361
3396
|
const [arg] = node.arguments;
|
|
3362
3397
|
if (arg) checkThrowArgument(arg);
|
|
3363
3398
|
}
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
if (isMemberExpression(node.callee) && couldBeSubject(node.callee.object)) {
|
|
3399
|
+
function checkSubjectError(node) {
|
|
3400
|
+
if (!isMemberExpression(node.callee) || !couldBeSubject(node.callee.object)) return;
|
|
3367
3401
|
const [arg] = node.arguments;
|
|
3368
3402
|
if (arg) checkThrowArgument(arg, true);
|
|
3369
3403
|
}
|
|
3404
|
+
return {
|
|
3405
|
+
"CallExpression[callee.name='throwError']": checkThrowError,
|
|
3406
|
+
"CallExpression[callee.property.name='throwError']": checkThrowError,
|
|
3407
|
+
"CallExpression[callee.property.name='error']": checkSubjectError
|
|
3408
|
+
};
|
|
3370
3409
|
}
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
"CallExpression[callee.property.name='throwError']": checkThrowError,
|
|
3374
|
-
"CallExpression[callee.property.name='error']": checkSubjectError
|
|
3375
|
-
};
|
|
3376
|
-
}
|
|
3377
|
-
});
|
|
3410
|
+
})
|
|
3411
|
+
};
|
|
3378
3412
|
const plugin = {
|
|
3379
3413
|
meta: {
|
|
3380
3414
|
name,
|
|
3381
3415
|
version,
|
|
3382
3416
|
namespace: "rxjs-x"
|
|
3383
3417
|
},
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
"ban-operators": banOperatorsRule,
|
|
3387
|
-
"finnish": finnishRule,
|
|
3388
|
-
"just": justRule,
|
|
3389
|
-
"no-async-subscribe": noAsyncSubscribeRule,
|
|
3390
|
-
"no-connectable": noConnectableRule,
|
|
3391
|
-
"no-create": noCreateRule,
|
|
3392
|
-
"no-cyclic-action": noCyclicActionRule,
|
|
3393
|
-
"no-explicit-generics": noExplicitGenericsRule,
|
|
3394
|
-
"no-exposed-subjects": noExposedSubjectsRule,
|
|
3395
|
-
"no-finnish": noFinnishRule,
|
|
3396
|
-
"no-floating-observables": noFloatingObservablesRule,
|
|
3397
|
-
"no-ignored-default-value": noIgnoredDefaultValueRule,
|
|
3398
|
-
"no-ignored-error": noIgnoredErrorRule,
|
|
3399
|
-
"no-ignored-notifier": noIgnoredNotifierRule,
|
|
3400
|
-
"no-ignored-replay-buffer": noIgnoredReplayBufferRule,
|
|
3401
|
-
"no-ignored-subscribe": noIgnoredSubscribeRule,
|
|
3402
|
-
"no-ignored-subscription": noIgnoredSubscriptionRule,
|
|
3403
|
-
"no-ignored-takewhile-value": noIgnoredTakewhileValueRule,
|
|
3404
|
-
"no-implicit-any-catch": noImplicitAnyCatchRule,
|
|
3405
|
-
"no-index": noIndexRule,
|
|
3406
|
-
"no-internal": noInternalRule,
|
|
3407
|
-
"no-misused-observables": noMisusedObservablesRule,
|
|
3408
|
-
"no-nested-subscribe": noNestedSubscribeRule,
|
|
3409
|
-
"no-redundant-notify": noRedundantNotifyRule,
|
|
3410
|
-
"no-sharereplay": noSharereplayRule,
|
|
3411
|
-
"no-sharereplay-before-takeuntil": noSharereplayBeforeTakeuntilRule,
|
|
3412
|
-
"no-subclass": noSubclassRule,
|
|
3413
|
-
"no-subject-unsubscribe": noSubjectUnsubscribeRule,
|
|
3414
|
-
"no-subject-value": noSubjectValueRule,
|
|
3415
|
-
"no-subscribe-handlers": noSubscribeHandlersRule,
|
|
3416
|
-
"no-subscribe-in-pipe": noSubscribeInPipeRule,
|
|
3417
|
-
"no-topromise": noTopromiseRule,
|
|
3418
|
-
"no-unbound-methods": noUnboundMethodsRule,
|
|
3419
|
-
"no-unnecessary-collection": noUnnecessaryCollectionRule,
|
|
3420
|
-
"no-unsafe-catch": noUnsafeCatchRule,
|
|
3421
|
-
"no-unsafe-first": noUnsafeFirstRule,
|
|
3422
|
-
"no-unsafe-subject-next": noUnsafeSubjectNext,
|
|
3423
|
-
"no-unsafe-switchmap": noUnsafeSwitchmapRule,
|
|
3424
|
-
"no-unsafe-takeuntil": noUnsafeTakeuntilRule,
|
|
3425
|
-
"prefer-observer": preferObserverRule,
|
|
3426
|
-
"prefer-root-operators": preferRootOperatorsRule,
|
|
3427
|
-
"suffix-subjects": suffixSubjectsRule,
|
|
3428
|
-
"throw-error": throwErrorRule
|
|
3429
|
-
}
|
|
3418
|
+
/** Compatibility with `defineConfig` until https://github.com/typescript-eslint/typescript-eslint/issues/11543 is addressed. */
|
|
3419
|
+
rules: allRules
|
|
3430
3420
|
};
|
|
3431
3421
|
const rxjsX = {
|
|
3432
3422
|
...plugin,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-rxjs-x",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Modern ESLint plugin for RxJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lint",
|
|
@@ -46,9 +46,6 @@
|
|
|
46
46
|
"test": "vitest",
|
|
47
47
|
"typecheck": "tsc --noEmit"
|
|
48
48
|
},
|
|
49
|
-
"resolutions": {
|
|
50
|
-
"rolldown": "^1.0.0-rc.12"
|
|
51
|
-
},
|
|
52
49
|
"dependencies": {
|
|
53
50
|
"@typescript-eslint/utils": "^8.58.0",
|
|
54
51
|
"decamelize": "^6.0.1",
|
|
@@ -56,34 +53,41 @@
|
|
|
56
53
|
},
|
|
57
54
|
"devDependencies": {
|
|
58
55
|
"@eslint/js": "^10.0.1",
|
|
56
|
+
"@fast-check/vitest": "^0.4.1",
|
|
59
57
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
60
58
|
"@types/common-tags": "^1.8.4",
|
|
61
|
-
"@types/node": "~20.19.
|
|
62
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
59
|
+
"@types/node": "~20.19.43",
|
|
60
|
+
"@typescript-eslint/rule-tester": "^8.63.0",
|
|
63
61
|
"@typescript/vfs": "^1.6.4",
|
|
64
|
-
"@vitest/coverage-v8": "^4.1.
|
|
65
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
66
|
-
"bumpp": "^11.0
|
|
62
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
63
|
+
"@vitest/eslint-plugin": "^1.6.23",
|
|
64
|
+
"bumpp": "^11.1.0",
|
|
67
65
|
"common-tags": "^1.8.2",
|
|
68
|
-
"eslint": "^10.
|
|
66
|
+
"eslint": "^10.7.0",
|
|
69
67
|
"eslint-config-flat-gitignore": "^2.3.0",
|
|
70
|
-
"eslint-doc-generator": "^3.
|
|
71
|
-
"eslint-import-resolver-typescript": "^4.4.
|
|
68
|
+
"eslint-doc-generator": "^3.6.0",
|
|
69
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
72
70
|
"eslint-plugin-eslint-plugin": "patch:eslint-plugin-eslint-plugin@npm%3A7.3.1#~/.yarn/patches/eslint-plugin-eslint-plugin-npm-7.3.1-6b766f9a07.patch",
|
|
73
|
-
"eslint-plugin-import-x": "^4.
|
|
74
|
-
"eslint-plugin-n": "^
|
|
75
|
-
"eslint-plugin-package-json": "
|
|
76
|
-
"eslint-plugin-perfectionist": "^5.
|
|
77
|
-
"eslint-plugin-regexp": "^3.1.
|
|
71
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
72
|
+
"eslint-plugin-n": "^18.2.2",
|
|
73
|
+
"eslint-plugin-package-json": "~1.3.0",
|
|
74
|
+
"eslint-plugin-perfectionist": "^5.10.0",
|
|
75
|
+
"eslint-plugin-regexp": "^3.1.1",
|
|
78
76
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
77
|
+
"fast-check": "^4.9.0",
|
|
79
78
|
"jsonc-eslint-parser": "^3.1.0",
|
|
80
|
-
"markdownlint-cli2": "~0.
|
|
79
|
+
"markdownlint-cli2": "~0.23.0",
|
|
81
80
|
"rxjs": "^7.8.2",
|
|
82
|
-
"tsdown": "^0.
|
|
83
|
-
"typescript": "~6.0.
|
|
84
|
-
"typescript-eslint": "^8.
|
|
85
|
-
"
|
|
86
|
-
"vitest": "^4.1.
|
|
81
|
+
"tsdown": "^0.22.4",
|
|
82
|
+
"typescript": "~6.0.3",
|
|
83
|
+
"typescript-eslint": "^8.63.0",
|
|
84
|
+
"unrun": "~0.3.1",
|
|
85
|
+
"vitest": "^4.1.10"
|
|
86
|
+
},
|
|
87
|
+
"dependenciesMeta": {
|
|
88
|
+
"unrs-resolver": {
|
|
89
|
+
"built": false
|
|
90
|
+
}
|
|
87
91
|
},
|
|
88
92
|
"peerDependencies": {
|
|
89
93
|
"eslint": "^10.0.1",
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
"optional": true
|
|
96
100
|
}
|
|
97
101
|
},
|
|
98
|
-
"packageManager": "yarn@4.
|
|
102
|
+
"packageManager": "yarn@4.17.1+sha512.ccbfabf7d7b6b32075088be9386fb9a2e00bb6887ef07fa56effabc890a56d53da1ccc4128d62db245fcbd3961b236d75335bdf7d5320ed6eafb7588b7ad4697",
|
|
99
103
|
"engines": {
|
|
100
104
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
|
101
105
|
},
|